diff --git a/plugins/org.eclipse.n4js.cli/pom.xml b/plugins/org.eclipse.n4js.cli/pom.xml index 9b93d26146..d33c735548 100644 --- a/plugins/org.eclipse.n4js.cli/pom.xml +++ b/plugins/org.eclipse.n4js.cli/pom.xml @@ -34,10 +34,6 @@ Contributors: org.apache.maven.plugins maven-clean-plugin - - org.eclipse.xtend - xtend-maven-plugin - org.apache.maven.plugins maven-dependency-plugin diff --git a/plugins/org.eclipse.n4js.common.unicode/.classpath b/plugins/org.eclipse.n4js.common.unicode/.classpath index 3947fc4c6b..3cfbdf7de5 100644 --- a/plugins/org.eclipse.n4js.common.unicode/.classpath +++ b/plugins/org.eclipse.n4js.common.unicode/.classpath @@ -6,7 +6,6 @@ - diff --git a/plugins/org.eclipse.n4js.common.unicode/build.properties b/plugins/org.eclipse.n4js.common.unicode/build.properties index b7d1cdc73c..a73891df20 100644 --- a/plugins/org.eclipse.n4js.common.unicode/build.properties +++ b/plugins/org.eclipse.n4js.common.unicode/build.properties @@ -1,7 +1,6 @@ source.. = src/,\ src-gen/,\ - grammar-gen/,\ - xtend-gen/ + grammar-gen/ bin.includes = META-INF/,\ .,\ about.html diff --git a/plugins/org.eclipse.n4js.common.unicode/pom.xml b/plugins/org.eclipse.n4js.common.unicode/pom.xml index 665e2ad854..6d0236149e 100644 --- a/plugins/org.eclipse.n4js.common.unicode/pom.xml +++ b/plugins/org.eclipse.n4js.common.unicode/pom.xml @@ -61,12 +61,6 @@ Contributors: - - - org.eclipse.xtend - xtend-maven-plugin - - diff --git a/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeRuntimeModule.xtend b/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeRuntimeModule.java similarity index 79% rename from plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeRuntimeModule.xtend rename to plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeRuntimeModule.java index f244e94392..9f7264bea1 100644 --- a/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeRuntimeModule.xtend +++ b/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeRuntimeModule.java @@ -8,11 +8,11 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.common.unicode - +package org.eclipse.n4js.common.unicode; /** * Use this class to register components to be used at runtime / without the Equinox extension registry. */ -class UnicodeRuntimeModule extends AbstractUnicodeRuntimeModule { +public class UnicodeRuntimeModule extends AbstractUnicodeRuntimeModule { + // empty } diff --git a/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeStandaloneSetup.xtend b/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeStandaloneSetup.java similarity index 76% rename from plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeStandaloneSetup.xtend rename to plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeStandaloneSetup.java index 5438dc513d..461932a613 100644 --- a/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeStandaloneSetup.xtend +++ b/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/UnicodeStandaloneSetup.java @@ -8,15 +8,15 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.common.unicode - +package org.eclipse.n4js.common.unicode; /** * Initialization support for running Xtext languages without Equinox extension registry. */ -class UnicodeStandaloneSetup extends UnicodeStandaloneSetupGenerated { +public class UnicodeStandaloneSetup extends UnicodeStandaloneSetupGenerated { - def static void doSetup() { - new UnicodeStandaloneSetup().createInjectorAndDoEMFRegistration() + /***/ + static public void doSetup() { + new UnicodeStandaloneSetup().createInjectorAndDoEMFRegistration(); } } diff --git a/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/generator/UnicodeGrammarGenerator.java b/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/generator/UnicodeGrammarGenerator.java new file mode 100644 index 0000000000..69d5c253df --- /dev/null +++ b/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/generator/UnicodeGrammarGenerator.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.common.unicode.generator; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.function.Function; + +import com.google.common.base.Charsets; +import com.google.common.base.Strings; +import com.google.common.io.Files; + +/***/ +public class UnicodeGrammarGenerator { + + /** + * This generator isn't called by the GenerateUnicode.mwe2, this have to be done manually + */ + @SuppressWarnings("unused") + static void main(String[] args) throws IOException { + // if (args.head == '-file') + new UnicodeGrammarGenerator(); + // else + // println(generateUnicodeRules) + } + + /** + * The write-on-instantiation allows to use this generator in mwe2 as #bean + */ + UnicodeGrammarGenerator() throws IOException { + Files.asCharSink(new File("grammar-gen/org/eclipse/n4js/common/unicode/Unicode.xtext"), Charsets.UTF_8) + .write(generateUnicodeRules()); + } + + static String generateUnicodeRules() { + return """ + /** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ + + // Important note: + // This grammar is auto generated by the + // org.eclipse.n4js.common.unicode.generator.UnicodeGrammarGenerator + // + // Rather than editing this manually, update the generator instead! + + grammar org.eclipse.n4js.common.unicode.Unicode + + import "http://www.eclipse.org/emf/2002/Ecore" as ecore + + terminal fragment HEX_DIGIT: + (DECIMAL_DIGIT_FRAGMENT|'a'..'f'|'A'..'F') + ; + terminal fragment DECIMAL_INTEGER_LITERAL_FRAGMENT: + '0' + | '1'..'9' DECIMAL_DIGIT_FRAGMENT* + ; + terminal fragment DECIMAL_DIGIT_FRAGMENT: + '0'..'9' + ; + terminal fragment ZWJ: + '\u200D' + ; + terminal fragment ZWNJ: + '\u200C' + ; + terminal fragment BOM: + '\uFEFF' + ; + terminal fragment WHITESPACE_FRAGMENT: + '\u0009' | '\u000B' | '\u000C' | '\u0020' | '\u00A0' | BOM | UNICODE_SPACE_SEPARATOR_FRAGMENT + ; + terminal fragment LINE_TERMINATOR_FRAGMENT: + '\u000A' | '\u000D' | '\u2028' | '\u2029' + ; + terminal fragment LINE_TERMINATOR_SEQUENCE_FRAGMENT: + '\u000A' | '\u000D' '\u000A'? | '\u2028' | '\u2029' + ; + terminal fragment SL_COMMENT_FRAGMENT: + '//' (!LINE_TERMINATOR_FRAGMENT)* + ; + terminal fragment ML_COMMENT_FRAGMENT: + '/*' -> '*/' + ; + + terminal fragment UNICODE_COMBINING_MARK_FRAGMENT: + // any character in the Unicode categories + // ―Non-spacing mark (Mn) + // ―Combining spacing mark (Mc) + «generateUnicodeRules [ isCombiningMark ]» + ; + terminal fragment UNICODE_DIGIT_FRAGMENT: + // any character in the Unicode categories + // ―Decimal number (Nd) + «generateUnicodeRules [ isDigit ]» + ; + terminal fragment UNICODE_CONNECTOR_PUNCTUATION_FRAGMENT: + // any character in the Unicode categories + // ―Connector punctuation (Pc) + «generateUnicodeRules [ isConnectorPunctuation ]» + ; + terminal fragment UNICODE_LETTER_FRAGMENT: + // any character in the Unicode categories + // ―Uppercase letter (Lu) + // ―Lowercase letter (Ll) + // ―Titlecase letter (Lt) + // ―Modifier letter (Lm) + // ―Other letter (Lo) + // ―Letter number (Nl) + «generateUnicodeRules [ isLetter ]» + ; + terminal fragment UNICODE_SPACE_SEPARATOR_FRAGMENT: + // any character in the Unicode categories + // ―space separator (Zs) + «generateUnicodeRules [ isSpaceSeparator ]» + ; + terminal fragment ANY_OTHER: + . + ; + """; + } + + static StringWriter generateUnicodeRules(Function guard) { + Character prev = null; + boolean run = false; + boolean first = true; + char c = Character.MIN_VALUE; + StringWriter result = new StringWriter(); + PrintWriter printer = new PrintWriter(result, true); + while (true) { + if (guard.apply((int) c)) { + if (!run) { + prev = c; + run = true; + } + } else { + if (run && prev != null) { + if (!first) { + printer.print("| "); + } else { + printer.print(" "); + first = false; + } + printer.print("'\\u" + Strings.padStart(Integer.toHexString(prev).toUpperCase(), 4, '0') + "'"); + if (prev.charValue() == c - 1) { + printer.println(); + } else { + printer.println( + "..'\\u" + Strings.padStart(Integer.toHexString(c - 1).toUpperCase(), 4, '0') + "'"); + } + prev = null; + run = false; + } + } + c = (char) (c + 1); + if (c == Character.MAX_VALUE) { + return result; + } + } + } + +} diff --git a/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/generator/UnicodeGrammarGenerator.xtend b/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/generator/UnicodeGrammarGenerator.xtend deleted file mode 100644 index aaf742462e..0000000000 --- a/plugins/org.eclipse.n4js.common.unicode/src/org/eclipse/n4js/common/unicode/generator/UnicodeGrammarGenerator.xtend +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.common.unicode.generator - -import com.google.common.base.Charsets -import com.google.common.base.Strings -import com.google.common.io.Files -import java.io.File -import java.io.PrintWriter -import java.io.StringWriter - -import static extension org.eclipse.n4js.common.unicode.CharTypes.* -import java.io.IOException - -class UnicodeGrammarGenerator { - - /** - * This generator isn't called by the GenerateUnicode.mwe2, this have to be done manually - */ - def static void main(String[] args) throws IOException { -// if (args.head == '-file') - new UnicodeGrammarGenerator -// else -// println(generateUnicodeRules) - } - - /** - * The write-on-instantiation allows to use this generator in mwe2 as #bean - */ - new() throws IOException { - Files.asCharSink(new File('grammar-gen/org/eclipse/n4js/common/unicode/Unicode.xtext'), Charsets.UTF_8).write(generateUnicodeRules); - } - - def static generateUnicodeRules() ''' - /** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ - - // Important note: - // This grammar is auto generated by the - // org.eclipse.n4js.common.unicode.generator.UnicodeGrammarGenerator - // - // Rather than editing this manually, update the generator instead! - - grammar org.eclipse.n4js.common.unicode.Unicode - - import "http://www.eclipse.org/emf/2002/Ecore" as ecore - - terminal fragment HEX_DIGIT: - (DECIMAL_DIGIT_FRAGMENT|'a'..'f'|'A'..'F') - ; - terminal fragment DECIMAL_INTEGER_LITERAL_FRAGMENT: - '0' - | '1'..'9' DECIMAL_DIGIT_FRAGMENT* - ; - terminal fragment DECIMAL_DIGIT_FRAGMENT: - '0'..'9' - ; - terminal fragment ZWJ: - '\u200D' - ; - terminal fragment ZWNJ: - '\u200C' - ; - terminal fragment BOM: - '\uFEFF' - ; - terminal fragment WHITESPACE_FRAGMENT: - '\u0009' | '\u000B' | '\u000C' | '\u0020' | '\u00A0' | BOM | UNICODE_SPACE_SEPARATOR_FRAGMENT - ; - terminal fragment LINE_TERMINATOR_FRAGMENT: - '\u000A' | '\u000D' | '\u2028' | '\u2029' - ; - terminal fragment LINE_TERMINATOR_SEQUENCE_FRAGMENT: - '\u000A' | '\u000D' '\u000A'? | '\u2028' | '\u2029' - ; - terminal fragment SL_COMMENT_FRAGMENT: - '//' (!LINE_TERMINATOR_FRAGMENT)* - ; - terminal fragment ML_COMMENT_FRAGMENT: - '/*' -> '*/' - ; - - terminal fragment UNICODE_COMBINING_MARK_FRAGMENT: - // any character in the Unicode categories - // ―Non-spacing mark (Mn) - // ―Combining spacing mark (Mc) - «generateUnicodeRules [ isCombiningMark ]» - ; - terminal fragment UNICODE_DIGIT_FRAGMENT: - // any character in the Unicode categories - // ―Decimal number (Nd) - «generateUnicodeRules [ isDigit ]» - ; - terminal fragment UNICODE_CONNECTOR_PUNCTUATION_FRAGMENT: - // any character in the Unicode categories - // ―Connector punctuation (Pc) - «generateUnicodeRules [ isConnectorPunctuation ]» - ; - terminal fragment UNICODE_LETTER_FRAGMENT: - // any character in the Unicode categories - // ―Uppercase letter (Lu) - // ―Lowercase letter (Ll) - // ―Titlecase letter (Lt) - // ―Modifier letter (Lm) - // ―Other letter (Lo) - // ―Letter number (Nl) - «generateUnicodeRules [ isLetter ]» - ; - terminal fragment UNICODE_SPACE_SEPARATOR_FRAGMENT: - // any character in the Unicode categories - // ―space separator (Zs) - «generateUnicodeRules [ isSpaceSeparator ]» - ; - terminal fragment ANY_OTHER: - . - ; - ''' - def static generateUnicodeRules((int)=>boolean guard) { - var Character prev = null; - var run = false; - var first = true; - var char c = Character.MIN_VALUE - val result = new StringWriter - val printer = new PrintWriter(result, true) - while(true) { - if (guard.apply(c as int)) { - if (!run) { - prev = c; - run = true; - } - } else { - if (run) { - if (!first) { - printer.print("| "); - } else { - printer.print(" "); - first = false; - } - printer.print("'\\u" + Strings.padStart(Integer.toHexString(prev).toUpperCase(), 4, '0') + "'"); - if (prev.charValue() == c - 1) { - printer.println(); - } else { - printer.println("..'\\u" + Strings.padStart(Integer.toHexString(c - 1).toUpperCase(), 4, '0') + "'"); - } - prev = null; - run = false; - } - } - c = (c + 1) as char - if (c == Character.MAX_VALUE) { - return result; - } - } - - } - - -} diff --git a/plugins/org.eclipse.n4js.common.unicode/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.common.unicode/xtend-gen/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/plugins/org.eclipse.n4js.common.unicode/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/plugins/org.eclipse.n4js.dts/pom.xml b/plugins/org.eclipse.n4js.dts/pom.xml index 0b20fe0301..b9645b1212 100644 --- a/plugins/org.eclipse.n4js.dts/pom.xml +++ b/plugins/org.eclipse.n4js.dts/pom.xml @@ -37,11 +37,7 @@ Contributors: org.apache.maven.plugins maven-clean-plugin - - - org.eclipse.xtend - xtend-maven-plugin - + org.apache.maven.plugins maven-dependency-plugin diff --git a/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/AssignmentRelationFactory.java b/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/AssignmentRelationFactory.java index c50d5e9ff1..7148457f77 100644 --- a/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/AssignmentRelationFactory.java +++ b/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/AssignmentRelationFactory.java @@ -207,7 +207,7 @@ private void findInCorrespondingDestructNodes(Multimap assgns, C private void findInDestructNodes(Multimap assgns, DestructNode dNode) { for (Iterator dnIter = dNode.stream().iterator(); dnIter.hasNext();) { DestructNode dnChild = dnIter.next(); - ControlFlowElement lhs = dnChild.getVarRef() != null ? dnChild.getVarRef() : dnChild.getVarDecl(); + ControlFlowElement lhs = dnChild.varRef != null ? dnChild.varRef : dnChild.varDecl; EObject rhs = DestructureUtilsForSymbols.getValueFromDestructuring(dnChild); if (rhs == null) { Symbol undefinedSymbol = symbolFactory.getUndefined(); diff --git a/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/DestructureUtilsForSymbols.java b/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/DestructureUtilsForSymbols.java index c751d3bab0..93b0ef8b75 100644 --- a/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/DestructureUtilsForSymbols.java +++ b/plugins/org.eclipse.n4js.flowgraphs/src/org/eclipse/n4js/flowgraphs/dataflow/DestructureUtilsForSymbols.java @@ -43,8 +43,8 @@ public static EObject getValueFromDestructuring(DestructNode dNode) { return null; } - EObject assignedValue = dNode.getAssignedElem(); - EObject defaultValue = dNode.getDefaultExpr(); + EObject assignedValue = dNode.assignedElem; + EObject defaultValue = dNode.defaultExpr; return respectDefaultValue(assignedValue, defaultValue); } diff --git a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/codeActions/util/ChangeProvider.java b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/codeActions/util/ChangeProvider.java index ec312225d6..eeef172f27 100644 --- a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/codeActions/util/ChangeProvider.java +++ b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/codeActions/util/ChangeProvider.java @@ -193,7 +193,7 @@ public static TextEdit removeText(Document doc, int offset, int length, boolean String resultLineContent = startLineContent + endLineContent; - if (resultLineContent.isBlank()) { + if (resultLineContent.isBlank() && doc.getLineCount() > posEnd.getLine() + 1) { posStart = new Position(posStart.getLine(), 0); posEnd = new Position(posEnd.getLine() + 1, 0); } diff --git a/plugins/org.eclipse.n4js.jsdoc2spec/.classpath b/plugins/org.eclipse.n4js.jsdoc2spec/.classpath index a2e7d69e3d..3628e33687 100644 --- a/plugins/org.eclipse.n4js.jsdoc2spec/.classpath +++ b/plugins/org.eclipse.n4js.jsdoc2spec/.classpath @@ -6,7 +6,6 @@ - diff --git a/plugins/org.eclipse.n4js.jsdoc2spec/build.properties b/plugins/org.eclipse.n4js.jsdoc2spec/build.properties index 0470e65b92..fb36160409 100644 --- a/plugins/org.eclipse.n4js.jsdoc2spec/build.properties +++ b/plugins/org.eclipse.n4js.jsdoc2spec/build.properties @@ -1,5 +1,4 @@ -source.. = src/,\ - xtend-gen/ +source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff --git a/plugins/org.eclipse.n4js.jsdoc2spec/pom.xml b/plugins/org.eclipse.n4js.jsdoc2spec/pom.xml index 4750cb5dd4..b3206816bc 100644 --- a/plugins/org.eclipse.n4js.jsdoc2spec/pom.xml +++ b/plugins/org.eclipse.n4js.jsdoc2spec/pom.xml @@ -49,10 +49,6 @@ Contributors: --> - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocFactory.xtend b/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocFactory.java similarity index 60% rename from plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocFactory.xtend rename to plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocFactory.java index 21f14197a8..8b175069e6 100644 --- a/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocFactory.xtend +++ b/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocFactory.java @@ -10,9 +10,11 @@ */ package org.eclipse.n4js.jsdoc2spec.adoc; -import com.google.inject.Inject -import org.eclipse.n4js.jsdoc.N4JSDocHelper -import java.util.Map +import java.util.Map; + +import org.eclipse.n4js.jsdoc.N4JSDocHelper; + +import com.google.inject.Inject; /** * Creates AsciiDoc spec fragments for spec region entries. @@ -25,20 +27,21 @@ public class ADocFactory { @Inject ADocSerializer ADocSerializer; - /** * Creates the spec of the given entry for the AsciiDoc document. */ - public def CharSequence createSpecRegionString(SpecRequirementSection spec, Map specsByKey) { - return ADocSerializer.process(spec, specsByKey); + public CharSequence createSpecRegionString(SpecRequirementSection spec, + @SuppressWarnings("unused") Map specsByKey) { + return ADocSerializer.process(spec); } /** * Creates the spec of the given entry for the AsciiDoc document. */ - public def CharSequence createSpecRegionString(SpecIdentifiableElementSection spec, Map specsByKey) { - if (spec.getDoclet === null) { - spec.doclet = n4jsDocHelper.getDoclet(spec.identifiableElement); + public CharSequence createSpecRegionString(SpecIdentifiableElementSection spec, + Map specsByKey) { + if (spec.getDoclet() == null) { + spec.setDoclet(n4jsDocHelper.getDoclet(spec.getIdentifiableElement())); } return ADocSerializer.process(spec, specsByKey); } diff --git a/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocSerializer.java b/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocSerializer.java new file mode 100644 index 0000000000..fcc0a417c7 --- /dev/null +++ b/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocSerializer.java @@ -0,0 +1,726 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.jsdoc2spec.adoc; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.eclipse.n4js.jsdoc.N4JSDocletParser.TAG_CODE; +import static org.eclipse.n4js.jsdoc.N4JSDocletParser.TAG_LINK; +import static org.eclipse.n4js.jsdoc.N4JSDocletParser.TAG_REQID; +import static org.eclipse.n4js.jsdoc.N4JSDocletParser.TAG_SPEC; +import static org.eclipse.n4js.jsdoc.N4JSDocletParser.TAG_SPECFROMDESCR; +import static org.eclipse.n4js.jsdoc.N4JSDocletParser.TAG_TASK; +import static org.eclipse.n4js.jsdoc.N4JSDocletParser.TAG_TODO; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.groupBy; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.isNullOrEmpty; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.sortBy; +import static org.eclipse.xtext.xbase.lib.StringExtensions.toFirstUpper; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.jsdoc.dom.Composite; +import org.eclipse.n4js.jsdoc.dom.Doclet; +import org.eclipse.n4js.jsdoc.dom.InlineTag; +import org.eclipse.n4js.jsdoc.dom.LineTag; +import org.eclipse.n4js.jsdoc.dom.Literal; +import org.eclipse.n4js.jsdoc.dom.SimpleTypeReference; +import org.eclipse.n4js.jsdoc.dom.TagValue; +import org.eclipse.n4js.jsdoc.dom.Text; +import org.eclipse.n4js.jsdoc.dom.VariableReference; +import org.eclipse.n4js.jsdoc.tags.DefaultLineTagDefinition; +import org.eclipse.n4js.jsdoc2spec.KeyUtils; +import org.eclipse.n4js.jsdoc2spec.RepoRelativePath; +import org.eclipse.n4js.jsdoc2spec.SpecTestInfo; +import org.eclipse.n4js.ts.types.ContainerType; +import org.eclipse.n4js.ts.types.FieldAccessor; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.SyntaxRelatedTElement; +import org.eclipse.n4js.ts.types.TAnnotation; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMemberWithAccessModifier; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TN4Classifier; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.typesystem.utils.AllSuperTypesCollector; +import org.eclipse.n4js.utils.DeclMergingHelper; +import org.eclipse.n4js.utils.Strings; +import org.eclipse.n4js.validation.N4JSElementKeywordProvider; +import org.eclipse.n4js.validation.ValidatorMessageHelper; + +import com.google.inject.Inject; + +/** + * Print AsciiDoc code of specification JSDoc. Start and end markers are printed by client. + * + * Needs to be injected. + */ +class ADocSerializer { + @Inject + Html2ADocConverter html2aDocConverter; + @Inject + ValidatorMessageHelper validatorMessageHelper; + @Inject + N4JSElementKeywordProvider keywordProvider; + @Inject + RepoRelativePathHolder repoPathHolder; + @Inject + DeclMergingHelper declMergingHelper; + + String process(SpecRequirementSection spec) { + StringBuilder strb = new StringBuilder(); + appendSpecElementPost(strb, spec); + return Strings.stripAllTrailing(strb.toString()); + } + + String process(SpecIdentifiableElementSection spec, Map specsByKey) { + StringBuilder strb = new StringBuilder(); + appendSpecElementPre(strb, spec); + appendSpec(strb, spec); + appendSpecElementPost(strb, spec, specsByKey); + return Strings.stripAllTrailing(strb.toString()); + } + + private StringBuilder appendSpecElementPost(StringBuilder strb, SpecRequirementSection spec) { + if (!isNullOrEmpty(spec.getTestInfosForType())) { + Map> groupdTests = groupBy(spec.getTestInfosForType(), sti -> sti.testTitle); + appendApiConstraints(strb, groupdTests); + } + return strb; + } + + private StringBuilder appendSpec(StringBuilder strb, SpecIdentifiableElementSection spec) { + strb.append("\n"); + + boolean addedTaskLinks = false; + List taskTags = spec.getDoclet().lineTags(TAG_TASK.getTitle()); + for (LineTag tag : taskTags) { + String taskID = TAG_TASK.getValue(tag, ""); + if (!taskID.isEmpty()) { + if (taskID.startsWith("*")) { + appendTaskLink(strb, taskID.substring(1)); + } else { + appendTaskLink(strb, taskID); + } + strb.append(" "); + addedTaskLinks = true; + } + } + if (addedTaskLinks) + strb.append("\n\n"); + + appendSpecDescription(strb, spec); + return strb; + } + + private StringBuilder appendSpecDescription(StringBuilder strb, SpecIdentifiableElementSection spec) { + Doclet doclet = spec.getDoclet(); + boolean bSpecFromDescr = doclet.hasLineTag(TAG_SPECFROMDESCR.getTitle()); + String reqID = getReqId(doclet); + List specTags = doclet.lineTags(TAG_SPEC.getTitle()); + + if (specTags.isEmpty() && !bSpecFromDescr && reqID.isEmpty()) { + return strb; + } + + if (!(spec.idElement instanceof TN4Classifier || spec.idElement instanceof TEnum)) + strb.append("==== Description\n\n"); + + if (!reqID.isEmpty()) { + strb.append("See req:" + reqID + "[].\n"); + } + + appendContents(strb, doclet); + for (LineTag tag : specTags) { + appendContents(strb, tag.getValueByKey(DefaultLineTagDefinition.CONTENTS)); + } + + strb.append("\n"); + + return strb; + } + + private StringBuilder appendSpecDescriptions(StringBuilder strb, Iterable doclets) { + for (Doclet doclet : doclets) { + boolean bSpecFromDescr = doclet.hasLineTag(TAG_SPECFROMDESCR.getTitle()); + List specTags = doclet.lineTags(TAG_SPEC.getTitle()); + if (!specTags.isEmpty() || bSpecFromDescr) { + appendContents(strb, doclet); + for (LineTag tag : specTags) { + appendContents(strb, tag.getValueByKey(DefaultLineTagDefinition.CONTENTS)); + } + } + } + return strb; + } + + private StringBuilder appendContents(StringBuilder strb, Composite composite) { + boolean contentAdded = false; + for (EObject c : composite.eContents()) { + String newContent = processContent(c).toString(); + strb.append(newContent); + contentAdded |= !newContent.isBlank(); + } + if (contentAdded) { + strb.append("\n"); + } + return strb; + } + + private CharSequence processContent(EObject node) { + if (node instanceof Text) { + return processContent((Text) node); + } + if (node instanceof Literal) { + return processContent((Literal) node); + } + if (node instanceof SimpleTypeReference) { + return processContent((SimpleTypeReference) node); + } + if (node instanceof VariableReference) { + return processContent((VariableReference) node); + } + if (node instanceof InlineTag) { + return processContent((InlineTag) node); + } + + return ""; + } + + private CharSequence processContent(Text node) { + return html2aDocConverter.transformHTML(node.getText()); + } + + private CharSequence processContent(Literal node) { + return html2aDocConverter.transformHTML(node.getValue()); + } + + private CharSequence processContent(SimpleTypeReference node) { + return html2aDocConverter.passThenMonospace(html2aDocConverter.transformHTML(node.getTypeName())); + } + + private CharSequence processContent(VariableReference node) { + return html2aDocConverter.passThenMonospace(html2aDocConverter.transformHTML(node.getVariableName())); + } + + private CharSequence processContent(InlineTag node) { + if (Objects.equals(TAG_CODE.getTitle(), node.getTitle().getTitle())) { + return html2aDocConverter.passThenMonospace(html2aDocConverter.transformHTML(TAG_CODE.getValue(node, ""))); + } + if (Objects.equals(TAG_LINK.getTitle(), node.getTitle().getTitle())) { + return html2aDocConverter.passThenMonospace(html2aDocConverter.transformHTML(TAG_LINK.getValue(node, ""))); + } + + StringBuilder strb = new StringBuilder(); + for (TagValue tv : node.getValues()) { + appendContents(strb, tv); + } + return strb; + } + + private StringBuilder appendSpecElementPre(StringBuilder strb, SpecIdentifiableElementSection spec) { + IdentifiableElement element = spec.getIdentifiableElement(); + if (element instanceof TMember) { + return appendElementCodePre(strb, (TMember) element, spec); + } + if (element instanceof TMethod) { + return appendElementCodePre(strb, (TMethod) element, spec); + } + if (element instanceof TFunction) { + return appendElementCodePre(strb, (TFunction) element, spec); + } + if (element instanceof TVariable) { + return appendElementCodePre(strb, (TVariable) element, spec); + } + return appendElementCodePre(strb, element, spec); + } + + /** + * E.g. classes + */ + private StringBuilder appendElementCodePre(StringBuilder strb, + @SuppressWarnings("unused") IdentifiableElement element, SpecIdentifiableElementSection spec) { + + if (hasTodo(spec.getDoclet())) { + strb.append(getTodoLink(spec.getDoclet())); + } + return strb; + } + + private StringBuilder appendElementCodePre(StringBuilder strb, TMember element, + SpecIdentifiableElementSection spec) { + return appendMemberOrVarOrFuncPre(strb, + validatorMessageHelper.shortDescription(element), + element, + spec); + } + + private StringBuilder appendElementCodePre(StringBuilder strb, TMethod element, + SpecIdentifiableElementSection spec) { + return appendMemberOrVarOrFuncPre(strb, + validatorMessageHelper.shortDescription((TMember) element), + element, + spec); + } + + private StringBuilder appendElementCodePre(StringBuilder strb, TFunction element, + SpecIdentifiableElementSection spec) { + return appendMemberOrVarOrFuncPre(strb, + validatorMessageHelper.shortDescription(element), + element, + spec); + } + + private StringBuilder appendElementCodePre(StringBuilder strb, TVariable element, + SpecIdentifiableElementSection spec) { + return appendMemberOrVarOrFuncPre(strb, + validatorMessageHelper.shortDescription(element), + element, + spec); + } + + private StringBuilder appendMemberOrVarOrFuncPre(StringBuilder strb, String shortDescr, + SyntaxRelatedTElement element, SpecIdentifiableElementSection spec) { + + boolean isIntegratedFromPolyfill = !Objects.equals(spec.sourceEntry.trueFolder, spec.sourceEntry.folder); + String trueSrcFolder = spec.sourceEntry.repository + ":" + spec.sourceEntry.trueFolder; + String todoLink = hasTodo(spec.getDoclet()) ? "\n" + getTodoLink(spec.getDoclet()) : ""; + String polyfill = isIntegratedFromPolyfill + ? "\n\n[.small]#(Integrated from static polyfill aware class in: %s)#".formatted(trueSrcFolder) + : ""; + + strb.append(""" + + [[gsec:spec_%s]] + [role=memberdoc] + === %s%s%s + + ==== Signature + + %s + + """.formatted( + spec.sourceEntry.getAdocCompatibleAnchorID(), + html2aDocConverter.pass(toFirstUpper(shortDescr)), + todoLink, + polyfill, + codeLink(element))); + return strb; + } + + private CharSequence codeLink(EObject element) { + if (element instanceof TVariable) { + return codeLink((TVariable) element); + } + if (element instanceof TMethod) { + return codeLink((TMethod) element); + } + if (element instanceof TFunction) { + return codeLink((TFunction) element); + } + if (element instanceof TMember) { + return codeLink((TMember) element); + } + + throw new IllegalArgumentException(); + } + + private CharSequence codeLink(TMember member) { + return doCodeLink(member, fullSignature(member)); + } + + private CharSequence codeLink(TMethod method) { + return doCodeLink(method, fullSignature(method)); + } + + private CharSequence codeLink(TFunction func) { + return doCodeLink(func, fullSignature(func)); + } + + private CharSequence codeLink(TVariable tvar) { + return doCodeLink(tvar, fullSignature(tvar)); + } + + private String fullSignature(TMember member) { + StringBuilder strb = new StringBuilder(); + for (TAnnotation a : filter(member.getAnnotations(), + ann -> !AnnotationDefinition.INTERNAL.name.equals(ann.getName()))) { + + strb.append(a.getAnnotationAsString() + " "); + } + strb.append(keywordProvider.keyword(member.getMemberAccessModifier()) + " "); + if (member.isAbstract()) { + strb.append("@abstract "); + } + strb.append(member.getMemberAsString()); + + return strb.toString(); + } + + private String fullSignature(TMethod method) { + return validatorMessageHelper.fullFunctionSignature(method); + } + + private String fullSignature(TFunction func) { + return validatorMessageHelper.fullFunctionSignature(func); + } + + private String fullSignature(TVariable tvar) { + if (tvar.getTypeRef() == null) { + return tvar.getName(); + } + return tvar.getName() + ": " + tvar.getTypeRef().getTypeRefAsString(); + } + + private CharSequence doCodeLink(IdentifiableElement element, String signature) { + RepoRelativePath rrp = repoPathHolder.get(element); + StringBuilder strb = new StringBuilder(); + + if (rrp != null) { + SourceEntry se = SourceEntryFactory.create(repoPathHolder, rrp, element); + appendSourceLink(strb, se, html2aDocConverter.passThenMonospace(signature)); + } + + return strb.toString(); + } + + private boolean isInSpec(TMember member, Map specsByKey) { + if (member == null) { + return false; + } + return specsByKey.containsKey(KeyUtils.getSpecKey(repoPathHolder, member)); + } + + private String getFormattedID(Entry> entry, List superTypes) { + int index = superTypes.indexOf(entry.getKey().getContainingType()); + return String.format("%04d", index) + entry.getKey().getName(); + } + + private StringBuilder appendSpecElementPost(StringBuilder strb, SpecIdentifiableElementSection specRegion, + Map specsByKey) { + + IdentifiableElement element = specRegion.getIdentifiableElement(); + if (element instanceof TMember) { + return appendElementPost(strb, (TMember) element, specRegion, specsByKey); + } + if (element instanceof TMethod) { + return appendElementPost(strb, (TMethod) element, specRegion, specsByKey); + } + if (element instanceof TFunction) { + return appendElementPost(strb, (TFunction) element, specRegion, specsByKey); + } + if (element instanceof TVariable) { + return appendElementPost(strb, (TVariable) element, specRegion, specsByKey); + } + return appendElementPost(strb, element, specRegion, specsByKey); + } + + private StringBuilder appendElementPost(StringBuilder strb, + IdentifiableElement element, SpecIdentifiableElementSection specRegion, + Map specsByKey) { + + if (element instanceof ContainerType) { + Map> testsForInherited = specRegion.getTestInfosForInheritedMember(); + if (testsForInherited == null || testsForInherited.isEmpty()) { + return strb; + } + + String typeName = element.getName(); + List superTypes = AllSuperTypesCollector.collect((ContainerType) element, + declMergingHelper); + + Iterable>> tests = filter(testsForInherited.entrySet(), + e -> e.getValue() != null && !e.getValue().isEmpty()); + Iterable>> sortedTests = sortBy(tests, + e -> getFormattedID(e, superTypes)); + + for (Entry> tmemberSpecs : sortedTests) { + String shortDescr = validatorMessageHelper.shortDescription(tmemberSpecs.getKey()); + String secSpecLink = typeName + "." + validatorMessageHelper.shortQualifiedName(tmemberSpecs.getKey()); + String secSpecLinkEsc = SourceEntry.getEscapedAdocAnchorString(secSpecLink); + String description = validatorMessageHelper.description(tmemberSpecs.getKey().getContainingType()); + String shortQualName = validatorMessageHelper.shortQualifiedName(tmemberSpecs.getKey()); + String gsecSpec = isInSpec(tmemberSpecs.getKey(), specsByKey) + ? "\n\t<>\n".formatted(html2aDocConverter.pass(shortQualName)) + : ""; + + strb.append(""" + + [[gsec:spec_%s]] + [role=memberdoc] + === %s + + Inherited from + %s%s + + """.formatted( + secSpecLinkEsc, + html2aDocConverter.pass(toFirstUpper(shortDescr)), + html2aDocConverter.pass(description), + gsecSpec)); + + appendConstraints(strb, tmemberSpecs.getKey(), specRegion, tmemberSpecs.getValue(), false); + } + + } + return strb; + + } + + private StringBuilder appendElementPost(StringBuilder strb, TMember element, + SpecIdentifiableElementSection specRegion, + @SuppressWarnings("unused") Map specsByKey) { + + appendConstraints(strb, element, specRegion, specRegion.getTestInfosForMember(), + !hasReqId(specRegion.getDoclet())); + return strb; + } + + private StringBuilder appendElementPost(StringBuilder strb, TMethod element, + SpecIdentifiableElementSection specRegion, + @SuppressWarnings("unused") Map specsByKey) { + + appendConstraints(strb, element, specRegion, specRegion.getTestInfosForMember(), + !hasReqId(specRegion.getDoclet())); + return strb; + } + + private StringBuilder appendElementPost(StringBuilder strb, TFunction element, + SpecIdentifiableElementSection specRegion, + @SuppressWarnings("unused") Map specsByKey) { + + if (!isNullOrEmpty(specRegion.getTestInfosForType())) { + Map> groupdTests = groupBy(specRegion.getTestInfosForType(), + sti -> sti.testTitle); + + strb.append("==== Semantics\n"); + appendApiConstraints(strb, groupdTests); + } else { + + String reqID = getReqId(specRegion.getDoclet()); + if (reqID.isEmpty()) { + String todoLink = getTodoLink( + "Add tests specifying semantics for " + html2aDocConverter.passThenMonospace(element.getName()), + "test function " + html2aDocConverter.pass(element.getName())); + + strb.append(""" + + ==== Semantics + %s + """.formatted(todoLink)); + } else { + strb.append("% tests see " + reqID); + } + } + + return strb; + } + + private StringBuilder appendElementPost(StringBuilder strb, @SuppressWarnings("unused") TVariable element, + SpecIdentifiableElementSection specRegion, + @SuppressWarnings("unused") Map specsByKey) { + + if (!isNullOrEmpty(specRegion.getTestInfosForType())) { + Map> groupdTests = groupBy(specRegion.getTestInfosForType(), + sti -> sti.testTitle); + + strb.append("==== Semantics\n"); + appendApiConstraints(strb, groupdTests); + } + return strb; // test are optional for variables. + } + + private StringBuilder appendConstraints(StringBuilder strb, TMember element, + SpecIdentifiableElementSection specRegion, Set specTestInfos, boolean addTodo) { + + if (!isNullOrEmpty(specTestInfos)) { + Map> groupdTests = groupBy(specTestInfos, sti -> sti.testTitle); + strb.append("==== Semantics\n"); + appendApiConstraints(strb, groupdTests); + + } else if (addTodo) { + + if (elementMayNeedsTest(element, specRegion)) { + String todoLink = getTodoLink( + "Add tests specifying semantics for " + + html2aDocConverter.passThenMonospace(element.getMemberAsString()), + "test " + html2aDocConverter + .pass(element.getContainingType().getName() + "." + element.getName())); + + strb.append(""" + + ==== Semantics + %s + """.formatted(todoLink)); + } + } + return strb; + } + + private boolean elementMayNeedsTest(TMember element, SpecIdentifiableElementSection spec) { + // there are tests, so we show them + if (!isNullOrEmpty(spec.getTestInfosForType())) { + return true; + } + if ((element instanceof TMethod) || (element instanceof FieldAccessor)) { + if (element.getContainingType() instanceof TInterface) { + if (element instanceof TMemberWithAccessModifier) { + return !((TMemberWithAccessModifier) element).isHasNoBody(); + } + } + return !element.isAbstract(); + } + return false; + } + + /** + * List of tests in apiConstraint macros. + */ + private StringBuilder appendApiConstraints(StringBuilder strb, + Map> groupdTests) { + for (Map.Entry> group : sortBy(groupdTests.entrySet(), + e -> e.getKey().toString())) { + strb.append("\n"); + strb.append(". *"); + String key = group.getKey().toString(); + String keyWithoutPrecedingNumber = removePrecedingNumber(key); + strb.append(html2aDocConverter.pass(keyWithoutPrecedingNumber)); + strb.append("* ("); + Iterator iter = group.getValue().iterator(); + while (iter.hasNext()) { + SpecTestInfo testSpec = iter.next(); + strb.append(nfgitTest(testSpec)); + if (iter.hasNext()) { + strb.append(", \n"); + } + } + strb.append(")\n"); + Iterable doclets = map(filter(group.getValue(), spi -> spi.doclet != null), spi -> spi.doclet); + StringBuilder strbTmp = new StringBuilder(); + appendSpecDescriptions(strbTmp, doclets); + if (strbTmp.length() > 0) { + strb.append("+\n"); + strb.append("[.generatedApiConstraint]\n"); + strb.append("====\n\n"); + strb.append(strbTmp); + strb.append("\n====\n"); + } + } + return strb; + } + + private CharSequence nfgitTest(SpecTestInfo testInfo) { + StringBuilder strb = new StringBuilder(); + if (testInfo.rrp == null) { + strb.append(small(testInfo.testModuleSpec() + ".")); + strb.append(testInfo.testMethodTypeName() + "." + testInfo.testMethodName()); + } else { + SourceEntry pc = SourceEntryFactory.create(testInfo); + String strCase = "Test"; + if (!isNullOrEmpty(testInfo.testCase)) { + String formattedCase = removePrecedingNumber(testInfo.testCase); + if (isNullOrEmpty(formattedCase)) { + formattedCase = testInfo.testCase; + } + html2aDocConverter.pass(formattedCase); + } + StringBuilder strbTmp = new StringBuilder(); + appendSourceLink(strbTmp, pc, strCase); + strb.append(small(strbTmp)); + } + return strb.toString(); + } + + /** + * Reminder: Escaping the caption using the method {@link Html2ADocConverter#pass} is recommended. + */ + private StringBuilder appendSourceLink(StringBuilder strb, SourceEntry pc, String caption) { + strb.append("srclnk:++" + pc.toPQN() + "++[" + caption + "]"); + return strb; + } + + /** + * Returns req id, may be an empty string but never null. + */ + private String getReqId(Doclet doclet) { + return TAG_REQID.getValue(doclet, ""); + } + + /** + * Returns true, if spec contains a reference to a requirement id. + */ + private boolean hasReqId(Doclet doclet) { + return !getReqId(doclet).isEmpty(); + } + + /** + * Returns true, if spec contains a reference to a todo. + */ + private boolean hasTodo(Doclet doclet) { + return !getTodo(doclet).isEmpty(); + } + + /** + * Returns todo, may be an empty string but never null. + */ + private String getTodo(Doclet doclet) { + return TAG_TODO.getValue(doclet, ""); + } + + private String getTodoLink(String todoText, String sideText) { + String str = isNullOrEmpty(sideText) ? "" : ", title=\"" + sideText + "\""; + String todo = """ + + [TODO%s] + -- + %s + -- + """.formatted(str, todoText); + return todo; + } + + private String getTodoLink(Doclet doclet) { + return getTodoLink(getTodo(doclet), ""); + } + + private StringBuilder appendTaskLink(StringBuilder strb, String taskID) { + strb.append("task:" + taskID + "[]"); + return strb; + } + + private String small(CharSequence smallString) { + return "[.small]#" + smallString + "#"; + } + + private String removePrecedingNumber(String key) { + for (var i = 0; i < key.length(); i++) { + String stringAt = Character.toString(key.charAt(i)); + if (!"0123456789 ".contains(stringAt)) { + return key.substring(i); + } + } + return ""; + } + +} diff --git a/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocSerializer.xtend b/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocSerializer.xtend deleted file mode 100644 index b5bc565c81..0000000000 --- a/plugins/org.eclipse.n4js.jsdoc2spec/src/org/eclipse/n4js/jsdoc2spec/adoc/ADocSerializer.xtend +++ /dev/null @@ -1,607 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.jsdoc2spec.adoc; - -import com.google.inject.Inject -import java.util.Collection -import java.util.List -import java.util.Map -import java.util.Map.Entry -import java.util.Set -import java.util.SortedSet -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.jsdoc.dom.Composite -import org.eclipse.n4js.jsdoc.dom.ContentNode -import org.eclipse.n4js.jsdoc.dom.Doclet -import org.eclipse.n4js.jsdoc.dom.InlineTag -import org.eclipse.n4js.jsdoc.dom.Literal -import org.eclipse.n4js.jsdoc.dom.SimpleTypeReference -import org.eclipse.n4js.jsdoc.dom.Text -import org.eclipse.n4js.jsdoc.dom.VariableReference -import org.eclipse.n4js.jsdoc.tags.DefaultLineTagDefinition -import org.eclipse.n4js.jsdoc2spec.KeyUtils -import org.eclipse.n4js.jsdoc2spec.RepoRelativePath -import org.eclipse.n4js.jsdoc2spec.SpecTestInfo -import org.eclipse.n4js.ts.types.ContainerType -import org.eclipse.n4js.ts.types.FieldAccessor -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.SyntaxRelatedTElement -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TMemberWithAccessModifier -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TN4Classifier -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.typesystem.utils.AllSuperTypesCollector -import org.eclipse.n4js.utils.DeclMergingHelper -import org.eclipse.n4js.utils.Strings -import org.eclipse.n4js.validation.N4JSElementKeywordProvider -import org.eclipse.n4js.validation.ValidatorMessageHelper - -import static org.eclipse.n4js.jsdoc.N4JSDocletParser.* - -/** - * Print AsciiDoc code of specification JSDoc. Start and end markers are printed by client. - * - * Needs to be injectd. - */ -class ADocSerializer { - @Inject extension Html2ADocConverter; - @Inject ValidatorMessageHelper validatorMessageHelper; - @Inject N4JSElementKeywordProvider keywordProvider; - @Inject RepoRelativePathHolder repoPathHolder; - @Inject DeclMergingHelper declMergingHelper; - - - def String process(SpecRequirementSection spec, Map specsByKey) { - val strb = new StringBuilder(); - strb.appendSpecElementPost(spec, specsByKey); - return Strings.stripAllTrailing(strb.toString()); - } - - def String process(SpecIdentifiableElementSection spec, Map specsByKey) { - val strb = new StringBuilder(); - strb.appendSpecElementPre(spec); - strb.appendSpec(spec); - strb.appendSpecElementPost(spec, specsByKey); - return Strings.stripAllTrailing(strb.toString()); - } - - private def StringBuilder appendSpecElementPost(StringBuilder strb, SpecRequirementSection spec, Map map) { - if (! spec.getTestInfosForType.isNullOrEmpty) { - val Map> groupdTests = spec.getTestInfosForType.groupBy[testTitle]; - strb.appendApiConstraints(groupdTests); - } - return strb - } - - private def StringBuilder appendSpec(StringBuilder strb, SpecIdentifiableElementSection spec) { - strb.append("\n"); - - var addedTaskLinks = false; - val taskTags = spec.getDoclet.lineTags(TAG_TASK.title); - for (tag : taskTags) { - val taskID = TAG_TASK.getValue(tag, ""); - if (!taskID.empty) { - if (taskID.startsWith("*")) { - strb.appendTaskLink(taskID.substring(1)); - } else { - strb.appendTaskLink(taskID); - } - strb.append(" "); - addedTaskLinks = true; - } - } - if(addedTaskLinks) - strb.append("\n\n"); - - strb.appendSpecDescription(spec); - return strb - } - - private def StringBuilder appendSpecDescription(StringBuilder strb, SpecIdentifiableElementSection spec) { - val doclet = spec.getDoclet; - val bSpecFromDescr = doclet.hasLineTag(TAG_SPECFROMDESCR.title); - val reqID = getReqId(doclet); - val specTags = doclet.lineTags(TAG_SPEC.title); - - if (specTags.empty && !bSpecFromDescr && reqID.isEmpty) - return strb; - - if (!(spec.idElement instanceof TN4Classifier || spec.idElement instanceof TEnum)) - strb.append("==== Description\n\n"); - - if (!reqID.isEmpty) { - strb.append("See req:"+reqID +"[].\n"); - } - - strb.appendContents(doclet); - for (tag : specTags) { - strb.appendContents(tag.getValueByKey(DefaultLineTagDefinition.CONTENTS)); - } - - strb.append("\n"); - - return strb; - } - - private def StringBuilder appendSpecDescriptions(StringBuilder strb, Iterable doclets) { - for (doclet : doclets) { - var bSpecFromDescr = doclet.hasLineTag(TAG_SPECFROMDESCR.title); - val specTags = doclet.lineTags(TAG_SPEC.title); - if (! specTags.empty || bSpecFromDescr) { - strb.appendContents(doclet); - for (tag : specTags) { - strb.appendContents(tag.getValueByKey(DefaultLineTagDefinition.CONTENTS)); - } - } - } - return strb; - } - - private def StringBuilder appendContents(StringBuilder strb, Composite composite) { - for (c : composite.contents) { - strb.append(processContent(c)); - } - if (!composite.contents.isEmpty) { - strb.append("\n"); - } - return strb; - } - - - private def dispatch CharSequence processContent(ContentNode node) {} - private def dispatch CharSequence processContent(Text node) { - return transformHTML(node.text); - } - private def dispatch CharSequence processContent(Literal node) { - return transformHTML(node.value); - } - private def dispatch CharSequence processContent(SimpleTypeReference node) { - return passThenMonospace(transformHTML(node.typeName)); - } - private def dispatch CharSequence processContent(VariableReference node) { - return passThenMonospace(transformHTML(node.variableName)); - } - private def dispatch CharSequence processContent(InlineTag node) { - switch (node.title.title) { - case TAG_CODE.title: passThenMonospace(transformHTML(TAG_CODE.getValue(node, ""))) - case TAG_LINK.title: passThenMonospace(transformHTML(TAG_LINK.getValue(node, ""))) - default: { - val StringBuilder strb = new StringBuilder(); - node.values.forEach[strb.appendContents(it)]; - return strb; - } - } - } - - - private def StringBuilder appendSpecElementPre(StringBuilder strb, SpecIdentifiableElementSection spec) { - return strb.appendElementCodePre(spec.identifiableElement, spec); - } - - - /** - * E.g. classes - */ - private def dispatch StringBuilder appendElementCodePre(StringBuilder strb, IdentifiableElement element, SpecIdentifiableElementSection spec) { - if (hasTodo(spec.doclet)) - strb.append(getTodoLink(spec.doclet)); - return strb; - } - private def dispatch StringBuilder appendElementCodePre(StringBuilder strb, TMember element, SpecIdentifiableElementSection spec) { - return strb.appendMemberOrVarOrFuncPre( - validatorMessageHelper.shortDescription(element), - validatorMessageHelper.shortQualifiedName(element), - element.memberAsString, - element, - spec - ); - } - private def dispatch StringBuilder appendElementCodePre(StringBuilder strb, TMethod element, SpecIdentifiableElementSection spec) { - return strb.appendMemberOrVarOrFuncPre( - validatorMessageHelper.shortDescription(element as TMember), - validatorMessageHelper.shortQualifiedName(element as TMember), - element.memberAsString, - element, - spec - ); - } - private def dispatch StringBuilder appendElementCodePre(StringBuilder strb, TFunction element, SpecIdentifiableElementSection spec) { - return strb.appendMemberOrVarOrFuncPre( - validatorMessageHelper.shortDescription(element), - validatorMessageHelper.shortQualifiedName(element), - element.name, - element, - spec - ); - } - private def dispatch StringBuilder appendElementCodePre(StringBuilder strb, TVariable element, SpecIdentifiableElementSection spec) { - return strb.appendMemberOrVarOrFuncPre( - validatorMessageHelper.shortDescription(element), - validatorMessageHelper.shortQualifiedName(element), - element.name, - element, - spec - ); - } - - - private def StringBuilder appendMemberOrVarOrFuncPre(StringBuilder strb, String shortDescr, String shortQN, - String reqName, SyntaxRelatedTElement element, SpecIdentifiableElementSection spec) { - - val boolean isIntegratedFromPolyfill = spec.sourceEntry.trueFolder != spec.sourceEntry.folder; - val String trueSrcFolder = spec.sourceEntry.repository + ":" + spec.sourceEntry.trueFolder; - - strb.append( - ''' - - [[gsec:spec_«spec.sourceEntry.adocCompatibleAnchorID»]] - [role=memberdoc] - === «pass(shortDescr.toFirstUpper)» - «IF hasTodo(spec.doclet)» - «getTodoLink(spec.doclet)» - «ENDIF» - «IF isIntegratedFromPolyfill» - - [.small]#(Integrated from static polyfill aware class in: «trueSrcFolder»)# - «ENDIF» - - ==== Signature - - «codeLink(element)» - - '''); - return strb; - } - - - private def dispatch CharSequence codeLink(TMember member) { - return doCodeLink(member, fullSignature(member)); - } - private def dispatch CharSequence codeLink(TMethod method) { - return doCodeLink(method, fullSignature(method)); - } - private def dispatch CharSequence codeLink(TFunction func) { - return doCodeLink(func, fullSignature(func)); - } - private def dispatch CharSequence codeLink(TVariable tvar) { - return doCodeLink(tvar, fullSignature(tvar)); - } - - - private def fullSignature(TMember member) { - val StringBuilder strb = new StringBuilder(); - for (a: member.annotations.filter[it.name!=AnnotationDefinition.INTERNAL.name]) { - strb.append(a.annotationAsString + " "); - } - strb.append(keywordProvider.keyword(member.memberAccessModifier) + " "); - if (member.abstract) { - strb.append("@abstract "); - } - strb.append(member.memberAsString); - - return strb.toString; - } - private def fullSignature(TMethod method) { - return validatorMessageHelper.fullFunctionSignature(method); - } - private def fullSignature(TFunction func) { - return validatorMessageHelper.fullFunctionSignature(func); - } - private def fullSignature(TVariable tvar) { - if (tvar.typeRef===null) { - return tvar.name; - } - return '''«tvar.name»: «tvar.typeRef.typeRefAsString»'''; - } - - - private def CharSequence doCodeLink(IdentifiableElement element, String signature) { - val RepoRelativePath rrp = repoPathHolder.get(element); - val strb = new StringBuilder(); - - if (rrp !== null) { - val SourceEntry se = SourceEntryFactory.create(repoPathHolder, rrp, element); - strb.appendSourceLink(se, passThenMonospace(signature)); - } - - return strb.toString(); - } - - - private def StringBuilder appendSpecElementPost(StringBuilder strb, SpecIdentifiableElementSection spec, Map map) { - return strb.appendElementPost(spec.identifiableElement, spec, map); - } - - private def dispatch StringBuilder appendElementPost(StringBuilder strb, - IdentifiableElement element, SpecIdentifiableElementSection specRegion, Map specsByKey) { - - if (element instanceof ContainerType) { - var Map> testsForInherited = specRegion.getTestInfosForInheritedMember - if (testsForInherited === null || testsForInherited.empty) { - return strb; - } - - val typeName = element.name; - val superTypes = AllSuperTypesCollector.collect( - element, declMergingHelper - ) - - val tests = testsForInherited.entrySet.filter[value!==null && !value.empty]; - val sortedTests = tests.sortBy[getFormattedID(it, superTypes)]; - for (tmemberSpecs : sortedTests) { - val shortDescr = validatorMessageHelper.shortDescription(tmemberSpecs.key); - val secSpecLink = typeName + "." + validatorMessageHelper.shortQualifiedName(tmemberSpecs.key); - val secSpecLinkEsc = SourceEntry.getEscapedAdocAnchorString(secSpecLink); - val description = validatorMessageHelper.description(tmemberSpecs.key.containingType); - val shortQualName = validatorMessageHelper.shortQualifiedName(tmemberSpecs.key); - - strb.append( - ''' - - [[gsec:spec_«secSpecLinkEsc»]] - [role=memberdoc] - === «pass(shortDescr.toFirstUpper)» - - Inherited from - «pass(description)» - «IF isInSpec(tmemberSpecs.key, specsByKey)» - <> - «ENDIF» - - '''); - - strb.appendConstraints(tmemberSpecs.key, specRegion, tmemberSpecs.value, false); - } - } - return strb - } - - - private def boolean isInSpec(TMember member, Map specsByKey) { - if (member === null) { - return false; - } - return specsByKey.containsKey(KeyUtils.getSpecKey(repoPathHolder, member)); - } - - private def String getFormattedID(Entry> entry, List superTypes) { - val index = superTypes.indexOf(entry.key.containingType); - return String.format("%04d", index) + entry.key.name - } - - - private def dispatch StringBuilder appendElementPost(StringBuilder strb, TMember element, SpecIdentifiableElementSection specRegion, Map specsByKey) { - strb.appendConstraints(element, specRegion, specRegion.getTestInfosForMember, ! hasReqId(specRegion.getDoclet)); - return strb; - } - private def dispatch StringBuilder appendElementPost(StringBuilder strb, TMethod element, SpecIdentifiableElementSection specRegion, Map specsByKey) { - strb.appendConstraints(element, specRegion, specRegion.getTestInfosForMember, ! hasReqId(specRegion.getDoclet)); - return strb; - } - private def dispatch StringBuilder appendElementPost(StringBuilder strb, TFunction element, SpecIdentifiableElementSection specRegion, Map specsByKey) { - - if (! specRegion.getTestInfosForType.isNullOrEmpty) { - val Map> groupdTests = specRegion.getTestInfosForType.groupBy[testTitle]; - strb.append("==== Semantics\n"); - strb.appendApiConstraints(groupdTests); - } else { - - val reqID = getReqId(specRegion.getDoclet); - if (reqID.isEmpty) { - val String todoLink = getTodoLink( - "Add tests specifying semantics for " + passThenMonospace(element.name), - "test function " + pass(element.name)); - - strb.append( - ''' - - ==== Semantics - «todoLink» - '''); - } else { - strb.append('''% tests see «reqID»'''); - } - } - - return strb; - } - private def dispatch StringBuilder appendElementPost(StringBuilder strb, TVariable element, SpecSection specRegion, Map specsByKey) { - if (! specRegion.getTestInfosForType.isNullOrEmpty) { - val Map> groupdTests = specRegion.getTestInfosForType.groupBy[testTitle]; - strb.append("==== Semantics\n"); - strb.appendApiConstraints(groupdTests) - } - return strb; // test are optional for variables. - } - - - private def StringBuilder appendConstraints(StringBuilder strb, TMember element, SpecIdentifiableElementSection specRegion, Set specTestInfos, boolean addTodo) { - if (! specTestInfos.isNullOrEmpty) { - val Map> groupdTests = specTestInfos.groupBy[testTitle]; - strb.append("==== Semantics\n"); - strb.appendApiConstraints(groupdTests); - - } else if (addTodo) { - - if (elementMayNeedsTest(element, specRegion)) { - val String todoLink = getTodoLink( - "Add tests specifying semantics for " + passThenMonospace(element.memberAsString), - "test " + pass(element.containingType.name + "." + element.name)); - - strb.append( - ''' - - ==== Semantics - «todoLink» - '''); - } - } - return strb - } - - private def boolean elementMayNeedsTest(TMember element, SpecIdentifiableElementSection spec) { - // there are tests, so we show them - if (! spec.getTestInfosForType.isNullOrEmpty) { - return true; - } - if ((element instanceof TMethod) || (element instanceof FieldAccessor)) { - if (element.containingType instanceof TInterface) { - if (element instanceof TMemberWithAccessModifier) { - return ! element.hasNoBody - } - } - return !element.isAbstract; - } - return false; - } - - /** - * List of tests in apiConstraint macros. - */ - private def StringBuilder appendApiConstraints(StringBuilder strb, Map> groupdTests) { - for (group : groupdTests.entrySet.sortBy[it.key.toString]) { - strb.append("\n"); - strb.append(". *"); - val key = group.key.toString; - val keyWithoutPrecedingNumber = removePrecedingNumber(key); - strb.append(pass(keyWithoutPrecedingNumber)); - strb.append("* ("); - val iter = group.value.iterator; - while (iter.hasNext) { - val SpecTestInfo testSpec = iter.next; - strb.append(nfgitTest(testSpec)); - if (iter.hasNext) { - strb.append(", \n"); - } - } - strb.append(")\n"); - val Iterable doclets = group.value.filter[doclet !== null].map[doclet]; - val strbTmp = new StringBuilder(); - strbTmp.appendSpecDescriptions(doclets); - if (strbTmp.length > 0) { - strb.append("+\n"); - strb.append("[.generatedApiConstraint]\n"); - strb.append("====\n\n"); - strb.append(strbTmp); - strb.append("\n====\n"); - } - } - return strb - } - - private def CharSequence nfgitTest(SpecTestInfo testInfo) { - val strb = new StringBuilder(); - if (testInfo.rrp === null) { - strb.append(small(testInfo.testModuleSpec() + ".")); - strb.append(testInfo.testMethodTypeName() + "." + testInfo.testMethodName()); - } else { - val pc = SourceEntryFactory.create(testInfo); - val strCase = if (testInfo.testCase.nullOrEmpty) "Test" else { - var formattedCase = removePrecedingNumber(testInfo.testCase); - if (formattedCase.nullOrEmpty) { - formattedCase = testInfo.testCase; - } - pass(formattedCase); - } - val strbTmp = new StringBuilder(); - strbTmp.appendSourceLink(pc, strCase); - strb.append(small(strbTmp)); - } - return strb.toString(); - } - - /** - * Reminder: Escaping the caption using the method {@link Html2ADocConverter#pass} is recommended. - */ - private def StringBuilder appendSourceLink(StringBuilder strb, SourceEntry pc, String caption) { - strb.append( - '''srclnk:++« - pc.toPQN - »++[« - caption - »]'''); - return strb; - } - - /** - * Returns req id, may be an empty string but never null. - */ - private def String getReqId(Doclet doclet) { - return TAG_REQID.getValue(doclet, ""); - } - - /** - * Returns true, if spec contains a reference to a requirement id. - */ - private def boolean hasReqId(Doclet doclet) { - return ! getReqId(doclet).isEmpty; - } - - /** - * Returns true, if spec contains a reference to a todo. - */ - private def boolean hasTodo(Doclet doclet) { - return ! getTodo(doclet).isEmpty; - } - - /** - * Returns todo, may be an empty string but never null. - */ - private def String getTodo(Doclet doclet) { - return TAG_TODO.getValue(doclet, ""); - } - - private def String getTodoLink(String todoText, String sideText) { - val todo = - ''' - - [TODO« - IF !sideText.isNullOrEmpty - », title="«sideText»"« - ENDIF - »] - -- - «todoText» - -- - - ''' - return todo; - } - - private def String getTodoLink(Doclet doclet) { - return getTodoLink(getTodo(doclet), ""); - } - - private def StringBuilder appendTaskLink(StringBuilder strb, String taskID) { - strb.append('''task:«taskID»[]'''); - return strb; - } - - private def String small(CharSequence smallString) { - return '''[.small]#«smallString»#'''; - } - - private def String removePrecedingNumber(String key) { - for (var i=0; i - diff --git a/plugins/org.eclipse.n4js.json.ide/build.properties b/plugins/org.eclipse.n4js.json.ide/build.properties index bef28d3d93..ec65859a4c 100644 --- a/plugins/org.eclipse.n4js.json.ide/build.properties +++ b/plugins/org.eclipse.n4js.json.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/plugins/org.eclipse.n4js.json.ide/pom.xml b/plugins/org.eclipse.n4js.json.ide/pom.xml index dca382ed36..a041e9011a 100644 --- a/plugins/org.eclipse.n4js.json.ide/pom.xml +++ b/plugins/org.eclipse.n4js.json.ide/pom.xml @@ -30,10 +30,6 @@ Contributors: org.apache.maven.plugins maven-clean-plugin - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/plugins/org.eclipse.n4js.json.ide/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.json.ide/xtend-gen/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/plugins/org.eclipse.n4js.json.ide/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/plugins/org.eclipse.n4js.json.model/pom.xml b/plugins/org.eclipse.n4js.json.model/pom.xml index 50cd02cef3..41b5a2c6e1 100644 --- a/plugins/org.eclipse.n4js.json.model/pom.xml +++ b/plugins/org.eclipse.n4js.json.model/pom.xml @@ -38,11 +38,6 @@ Contributors: maven-resources-plugin ${maven-resources-plugin.version} - - org.eclipse.xtend - xtend-maven-plugin - - diff --git a/plugins/org.eclipse.n4js.json/.classpath b/plugins/org.eclipse.n4js.json/.classpath index 4305a21582..7f20dda608 100644 --- a/plugins/org.eclipse.n4js.json/.classpath +++ b/plugins/org.eclipse.n4js.json/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/plugins/org.eclipse.n4js.json/build.properties b/plugins/org.eclipse.n4js.json/build.properties index 4f8080dec2..1ee90b0954 100644 --- a/plugins/org.eclipse.n4js.json/build.properties +++ b/plugins/org.eclipse.n4js.json/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = .,\ META-INF/,\ plugin.xml diff --git a/plugins/org.eclipse.n4js.json/pom.xml b/plugins/org.eclipse.n4js.json/pom.xml index 6f1f1701a9..327b7983eb 100644 --- a/plugins/org.eclipse.n4js.json/pom.xml +++ b/plugins/org.eclipse.n4js.json/pom.xml @@ -78,11 +78,6 @@ Contributors: - - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/formatting2/JSONFormatter.java b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/formatting2/JSONFormatter.java new file mode 100644 index 0000000000..c0994b46ee --- /dev/null +++ b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/formatting2/JSONFormatter.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2017 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.json.formatting2; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.json.JSON.JSONArray; +import org.eclipse.n4js.json.JSON.JSONDocument; +import org.eclipse.n4js.json.JSON.JSONObject; +import org.eclipse.n4js.json.JSON.JSONValue; +import org.eclipse.n4js.json.JSON.NameValuePair; +import org.eclipse.xtext.formatting2.AbstractFormatter2; +import org.eclipse.xtext.formatting2.IFormattableDocument; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.xbase.lib.Pair; + +/** + * A simple formatter for JSON files. + * + * Generally, puts name-value-pairs of objects and array elements on a separate line. + */ +public class JSONFormatter extends AbstractFormatter2 { + + @Override + public void format(Object al, IFormattableDocument document) { + if (al instanceof XtextResource) { + super._format((XtextResource) al, document); + return; + } else if (al instanceof JSONArray) { + format((JSONArray) al, document); + return; + } else if (al instanceof JSONObject) { + format((JSONObject) al, document); + return; + } else if (al instanceof JSONDocument) { + format((JSONDocument) al, document); + return; + } else if (al instanceof NameValuePair) { + format((NameValuePair) al, document); + return; + } else if (al instanceof EObject) { + super._format((EObject) al, document); + return; + } else if (al == null) { + super._format((Void) null, document); + return; + } else { + super._format(al, document); + return; + } + } + + /** */ + public void format(JSONDocument jSONDocument, IFormattableDocument document) { + // make sure all document elements are formatted + document.format(jSONDocument.getContent()); + for (ISemanticRegion sr : textRegionExtensions.allSemanticRegions(jSONDocument)) { + document.append(sr, f -> f.indent()); + } + } + + /** Put both brackets and every element of a JSONArray on a separate line. */ + public void format(JSONArray al, IFormattableDocument document) { + // avoid comma-only lines + configureCommas(al, document); + + Pair bracketPair = textRegionExtensions.regionFor(al).keywordPairs("[", "]") + .get(0); + + // if bracePair can be determined + if (bracketPair != null) { + // indent array elements + document.interior(bracketPair, f -> f.indent()); + + // format empty arrays to be a bracket pair without space (nor newline) in between + if (al.getElements().isEmpty()) { + document.append(bracketPair.getKey(), f -> f.noSpace()); + document.prepend(bracketPair.getValue(), f -> f.noSpace()); + return; + } + + // put closing bracket on a separate line + document.prepend(bracketPair.getValue(), f -> f.newLine()); + } + + // put every array element on a separate line + for (JSONValue val : al.getElements()) { + document.prepend(val, f -> f.newLine()); + } + // recursively format each element + for (JSONValue val : al.getElements()) { + document.format(val); + } + } + + /** On the direct level of an semantic Object enforce commas to ", " with autoWrap option. */ + private void configureCommas(EObject semEObject, IFormattableDocument document) { + for (ISemanticRegion sr : textRegionExtensions.regionFor(semEObject).keywords(",")) { + document.prepend(sr, f -> f.noSpace()); + document.append(sr, f -> { + f.oneSpace(); + f.autowrap(); + }); + } + } + + /** Put both curly braces and every name-value-pair of a JSONObject on a separate line. */ + public void format(JSONObject ol, IFormattableDocument document) { + configureCommas(ol, document); + + Pair bracePair = textRegionExtensions.regionFor(ol).keywordPairs("{", "}") + .get(0); + + // if bracePair can be determined + if (bracePair != null) { + document.interior(bracePair, f -> f.indent()); + + // format empty objects to be a brace pair without space (nor newline) in between + if (ol.getNameValuePairs().isEmpty()) { + document.append(bracePair.getKey(), f -> f.noSpace()); + document.prepend(bracePair.getValue(), f -> f.noSpace()); + return; + } + + document.prepend(bracePair.getValue(), f -> f.newLine()); // format WS in front of closing brace + for (NameValuePair nvp : ol.getNameValuePairs()) { + document.prepend(nvp, f -> f.newLine()); + } + + if (bracePair.getKey() != null + && bracePair.getKey().getNextSemanticRegion() == bracePair.getValue()) { + // empty multiline, trigger formatting: + document.append(bracePair.getKey(), f -> f.newLine()); + } + } + + // recursively format each name-value pair + for (NameValuePair nvp : ol.getNameValuePairs()) { + document.format(nvp); + } + } + + /***/ + public void format(NameValuePair nameValuePair, IFormattableDocument document) { + ISemanticRegion colon = textRegionExtensions.regionFor(nameValuePair).keyword(":"); + JSONValue value = nameValuePair.getValue(); + + document.prepend(colon, f -> f.noSpace()); + document.append(colon, f -> f.oneSpace()); + + document.format(value); + } +} diff --git a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/formatting2/JSONFormatter.xtend b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/formatting2/JSONFormatter.xtend deleted file mode 100644 index f1b089761f..0000000000 --- a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/formatting2/JSONFormatter.xtend +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2017 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.json.formatting2 - -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.json.JSON.JSONArray -import org.eclipse.n4js.json.JSON.JSONDocument -import org.eclipse.n4js.json.JSON.JSONObject -import org.eclipse.n4js.json.JSON.NameValuePair -import org.eclipse.xtext.formatting2.AbstractFormatter2 -import org.eclipse.xtext.formatting2.IFormattableDocument - -/** - * A simple formatter for JSON files. - * - * Generally, puts name-value-pairs of objects and array elements - * on a separate line. - */ -class JSONFormatter extends AbstractFormatter2 { - - /** */ - def dispatch void format(JSONDocument jSONDocument, extension IFormattableDocument document) { - // make sure all document elements are formatted - jSONDocument.getContent.format; - jSONDocument.allSemanticRegions.forEach[it.append[indent]] - } - - /** Put both brackets and every element of a JSONArray on a separate line. */ - def dispatch void format(JSONArray al, extension IFormattableDocument document) { - // avoid comma-only lines - al.configureCommas(document); - - val bracketPair = al.regionFor.keywordPairs("[", "]").head; - - // if bracePair can be determined - if (bracketPair !== null) { - // indent array elements - bracketPair.interior[indent]; - - // format empty arrays to be a bracket pair without space (nor newline) in between - if (al.elements.empty) { - bracketPair.key.append[noSpace] - bracketPair.value.prepend[noSpace] - return; - } - - // put closing bracket on a separate line - bracketPair.value.prepend[newLine]; - } - - // put every array element on a separate line - al.elements.forEach[it, num|prepend[newLine]]; - // recursively format each element - al.elements.forEach[it|format(it)] - - - } - - /** On the direct level of an semantic Object enforce commas to ", " with autoWrap option. */ - private def void configureCommas(EObject semEObject, extension IFormattableDocument document) { - semEObject.regionFor.keywords(",").forEach [ - prepend[noSpace]; - append[oneSpace; autowrap]; - ]; - } - - /** Put both curly braces and every name-value-pair of a JSONObject on a separate line. */ - def dispatch void format(JSONObject ol, extension IFormattableDocument document) { - ol.configureCommas(document); - - val bracePair = ol.regionFor.keywordPairs("{", "}").head; - - // if bracePair can be determined - if (bracePair !== null) { - bracePair?.interior[indent]; - - // format empty objects to be a brace pair without space (nor newline) in between - if (ol.nameValuePairs.empty) { - bracePair.key.append[noSpace] - bracePair.value.prepend[noSpace] - return; - } - - bracePair?.value.prepend[newLine]; // format WS in front of closing brace - ol.nameValuePairs.forEach[it, num|prepend[newLine]]; - - if (bracePair?.key?.nextSemanticRegion == bracePair?.value) { - // empty multiline, trigger formatting: - bracePair.key.append[newLine]; - } - } - - // recursively format each name-value pair - ol.nameValuePairs.forEach[format(it)]; - } - - def dispatch void format(NameValuePair nameValuePair, extension IFormattableDocument document) { - val colon = nameValuePair.regionFor.keyword(":"); - val value = nameValuePair.value; - - colon.prepend[noSpace]; - colon.append[oneSpace]; - - format(value) - } -} diff --git a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionManager.java b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionManager.java new file mode 100644 index 0000000000..19028a488a --- /dev/null +++ b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionManager.java @@ -0,0 +1,34 @@ +package org.eclipse.n4js.json.resource; + +import java.util.Collection; + +import org.eclipse.n4js.json.extension.IJSONResourceDescriptionExtension; +import org.eclipse.n4js.json.extension.JSONExtensionRegistry; +import org.eclipse.xtext.resource.IResourceDescription; +import org.eclipse.xtext.resource.IResourceDescription.Delta; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionManager; + +import com.google.inject.Inject; + +/** + * Resource description manager for JSON files. Delegates to registered JSON extensions. + */ +public class JSONResourceDescriptionManager extends DefaultResourceDescriptionManager { + + @Inject + private JSONExtensionRegistry extensionRegistry; + + /** + * Delegates to registered resource description extensions. + */ + @Override + public boolean isAffected(Collection deltas, IResourceDescription candidate, IResourceDescriptions context) { + for (IJSONResourceDescriptionExtension ext : extensionRegistry.getResourceDescriptionExtensions()) { + if (ext.isAffected(deltas, candidate, context)) { + return true; + } + } + return false; + } +} diff --git a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionManager.xtend b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionManager.xtend deleted file mode 100644 index 8ee0858a32..0000000000 --- a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionManager.xtend +++ /dev/null @@ -1,31 +0,0 @@ -package org.eclipse.n4js.json.resource - -import com.google.inject.Inject -import java.util.Collection -import org.eclipse.n4js.json.^extension.JSONExtensionRegistry -import org.eclipse.xtext.resource.IResourceDescription -import org.eclipse.xtext.resource.IResourceDescription.Delta -import org.eclipse.xtext.resource.IResourceDescriptions -import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionManager - -/** - * Resource description manager for JSON files. Delegates to registered JSON extensions. - */ -class JSONResourceDescriptionManager extends DefaultResourceDescriptionManager { - - @Inject - private JSONExtensionRegistry extensionRegistry; - - /** - * Delegates to registered resource description extensions. - */ - override boolean isAffected(Collection deltas, IResourceDescription candidate, - IResourceDescriptions context) { - for (ext : extensionRegistry.resourceDescriptionExtensions) { - if (ext.isAffected(deltas, candidate, context)) { - return true; - } - } - return false; - } -} diff --git a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionStrategy.java b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionStrategy.java new file mode 100644 index 0000000000..be70273c80 --- /dev/null +++ b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionStrategy.java @@ -0,0 +1,33 @@ +package org.eclipse.n4js.json.resource; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.json.JSON.JSONDocument; +import org.eclipse.n4js.json.extension.IJSONResourceDescriptionExtension; +import org.eclipse.n4js.json.extension.JSONExtensionRegistry; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; +import org.eclipse.xtext.util.IAcceptor; + +import com.google.inject.Inject; + +/** + * JSON resource description strategy based on {@link IJSONResourceDescriptionExtension}. + * + * Does nothing per default, except for the case in which an extension provides a custom resource description. + */ +public class JSONResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { + + @Inject + private JSONExtensionRegistry extensionRegistry; + + /** Delegates to registered resource description extensions. */ + @Override + public boolean createEObjectDescriptions(EObject eObject, IAcceptor acceptor) { + if (eObject instanceof JSONDocument) { + for (IJSONResourceDescriptionExtension ext : extensionRegistry.getResourceDescriptionExtensions()) { + ext.createJSONDocumentDescriptions((JSONDocument) eObject, acceptor); + } + } + return false; + } +} diff --git a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionStrategy.xtend b/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionStrategy.xtend deleted file mode 100644 index d30ef6a95b..0000000000 --- a/plugins/org.eclipse.n4js.json/src/org/eclipse/n4js/json/resource/JSONResourceDescriptionStrategy.xtend +++ /dev/null @@ -1,31 +0,0 @@ -package org.eclipse.n4js.json.resource - -import com.google.inject.Inject -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.json.JSON.JSONDocument -import org.eclipse.n4js.json.^extension.IJSONResourceDescriptionExtension -import org.eclipse.n4js.json.^extension.JSONExtensionRegistry -import org.eclipse.xtext.resource.IEObjectDescription -import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy -import org.eclipse.xtext.util.IAcceptor - -/** - * JSON resource description strategy based on {@link IJSONResourceDescriptionExtension}. - * - * Does nothing per default, except for the case in which an extension provides a custom resource description. - */ -public class JSONResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { - - @Inject - private JSONExtensionRegistry extensionRegistry; - - /** Delegates to registered resource description extensions. */ - override createEObjectDescriptions(EObject eObject, IAcceptor acceptor) { - if (eObject instanceof JSONDocument) { - for (ext : extensionRegistry.resourceDescriptionExtensions) { - ext.createJSONDocumentDescriptions(eObject, acceptor); - } - } - return false; - } -} diff --git a/plugins/org.eclipse.n4js.json/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.json/xtend-gen/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/plugins/org.eclipse.n4js.json/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/plugins/org.eclipse.n4js.model/.classpath b/plugins/org.eclipse.n4js.model/.classpath index 8b5d45b17c..af27bdbd59 100644 --- a/plugins/org.eclipse.n4js.model/.classpath +++ b/plugins/org.eclipse.n4js.model/.classpath @@ -1,6 +1,5 @@ - diff --git a/plugins/org.eclipse.n4js.model/build.properties b/plugins/org.eclipse.n4js.model/build.properties index 974cddd650..4bd779cde3 100644 --- a/plugins/org.eclipse.n4js.model/build.properties +++ b/plugins/org.eclipse.n4js.model/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - emf-gen/,\ - xtend-gen + emf-gen/ bin.includes = META-INF/,\ .,\ plugin.xml,\ diff --git a/plugins/org.eclipse.n4js.model/pom.xml b/plugins/org.eclipse.n4js.model/pom.xml index 936d0056b1..6d70b4cdff 100644 --- a/plugins/org.eclipse.n4js.model/pom.xml +++ b/plugins/org.eclipse.n4js.model/pom.xml @@ -38,11 +38,6 @@ Contributors: maven-resources-plugin ${maven-resources-plugin.version} - - org.eclipse.xtend - xtend-maven-plugin - - diff --git a/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructNode.java b/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructNode.java new file mode 100644 index 0000000000..4592294f0d --- /dev/null +++ b/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructNode.java @@ -0,0 +1,641 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.n4JS; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.emf.common.util.BasicEList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.xtend.lib.annotations.Data; +import org.eclipse.xtext.xbase.lib.Pair; +import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; + +/** + * Destructuring patterns can appear in very different forms within the AST and in different contexts. This helper class + * is used to transform those heterogeneous representations into a single, uniform structure, that can be traversed more + * easily. + *

+ * All fields are optional, i.e. may be 'null'. At most one of 'varRef', 'varDecl' and 'nestedPattern' may be non-null; + * if all three are 'null' the node is a padding node. + * + *

Overview of Destructuring Patterns in the AST

Different forms: + *
    + *
  1. as a {@link BindingPattern} (may contain nested {@code BindingPattern}s). + *
  2. as an {@link ArrayLiteral} (may contain nested patterns in form of {@code ArrayLiteral}s or + * {@code ObjectLiteral}s). + *
  3. as an {@link ObjectLiteral} (may contain nested patterns in form of {@code ArrayLiteral}s or + * {@code ObjectLiteral}s). + *
+ * Different contexts: + *
    + *
  1. within a {@link VariableStatement} (then contained in a {@link VariableBinding}, which is an alternative to a + * {@link VariableDeclaration}). + *
  2. within an {@link AssignmentExpression} (then it appears as the left-hand side expression) + *
  3. within a {@link ForStatement} (only in for..in and for..of, because if used in plain for it is a use case of a + * variable statement or assignment expression inside the for statement). + *
  4. NOT SUPPORTED YET: in the context of lists of formal parameters or function argument lists + *
+ * The above 6 use cases have several special characteristics and constraints, most of which are unified in this class. + * It might be possible to generate more unified patterns in the parser, but the above situation is more in line with + * terminology in the ES6 specification. + *

+ */ +@Data +@SuppressWarnings("javadoc") +public class DestructNode { + final public EObject astElement; + // property name (iff in object destructuring pattern) or 'null' (iff in array destructuring pattern) + final public String propName; + final public IdentifierRef varRef; + final public VariableDeclaration varDecl; + // nested pattern that will be bound/assigned (or 'null' iff 'varName' is non-null) + final public DestructNode[] nestedNodes; + final public Expression defaultExpr; + // can be an Expression or an IdentifiableElement (in case of Getter/Setter/Method) + final public TypableElement assignedElem; + final public boolean rest; + + public DestructNode(EObject astElement, String propName, IdentifierRef varRef, VariableDeclaration varDecl, + DestructNode[] nestedNodes, Expression defaultExpr, TypableElement assignedElem, boolean rest) { + + this.astElement = astElement; + this.propName = propName; + this.varRef = varRef; + this.varDecl = varDecl; + this.nestedNodes = nestedNodes; + this.defaultExpr = defaultExpr; + this.assignedElem = assignedElem; + this.rest = rest; + } + + /** + * Returns true if if receiving node belongs to a positional destructuring pattern (i.e. an array destructuring + * pattern). + */ + public boolean isPositional() { + return propName == null; + } + + /** + * Returns true if if the given nodes belong to a positional destructuring pattern (i.e. an array destructuring + * pattern). + */ + static public boolean arePositional(List nodes) { + return nodes != null && exists(nodes, n -> n.isPositional()); + } + + /** + * Returns true if if this is a padding node. + */ + public boolean isPadding() { + return varRef == null && varDecl == null && nestedNodes == null; + } + + /** + * If this node has a reference to a variable or a variable declaration, returns the variable's name, + * null otherwise. + */ + public String varName() { + if (varRef != null) { + return varRef.getId() == null ? null : varRef.getId().getName(); + } else if (varDecl != null) { + return varDecl.getName(); + } + return null; + } + + /** + * Returns the variable declaration contained in this node's astElement or null. + */ + public VariableDeclaration getVariableDeclaration() { + if (astElement instanceof BindingElement) { + return ((BindingElement) astElement).getVarDecl(); + } + if (astElement instanceof BindingProperty && ((BindingProperty) astElement).getValue() != null) { + return ((BindingProperty) astElement).getValue().getVarDecl(); + } + return null; + } + + /** + * Returns the AST node and EStructuralFeature to be used when showing an error message on the receiving node's + * propName attribute. Intended for issue generation in validations. + */ + public Pair getEObjectAndFeatureForPropName() { + if (propName != null) { + if (astElement instanceof PropertyNameValuePairSingleName) { + PropertyNameValuePairSingleName pnvpsn = (PropertyNameValuePairSingleName) astElement; + if (pnvpsn.getExpression() instanceof AssignmentExpression) { + return Pair.of(pnvpsn.getExpression(), N4JSPackage.eINSTANCE.getAssignmentExpression_Lhs()); + } + } + if (astElement instanceof PropertyNameValuePairSingleName) { + return Pair.of(astElement, N4JSPackage.eINSTANCE.getPropertyNameValuePair_Expression()); + } + if (astElement instanceof BindingProperty) { + BindingProperty bp = (BindingProperty) astElement; + + if (bp.getDeclaredName() != null) { + return Pair.of(bp, N4JSPackage.eINSTANCE.getPropertyNameOwner_DeclaredName()); + } + if (bp.getProperty() != null) { + return Pair.of(bp, N4JSPackage.eINSTANCE.getBindingProperty_Property()); + } + if (bp.getValue() != null && bp.getValue().getVarDecl() != null + && bp.getValue().getVarDecl().getName() != null) { + return Pair.of(bp.getValue().getVarDecl(), N4JSPackage.eINSTANCE.getAbstractVariable_Name()); + } + } + if (astElement instanceof PropertyNameValuePair + && ((PropertyNameValuePair) astElement).getProperty() != null) { + + return Pair.of(astElement, N4JSPackage.eINSTANCE.getPropertyNameValuePair_Property()); + } + if (astElement instanceof PropertyNameOwner) { + return Pair.of(astElement, N4JSPackage.eINSTANCE.getPropertyNameOwner_DeclaredName()); + } + } + return Pair.of(astElement, null);// show error on entire node + } + + /** + * Returns the node with the given astElement. + */ + public DestructNode findNodeForElement(EObject pAstElement) { + return findNodeOrParentForElement(pAstElement, false); + } + + /** + * Returns the node with the given astElement or its parent node. + */ + public DestructNode findNodeOrParentForElement(EObject pAstElement, boolean returnParent) { + EObject reprAstElem = pAstElement; + if (pAstElement instanceof BindingElement && pAstElement.eContainer() instanceof BindingProperty) { + reprAstElem = pAstElement.eContainer(); + } + + if (this.astElement == reprAstElem) { + if (returnParent) { + return null; + } + return this; + } + if (this.nestedNodes == null) { + return null; + } + for (DestructNode nested : this.nestedNodes) { + if (nested.astElement == reprAstElem) { + if (returnParent) { + return this; + } + return nested; + } + DestructNode resNested = nested.findNodeOrParentForElement(reprAstElem, returnParent); + if (resNested != null) { + return resNested; + } + } + return null; + } + + /** + * Returns stream of this node and all its descendants, i.e. directly and indirectly nested nodes. + */ + public Stream stream() { + if (nestedNodes == null) { + return Stream.of(this); + } else { + return Stream.concat(Stream.of(this), Stream.of(nestedNodes).flatMap(dn -> dn.stream())); + } + } + + public static DestructNode unify(EObject eobj) { + if (eobj instanceof VariableBinding) { + return unify((VariableBinding) eobj); + } + if (eobj instanceof AssignmentExpression) { + return unify((AssignmentExpression) eobj); + } + if (eobj instanceof ForStatement) { + return unify((ForStatement) eobj); + } + return null; + } + + /** + * Returns a unified copy of the given destructuring pattern or null if it is invalid. This is helpful + * because these patterns can appear in very different forms and locations within the AST. + */ + public static DestructNode unify(EObject astElem, EObject lhs, Expression rhs) { + return new DestructNode( + astElem, // astElement + null, // propName + null, // varRef + null, // varDecl + toEntries(lhs, rhs), // nestedNodes + rhs, // defaultExpr + rhs, // assignedExpr + false // rest + ); + } + + /** + * Returns a unified copy of the given destructuring pattern or null if it is invalid. This is helpful + * because these patterns can appear in very different forms and locations within the AST. + */ + public static DestructNode unify(VariableBinding binding) { + if (binding != null && binding.getPattern() != null + // note: binding.expression is mandatory in variable statements + // but optional in for..in/of statements + && (binding.getExpression() != null || binding.eContainer() instanceof ForStatement)) { + + return unify(binding, binding.getPattern(), binding.getExpression()); + } + return null; + } + + /** + * Returns a unified copy of the given destructuring pattern or null if it is invalid. This is helpful + * because these patterns can appear in very different forms and locations within the AST. + */ + public static DestructNode unify(AssignmentExpression assignExpr) { + if (assignExpr != null && assignExpr.getLhs() != null && assignExpr.getRhs() != null + && DestructureUtils.isTopOfDestructuringAssignment(assignExpr)) { + + return unify(assignExpr, assignExpr.getLhs(), assignExpr.getRhs()); + } + return null; + } + + /** + * Returns a unified copy of the given destructuring pattern or null if it is invalid. This is helpful + * because these patterns can appear in very different forms and locations within the AST. + */ + public static DestructNode unify(ForStatement forStmnt) { + if (forStmnt != null && DestructureUtils.isTopOfDestructuringForStatement(forStmnt)) { + Expression valueToBeDestructured; + Expression defaultExpression; + + if (forStmnt.isForOf()) { + valueToBeDestructured = firstArrayElement(forStmnt.getExpression()); + defaultExpression = forStmnt.getExpression(); + } else if (forStmnt.isForIn()) { + StringLiteral slit1 = N4JSFactory.eINSTANCE.createStringLiteral(); + slit1.setValue(""); + valueToBeDestructured = slit1; + + StringLiteral slit2 = N4JSFactory.eINSTANCE.createStringLiteral(); + slit2.setValue(""); + defaultExpression = slit2; + } else { + // impossible because #isTopOfDestructuringForStatement() returned true + throw new IllegalStateException(); + } + + if (DestructureUtils.containsDestructuringPattern(forStmnt)) { + // case: for(var [a,b] of arr) {} + VariableBinding binding = head(filter(forStmnt.getVarDeclsOrBindings(), VariableBinding.class)); + Expression rhs = firstArrayElement(forStmnt.getExpression()); + return new DestructNode( + forStmnt, // astElement + null, // propName + null, // varRef + null, // varDecl + toEntries(binding.getPattern(), rhs), // nestedNodes + defaultExpression, // defaultExpr + valueToBeDestructured, // assignedExpr + false // rest + ); + } else if (DestructureUtils.isObjectOrArrayLiteral(forStmnt.getInitExpr())) { + // case: for([a,b] of arr) {} + return new DestructNode( + forStmnt, // astElement + null, // propName + null, // varRef + null, // varDecl + toEntries(forStmnt.getInitExpr(), null), // nestedNodes + defaultExpression, // defaultExpr + defaultExpression, // assignedExpr + false // rest + ); + } + } + return null; + } + + private static Expression firstArrayElement(Expression expr) { + return (expr instanceof ArrayLiteral) ? ((ArrayLiteral) expr).getElements().get(0).getExpression() : expr; + } + + private static DestructNode[] toEntries(EObject pattern, TypableElement rhs) { + Iterator patElemIter = null; + if (pattern instanceof ArrayLiteral) { + patElemIter = ((ArrayLiteral) pattern).getElements().iterator(); + } else if (pattern instanceof ObjectLiteral) { + patElemIter = ((ObjectLiteral) pattern).getPropertyAssignments().iterator(); + } else if (pattern instanceof ArrayBindingPattern) { + patElemIter = ((ArrayBindingPattern) pattern).getElements().iterator(); + } else if (pattern instanceof ObjectBindingPattern) { + patElemIter = ((ObjectBindingPattern) pattern).getProperties().iterator(); + } else { + return null; + } + + Iterator rhsElemIter = null; + if (rhs instanceof ArrayLiteral) { + rhsElemIter = ((ArrayLiteral) rhs).getElements().iterator(); + } + if (rhs instanceof ObjectLiteral) { + rhsElemIter = ((ObjectLiteral) rhs).getPropertyAssignments().iterator(); + } + + BasicEList nestedDNs = new BasicEList<>(); + while (patElemIter.hasNext()) { + EObject patElem = patElemIter.next(); + TypableElement litElem = (rhsElemIter == null) ? rhs : (rhsElemIter.hasNext()) ? rhsElemIter.next() : null; + + DestructNode nestedNode = null; + if (patElem instanceof ArrayElement) { + nestedNode = toEntry((ArrayElement) patElem, litElem); + } + if (patElem instanceof PropertyNameValuePair) { + nestedNode = toEntry((PropertyNameValuePair) patElem, litElem); + } + if (patElem instanceof BindingElement) { + nestedNode = toEntry((BindingElement) patElem, litElem); + } + if (patElem instanceof BindingProperty) { + nestedNode = toEntry((BindingProperty) patElem, litElem); + } + + if (nestedNode != null) { + nestedDNs.add(nestedNode); + } + } + return nestedDNs.toArray(new DestructNode[0]); + } + + private static DestructNode toEntry(ArrayElement elem, TypableElement rhs) { + TypableElement rhsExpr = (rhs instanceof ArrayElement) ? ((ArrayElement) rhs).getExpression() : rhs; + Expression expr = elem.getExpression(); // note: ArrayPadding will return null for getExpression() + if (expr instanceof AssignmentExpression) { + AssignmentExpression ae = (AssignmentExpression) expr; + return toEntry(elem, null, ae.getLhs(), ae.getRhs(), elem.isSpread(), rhsExpr); + } else { + return toEntry(elem, null, expr, null, elem.isSpread(), rhsExpr); + } + } + + private static DestructNode toEntry(PropertyNameValuePair pa, TypableElement rhs) { + TypableElement rhsExpr = (rhs instanceof PropertyNameValuePair) + ? ((PropertyNameValuePair) rhs).getExpression() + : rhs; + Expression expr = pa.getExpression(); + if (expr instanceof AssignmentExpression) { + AssignmentExpression ae = (AssignmentExpression) expr; + return toEntry(pa, pa.getName(), ae.getLhs(), ae.getRhs(), false, rhsExpr); + } else { + return toEntry(pa, pa.getName(), expr, null, false, rhsExpr); + } + } + + private static DestructNode toEntry(BindingElement elem, TypableElement rhs) { + TypableElement expr = (rhs instanceof ArrayElement) ? ((ArrayElement) rhs).getExpression() : rhs; + + if (elem.getVarDecl() != null) { + return toEntry(elem, null, elem.getVarDecl(), elem.getVarDecl().getExpression(), elem.isRest(), expr); + } else if (elem.getNestedPattern() != null) { + return toEntry(elem, null, elem.getNestedPattern(), elem.getExpression(), elem.isRest(), expr); + } else { + return toEntry(elem, null, null, null, false, expr); // return dummy entry to not break indices + } + } + + private static DestructNode toEntry(BindingProperty prop, TypableElement rhs) { + if (prop.getValue() != null && prop.getValue().getVarDecl() != null) { + TypableElement expr = getPropertyAssignmentExpression(rhs); + return toEntry(prop, prop.getName(), prop.getValue().getVarDecl(), + prop.getValue().getVarDecl().getExpression(), false, expr); + + } else if (prop.getValue() != null && prop.getValue().getNestedPattern() != null) { + TypableElement expr = getPropertyAssignmentExpression(rhs); + return toEntry(prop, prop.getName(), prop.getValue().getNestedPattern(), prop.getValue().getExpression(), + false, expr); + + } else { + return toEntry(prop, null, null, null, false, rhs); + } + } + + /** + * @param bindingTarget + * an IdentifierRef/VariableDeclaration or a nested pattern (which may be a BindingPattern, ArrayLiteral, + * or ObjectLiteral) + */ + private static DestructNode toEntry(EObject astElement, String propName, EObject bindingTarget, + Expression defaultExpr, boolean rest, TypableElement rhs) { + + if (bindingTarget == null) { + // no target -> create a padding node + return new DestructNode(astElement, propName, null, null, null, defaultExpr, null, rest); + + } else if (bindingTarget instanceof IdentifierRef) { + return new DestructNode(astElement, propName, (IdentifierRef) bindingTarget, null, null, defaultExpr, rhs, + rest); + + } else if (bindingTarget instanceof VariableDeclaration) { + return new DestructNode(astElement, propName, null, (VariableDeclaration) bindingTarget, null, defaultExpr, + rhs, rest); + + } else if (bindingTarget instanceof ArrayLiteral || bindingTarget instanceof ObjectLiteral || + bindingTarget instanceof BindingPattern) { + return new DestructNode(astElement, propName, null, null, toEntries(bindingTarget, rhs), defaultExpr, rhs, + rest); + + } else { + // invalid binding target (probably a corrupt AST) -> create a padding node + return new DestructNode(astElement, propName, null, null, null, defaultExpr, null, rest); + } + } + + /** @return the expression or function of the given PropertyAssignment */ + private static TypableElement getPropertyAssignmentExpression(TypableElement rhs) { + if (rhs instanceof PropertyGetterDeclaration) { + return ((PropertyGetterDeclaration) rhs).getDefinedFunctionOrAccessor(); + } + if (rhs instanceof PropertySetterDeclaration) { + return ((PropertySetterDeclaration) rhs).getDefinedFunctionOrAccessor(); + } + if (rhs instanceof PropertyMethodDeclaration) { + return ((PropertyMethodDeclaration) rhs).getDefinedFunctionOrAccessor(); + } + if (rhs instanceof PropertyNameValuePair) { + return ((PropertyNameValuePair) rhs).getExpression(); + } + if (rhs instanceof PropertyAssignmentAnnotationList) { + return null; + } + + return rhs; + } + + /** @return all {@link IdentifierRef} of variables that are written in the given assignment */ + public List getAllDeclaredIdRefs() { + List idRefs = new LinkedList<>(); + Iterator allNestedNodes = this.stream().iterator(); + + while (allNestedNodes.hasNext()) { + EObject eobj = allNestedNodes.next().astElement; + if (eobj instanceof ArrayElement) { + Expression expr = ((ArrayElement) eobj).getExpression(); + if (expr instanceof AssignmentExpression) { + idRefs.add((((AssignmentExpression) expr).getLhs())); + } else { + idRefs.add(expr); + } + + } else if (eobj instanceof PropertyNameValuePairSingleName) { + idRefs.add(((PropertyNameValuePairSingleName) eobj).getIdentifierRef()); + + } else if (eobj instanceof PropertyNameValuePair) { + Expression expr = ((PropertyNameValuePair) eobj).getExpression(); + if (expr instanceof AssignmentExpression) { + idRefs.add(((AssignmentExpression) expr).getLhs()); + } else { + idRefs.add(expr); + } + } + } + return idRefs; + } + + /** + * @return a pair where its key is the assigned EObject and its value is the default EObject to the given lhs AST + * element + */ + public static Pair getValueFromDestructuring(EObject nodeElem) { + EObject node = nodeElem; + EObject topNode = null; + EObject dNodeElem = null; + boolean breakSearch = false; + + while (!breakSearch) { + EObject parent = node.eContainer(); + dNodeElem = getDNodeElem(dNodeElem, parent, node); + topNode = getTopElem(topNode, parent); + breakSearch = parent instanceof Statement; + node = parent; + } + + DestructNode dNode = null; + if (topNode instanceof AssignmentExpression) { + dNode = DestructNode.unify(topNode); + } else if (topNode instanceof VariableBinding) { + dNode = DestructNode.unify(topNode); + } else if (topNode instanceof ForStatement) { + dNode = DestructNode.unify(topNode); + } + + if (dNode != null) { + dNode = dNode.findNodeForElement(dNodeElem); + if (dNode != null) { + EObject assgnValue = dNode.assignedElem; + EObject defaultValue = dNode.defaultExpr; + return Pair.of(assgnValue, defaultValue); + } + } + + return null; + } + + private static EObject getDNodeElem(EObject dNodeElem, EObject parent, EObject node) { + if (dNodeElem != null) { + return dNodeElem; + } + if (node instanceof BindingElement && parent instanceof BindingProperty) { + return parent; + } + if (node instanceof BindingElement || node instanceof ArrayElement || node instanceof PropertyAssignment) { + return node; + } + return null; + } + + private static EObject getTopElem(EObject oldTopNode, EObject parent) { + EObject newTopNode = null; + if (parent instanceof ForStatement) { + newTopNode = parent; + } + if (parent instanceof AssignmentExpression) { + newTopNode = parent; + } + if (parent instanceof VariableBinding) { + newTopNode = parent; + } + + if (newTopNode != null) { + return newTopNode; + } else { + return oldTopNode; + } + } + + public static List getAllDeclaredIdRefs(EObject eobj) { + DestructNode dnode = null; + if (eobj instanceof ForStatement) { + dnode = unify(eobj); + } + if (eobj instanceof VariableBinding) { + dnode = unify(eobj); + } + if (eobj instanceof AssignmentExpression) { + dnode = unify(eobj); + } + + if (dnode == null) { + return Collections.emptyList(); + } + return dnode.getAllDeclaredIdRefs(); + } + + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("astElement", this.astElement); + b.add("propName", this.propName); + b.add("varRef", this.varRef); + b.add("varDecl", this.varDecl); + b.add("nestedNodes", this.nestedNodes); + b.add("defaultExpr", this.defaultExpr); + b.add("assignedElem", this.assignedElem); + b.add("rest", this.rest); + return b.toString(); + } + + public List getNestedNodes() { + if (nestedNodes == null) { + return Collections.emptyList(); + } + return Arrays.asList(this.nestedNodes); + } + +} diff --git a/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructNode.xtend b/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructNode.xtend deleted file mode 100644 index 648f3500a9..0000000000 --- a/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructNode.xtend +++ /dev/null @@ -1,547 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.n4JS - -import java.util.Iterator -import java.util.LinkedList -import java.util.List -import java.util.stream.Stream -import org.eclipse.emf.common.util.BasicEList -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EStructuralFeature -import org.eclipse.n4js.ts.types.TypableElement -import org.eclipse.xtend.lib.annotations.Data - -/** - * Destructuring patterns can appear in very different forms within the AST and in different contexts. - * This helper class is used to transform those heterogeneous representations into a single, uniform - * structure, that can be traversed more easily. - *

- * All fields are optional, i.e. may be 'null'. At most one of 'varRef', 'varDecl' and 'nestedPattern' - * may be non-null; if all three are 'null' the node is a padding node. - * - *

Overview of Destructuring Patterns in the AST

- * Different forms: - *
    - *
  1. as a {@link BindingPattern} (may contain nested {@code BindingPattern}s). - *
  2. as an {@link ArrayLiteral} (may contain nested patterns in form of {@code ArrayLiteral}s or {@code ObjectLiteral}s). - *
  3. as an {@link ObjectLiteral} (may contain nested patterns in form of {@code ArrayLiteral}s or {@code ObjectLiteral}s). - *
- * Different contexts: - *
    - *
  1. within a {@link VariableStatement} (then contained in a {@link VariableBinding}, which is an alternative - * to a {@link VariableDeclaration}). - *
  2. within an {@link AssignmentExpression} (then it appears as the left-hand side expression) - *
  3. within a {@link ForStatement} (only in for..in and for..of, because if used in plain for it is a use case - * of a variable statement or assignment expression inside the for statement). - *
  4. NOT SUPPORTED YET: in the context of lists of formal parameters or function argument lists - *
- * The above 6 use cases have several special characteristics and constraints, most of which are unified in this class. - * It might be possible to generate more unified patterns in the parser, but the above situation is more in line with - * terminology in the ES6 specification. - *

- */ -@Data -public class DestructNode { - EObject astElement; - String propName; // property name (iff in object destructuring pattern) or 'null' (iff in array destructuring pattern) - IdentifierRef varRef; - VariableDeclaration varDecl; - DestructNode[] nestedNodes; // nested pattern that will be bound/assigned (or 'null' iff 'varName' is non-null) - Expression defaultExpr; - TypableElement assignedElem; // can be an Expression or an IdentifiableElement (in case of Getter/Setter/Method) - boolean rest; - - /** - * Tells if receiving node belongs to a positional destructuring pattern - * (i.e. an array destructuring pattern). - */ - def boolean isPositional() { - propName === null - } - - /** - * Tells if the given nodes belong to a positional destructuring pattern - * (i.e. an array destructuring pattern). - */ - static def boolean arePositional(DestructNode[] nodes) { - nodes !== null && nodes.exists[positional] - } - - /** - * Tells if this is a padding node. - */ - def boolean isPadding() { - varRef === null && varDecl === null && nestedNodes === null - } - - /** - * If this node has a reference to a variable or a variable declaration, - * returns the variable's name, null otherwise. - */ - def String varName() { - if (varRef !== null) - varRef.id?.name - else if (varDecl !== null) - varDecl.name - } - - /** - * Returns the variable declaration contained in this node's astElement or null. - */ - def VariableDeclaration getVariableDeclaration() { - switch (astElement) { - BindingElement: - astElement.varDecl - BindingProperty case astElement.value !== null: - astElement.value.varDecl - } - } - - /** - * Returns the AST node and EStructuralFeature to be used when showing an error message - * on the receiving node's propName attribute. Intended for issue generation in validations. - */ - def Pair getEObjectAndFeatureForPropName() { - if (propName !== null) { - switch (astElement) { - PropertyNameValuePairSingleName case astElement.expression instanceof AssignmentExpression: - astElement.expression -> N4JSPackage.eINSTANCE.assignmentExpression_Lhs - PropertyNameValuePairSingleName: - astElement -> N4JSPackage.eINSTANCE.propertyNameValuePair_Expression - BindingProperty case astElement.declaredName !== null: - astElement -> N4JSPackage.eINSTANCE.propertyNameOwner_DeclaredName - BindingProperty case astElement.property !== null: - astElement -> N4JSPackage.eINSTANCE.bindingProperty_Property - BindingProperty case astElement.value?.varDecl?.name !== null: - astElement.value.varDecl -> N4JSPackage.eINSTANCE.abstractVariable_Name - PropertyNameValuePair case astElement.property !== null: - astElement -> N4JSPackage.eINSTANCE.propertyNameValuePair_Property - PropertyNameOwner: - astElement -> N4JSPackage.eINSTANCE.propertyNameOwner_DeclaredName - default: - astElement -> null // show error on entire node - } - } else { - astElement -> null // show error on entire node - } - } - - /** - * Returns the node with the given astElement. - */ - def DestructNode findNodeForElement(EObject astElement) { - return findNodeOrParentForElement(astElement, false); - } - - /** - * Returns the node with the given astElement or its parent node. - */ - def DestructNode findNodeOrParentForElement(EObject astElement, boolean returnParent) { - val reprAstElem = if (astElement instanceof BindingElement && astElement.eContainer instanceof BindingProperty) - astElement.eContainer else astElement; - - if (this.astElement === reprAstElem) { - if (returnParent) { - return null; - } - return this; - } - if (this.nestedNodes === null) { - return null; - } - for (nested : this.nestedNodes) { - if (nested.astElement === reprAstElem) { - if (returnParent) { - return this; - } - return nested; - } - val resNested = nested.findNodeOrParentForElement(reprAstElem, returnParent); - if (resNested !== null) { - return resNested; - } - } - return null; - } - - /** - * Returns stream of this node and all its descendants, i.e. directly and indirectly nested nodes. - */ - def Stream stream() { - if (nestedNodes === null || nestedNodes.empty) { - Stream.of(this) - } else { - Stream.concat(Stream.of(this), Stream.of(nestedNodes).flatMap[stream]); - } - } - - public static def DestructNode unify(EObject eobj) { - return switch (eobj) { - VariableBinding: unify(eobj) - AssignmentExpression: unify(eobj) - ForStatement: unify(eobj) - default: null - } - } - - /** - * Returns a unified copy of the given destructuring pattern or null if it is invalid. - * This is helpful because these patterns can appear in very different forms and locations within the AST. - */ - public static def DestructNode unify(EObject astElem, EObject lhs, Expression rhs) { - new DestructNode( - astElem, // astElement - null, // propName - null, // varRef - null, // varDecl - toEntries(lhs, rhs), // nestedNodes - rhs, // defaultExpr - rhs, // assignedExpr - false // rest - ) - } - - /** - * Returns a unified copy of the given destructuring pattern or null if it is invalid. - * This is helpful because these patterns can appear in very different forms and locations within the AST. - */ - public static def DestructNode unify(VariableBinding binding) { - if (binding !== null && binding.pattern !== null // note: binding.expression is mandatory in variable statements but optional in for..in/of statements - && (binding.expression !== null || binding.eContainer instanceof ForStatement - )) { - unify(binding, binding.pattern, binding.expression); - } - } - - /** - * Returns a unified copy of the given destructuring pattern or null if it is invalid. - * This is helpful because these patterns can appear in very different forms and locations within the AST. - */ - public static def DestructNode unify(AssignmentExpression assignExpr) { - if (assignExpr !== null && assignExpr.lhs !== null && assignExpr.rhs !== null - && DestructureUtils.isTopOfDestructuringAssignment(assignExpr) - ) { - unify(assignExpr, assignExpr.lhs, assignExpr.rhs); - } - } - - /** - * Returns a unified copy of the given destructuring pattern or null if it is invalid. - * This is helpful because these patterns can appear in very different forms and locations within the AST. - */ - public static def DestructNode unify(ForStatement forStmnt) { - if (forStmnt !== null && DestructureUtils.isTopOfDestructuringForStatement(forStmnt)) { - val valueToBeDestructured = if (forStmnt.forOf) { - firstArrayElement(forStmnt.expression) - } else if (forStmnt.forIn) { - N4JSFactory.eINSTANCE.createStringLiteral() => [ - value = "" - ] - } else { - // impossible because #isTopOfDestructuringForStatement() returned true - throw new IllegalStateException - }; - val defaultExpression = if (forStmnt.forOf) { - forStmnt.expression - } else if (forStmnt.forIn) { - N4JSFactory.eINSTANCE.createStringLiteral() => [ - value = "" - ] - } else { - // impossible because #isTopOfDestructuringForStatement() returned true - throw new IllegalStateException - }; - - if (DestructureUtils.containsDestructuringPattern(forStmnt)) { - // case: for(var [a,b] of arr) {} - val binding = forStmnt.varDeclsOrBindings.filter(VariableBinding).head; - val rhs = firstArrayElement(forStmnt.expression); - return new DestructNode( - forStmnt, // astElement - null, // propName - null, // varRef - null, // varDecl - toEntries(binding.pattern, rhs), // nestedNodes - defaultExpression, // defaultExpr - valueToBeDestructured, // assignedExpr - false // rest - ) - } else if (DestructureUtils.isObjectOrArrayLiteral(forStmnt.getInitExpr())) { - // case: for([a,b] of arr) {} - return new DestructNode( - forStmnt, // astElement - null, // propName - null, // varRef - null, // varDecl - toEntries(forStmnt.initExpr, null), // nestedNodes - defaultExpression, // defaultExpr - defaultExpression, // assignedExpr - false // rest - ) - } - } - return null; - } - - private static def Expression firstArrayElement(Expression expr) { - return if (expr instanceof ArrayLiteral) expr.elements.head.expression else expr; - } - - private static def DestructNode[] toEntries(EObject pattern, TypableElement rhs) { - - val Iterator patElemIter = switch (pattern) { - ArrayLiteral: - pattern.elements.iterator - ObjectLiteral: - pattern.propertyAssignments.iterator - ArrayBindingPattern: - pattern.elements.iterator - ObjectBindingPattern: - pattern.properties.iterator - } - - var Iterator rhsElemIter = switch (rhs) { - ArrayLiteral: - rhs.elements.iterator - ObjectLiteral: - rhs.propertyAssignments.iterator - } - - val nestedDNs = new BasicEList(); - while (patElemIter.hasNext) { - val patElem = patElemIter.next; - val litElem = if (rhsElemIter === null) rhs else if (rhsElemIter.hasNext) rhsElemIter.next else null; - - val nestedNode = switch (patElem) { - ArrayElement: - toEntry(patElem, litElem) - PropertyNameValuePair: - toEntry(patElem, litElem) - BindingElement: - toEntry(patElem, litElem) - BindingProperty: - toEntry(patElem, litElem) - } - - if (nestedNode !== null) { - nestedDNs.add(nestedNode); - } - } - return nestedDNs; - } - - private static def DestructNode toEntry(ArrayElement elem, TypableElement rhs) { - val TypableElement rhsExpr = if (rhs instanceof ArrayElement) rhs.expression else rhs; - val expr = elem.expression; // note: ArrayPadding will return null for getExpression() - if (expr instanceof AssignmentExpression) - toEntry(elem, null, expr.lhs, expr.rhs, elem.spread, rhsExpr) - else - toEntry(elem, null, expr, null, elem.spread, rhsExpr) - } - - private static def DestructNode toEntry(PropertyNameValuePair pa, TypableElement rhs) { - val TypableElement rhsExpr = if (rhs instanceof PropertyNameValuePair) rhs.expression else rhs; - val expr = pa.expression; - if (expr instanceof AssignmentExpression) - toEntry(pa, pa.name, expr.lhs, expr.rhs, false, rhsExpr) - else - toEntry(pa, pa.name, expr, null, false, rhsExpr) - } - - private static def DestructNode toEntry(BindingElement elem, TypableElement rhs) { - val TypableElement expr = if (rhs instanceof ArrayElement) rhs.expression else rhs; - - if (elem.varDecl !== null) - toEntry(elem, null, elem.varDecl, elem.varDecl.expression, elem.rest, expr) - else if (elem.nestedPattern !== null) - toEntry(elem, null, elem.nestedPattern, elem.expression, elem.rest, expr) - else - toEntry(elem, null, null, null, false, expr) // return dummy entry to not break indices - } - - private static def DestructNode toEntry(BindingProperty prop, TypableElement rhs) { - if (prop.value?.varDecl !== null) { - val expr = getPropertyAssignmentExpression(rhs); - toEntry(prop, prop.name, prop.value.varDecl, prop.value.varDecl.expression, false, expr) - - } else if (prop.value?.nestedPattern !== null) { - val expr = getPropertyAssignmentExpression(rhs); - toEntry(prop, prop.name, prop.value.nestedPattern, prop.value.expression, false, expr) - - } else { - toEntry(prop, null, null, null, false, rhs) - } - } - - /** - * @param bindingTarget - * an IdentifierRef/VariableDeclaration or a nested pattern (which may be - * a BindingPattern, ArrayLiteral, or ObjectLiteral) - */ - private static def DestructNode toEntry(EObject astElement, String propName, EObject bindingTarget, - Expression defaultExpr, boolean rest, TypableElement rhs) { - - if (bindingTarget === null) { - // no target -> create a padding node - new DestructNode(astElement, propName, null, null, null, defaultExpr, null, rest) - - } else if (bindingTarget instanceof IdentifierRef) { - new DestructNode(astElement, propName, bindingTarget, null, null, defaultExpr, rhs, rest) - - } else if (bindingTarget instanceof VariableDeclaration) { - new DestructNode(astElement, propName, null, bindingTarget, null, defaultExpr, rhs, rest) - - } else if (bindingTarget instanceof ArrayLiteral || bindingTarget instanceof ObjectLiteral || - bindingTarget instanceof BindingPattern) { - new DestructNode(astElement, propName, null, null, toEntries(bindingTarget, rhs), defaultExpr, rhs, rest) - - } else { - // invalid binding target (probably a corrupt AST) -> create a padding node - new DestructNode(astElement, propName, null, null, null, defaultExpr, null, rest) - } - } - - /** @return the expression or function of the given PropertyAssignment */ - private static def TypableElement getPropertyAssignmentExpression(TypableElement rhs) { - switch (rhs) { - PropertyGetterDeclaration: - return rhs.definedFunctionOrAccessor - PropertySetterDeclaration: - return rhs.definedFunctionOrAccessor - PropertyMethodDeclaration: - return rhs.definedFunctionOrAccessor - PropertyNameValuePair: - return rhs.expression - PropertyAssignmentAnnotationList: - return null - default: - return rhs - } - } - - /** @return all {@link IdentifierRef} of variables that are written in the given assignment */ - public def List getAllDeclaredIdRefs() { - val List idRefs = new LinkedList(); - val Iterator allNestedNodes = this.stream().iterator(); - - while (allNestedNodes.hasNext()) { - val EObject eobj = allNestedNodes.next().getAstElement(); - if (eobj instanceof ArrayElement) { - val Expression expr = eobj.getExpression(); - if (expr instanceof AssignmentExpression) { - idRefs.add((expr.getLhs())); - } else { - idRefs.add(expr); - } - - } else if (eobj instanceof PropertyNameValuePairSingleName) { - idRefs.add(eobj.getIdentifierRef()); - - } else if (eobj instanceof PropertyNameValuePair) { - val Expression expr = eobj.getExpression(); - if (expr instanceof AssignmentExpression) { - idRefs.add(expr.getLhs()); - } else { - idRefs.add(expr); - } - } - } - return idRefs; - } - - /** @return a pair where its key is the assigned EObject and its value is the default EObject to the given lhs AST element */ - public static def Pair getValueFromDestructuring(EObject nodeElem) { - var EObject node = nodeElem; - var EObject topNode = null; - var EObject dNodeElem = null; - var boolean breakSearch = false; - - while (!breakSearch) { - var EObject parent = node.eContainer(); - dNodeElem = getDNodeElem(dNodeElem, parent, node); - topNode = getTopElem(topNode, parent); - breakSearch = parent instanceof Statement; - node = parent; - } - - var DestructNode dNode = if (topNode instanceof AssignmentExpression) { - DestructNode.unify(topNode); - } else if (topNode instanceof VariableBinding) { - DestructNode.unify(topNode); - } else if (topNode instanceof ForStatement) { - DestructNode.unify(topNode); - } else { - null; - }; - - if (dNode !== null) { - dNode = dNode.findNodeForElement(dNodeElem); - if (dNode !== null) { - var EObject assgnValue = dNode.getAssignedElem(); - var EObject defaultValue = dNode.getDefaultExpr(); - return assgnValue -> defaultValue; - } - } - - return null; - } - - private static def EObject getDNodeElem(EObject dNodeElem, EObject parent, EObject node) { - if (dNodeElem !== null) { - return dNodeElem; - } - if (node instanceof BindingElement && parent instanceof BindingProperty) { - return parent; - } - if (node instanceof BindingElement || node instanceof ArrayElement || node instanceof PropertyAssignment) { - return node; - } - } - - private static def EObject getTopElem(EObject oldTopNode, EObject parent) { - val EObject newTopNode = switch (parent) { - ForStatement: - parent - AssignmentExpression: - parent - VariableBinding: - parent - default: - null - }; - - if (newTopNode !== null) { - return newTopNode - } else { - oldTopNode; - } - } - - public static def List getAllDeclaredIdRefs(EObject eobj) { - val DestructNode dnode = switch (eobj) { - ForStatement: - unify(eobj) - VariableBinding: - unify(eobj) - AssignmentExpression: - unify(eobj) - default: - null - }; - - if (dnode === null) { - return #[]; - } - - return dnode.allDeclaredIdRefs; - } -} diff --git a/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructureUtils.java b/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructureUtils.java index c1d0340739..777b2887f7 100644 --- a/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructureUtils.java +++ b/plugins/org.eclipse.n4js.model/src/org/eclipse/n4js/n4JS/DestructureUtils.java @@ -275,7 +275,7 @@ public static boolean isInDestructuringPattern(EObject obj) { return isParentPartOfSameDestructuringPattern(obj); } - /** @return true iff the given {@link EObject} can be {@link DestructNode#getAstElement()} */ + /** @return true iff the given {@link EObject} can be {@link DestructNode#astElement} */ public static boolean isRepresentingElement(EObject eobj) { boolean isRepresentingElement = false; isRepresentingElement |= eobj instanceof ArrayElement; diff --git a/plugins/org.eclipse.n4js.model/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.model/xtend-gen/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/plugins/org.eclipse.n4js.model/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/plugins/org.eclipse.n4js.regex.ide/.classpath b/plugins/org.eclipse.n4js.regex.ide/.classpath index d28af888cb..fc609eff89 100644 --- a/plugins/org.eclipse.n4js.regex.ide/.classpath +++ b/plugins/org.eclipse.n4js.regex.ide/.classpath @@ -2,7 +2,6 @@ - diff --git a/plugins/org.eclipse.n4js.regex.ide/build.properties b/plugins/org.eclipse.n4js.regex.ide/build.properties index bef28d3d93..ec65859a4c 100644 --- a/plugins/org.eclipse.n4js.regex.ide/build.properties +++ b/plugins/org.eclipse.n4js.regex.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/plugins/org.eclipse.n4js.regex.ide/pom.xml b/plugins/org.eclipse.n4js.regex.ide/pom.xml index 0fd5ff17d0..6da554c206 100644 --- a/plugins/org.eclipse.n4js.regex.ide/pom.xml +++ b/plugins/org.eclipse.n4js.regex.ide/pom.xml @@ -30,10 +30,6 @@ Contributors: org.apache.maven.plugins maven-clean-plugin - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeModule.xtend b/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeModule.java similarity index 76% rename from plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeModule.xtend rename to plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeModule.java index 2daca727c9..c67769bc34 100644 --- a/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeModule.xtend +++ b/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeModule.java @@ -8,11 +8,11 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.regex.ide - +package org.eclipse.n4js.regex.ide; /** * Use this class to register ide components. */ -class RegularExpressionIdeModule extends AbstractRegularExpressionIdeModule { +public class RegularExpressionIdeModule extends AbstractRegularExpressionIdeModule { + // empty } diff --git a/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeSetup.java b/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeSetup.java new file mode 100644 index 0000000000..41d24e8772 --- /dev/null +++ b/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeSetup.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2017 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.regex.ide; + +import org.eclipse.n4js.regex.RegularExpressionRuntimeModule; +import org.eclipse.n4js.regex.RegularExpressionStandaloneSetup; +import org.eclipse.xtext.util.Modules2; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Initialization support for running Xtext languages as language servers. + */ +public class RegularExpressionIdeSetup extends RegularExpressionStandaloneSetup { + + @Override + public Injector createInjector() { + return Guice + .createInjector(Modules2.mixin(new RegularExpressionRuntimeModule(), new RegularExpressionIdeModule())); + } + +} diff --git a/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeSetup.xtend b/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeSetup.xtend deleted file mode 100644 index 926c575f3c..0000000000 --- a/plugins/org.eclipse.n4js.regex.ide/src/org/eclipse/n4js/regex/ide/RegularExpressionIdeSetup.xtend +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.regex.ide - -import com.google.inject.Guice -import org.eclipse.n4js.regex.RegularExpressionRuntimeModule -import org.eclipse.n4js.regex.RegularExpressionStandaloneSetup -import org.eclipse.xtext.util.Modules2 - -/** - * Initialization support for running Xtext languages as language servers. - */ -class RegularExpressionIdeSetup extends RegularExpressionStandaloneSetup { - - override createInjector() { - Guice.createInjector(Modules2.mixin(new RegularExpressionRuntimeModule, new RegularExpressionIdeModule)) - } - -} diff --git a/plugins/org.eclipse.n4js.regex.ide/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.regex.ide/xtend-gen/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/plugins/org.eclipse.n4js.regex.ide/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/plugins/org.eclipse.n4js.regex/.classpath b/plugins/org.eclipse.n4js.regex/.classpath index 217ca618e4..5153825dfe 100644 --- a/plugins/org.eclipse.n4js.regex/.classpath +++ b/plugins/org.eclipse.n4js.regex/.classpath @@ -12,6 +12,5 @@ - diff --git a/plugins/org.eclipse.n4js.regex/build.properties b/plugins/org.eclipse.n4js.regex/build.properties index 4e5f9fdb5f..ac1f3722dc 100644 --- a/plugins/org.eclipse.n4js.regex/build.properties +++ b/plugins/org.eclipse.n4js.regex/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = model/,\ META-INF/,\ .,\ diff --git a/plugins/org.eclipse.n4js.regex/pom.xml b/plugins/org.eclipse.n4js.regex/pom.xml index b32fd2695f..613d662dde 100644 --- a/plugins/org.eclipse.n4js.regex/pom.xml +++ b/plugins/org.eclipse.n4js.regex/pom.xml @@ -73,11 +73,6 @@ Contributors: - - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionRuntimeModule.xtend b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionRuntimeModule.java similarity index 65% rename from plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionRuntimeModule.xtend rename to plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionRuntimeModule.java index 7e0943ffd4..5cd4418fb9 100644 --- a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionRuntimeModule.xtend +++ b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionRuntimeModule.java @@ -8,20 +8,20 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.regex +package org.eclipse.n4js.regex; -import org.eclipse.xtext.conversion.impl.INTValueConverter +import org.eclipse.xtext.conversion.impl.INTValueConverter; /** * Use this class to register components to be used at runtime / without the Equinox extension registry. */ -class RegularExpressionRuntimeModule extends AbstractRegularExpressionRuntimeModule { +public class RegularExpressionRuntimeModule extends AbstractRegularExpressionRuntimeModule { /** * INT is a data type rule thus the specialized binding */ - def Class bindINTValueConverter() { - return RegExINTValueConverter; + public Class bindINTValueConverter() { + return RegExINTValueConverter.class; } } diff --git a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionStandaloneSetup.xtend b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionStandaloneSetup.java similarity index 74% rename from plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionStandaloneSetup.xtend rename to plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionStandaloneSetup.java index b69feb848e..6f00167592 100644 --- a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionStandaloneSetup.xtend +++ b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/RegularExpressionStandaloneSetup.java @@ -8,15 +8,15 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.regex - +package org.eclipse.n4js.regex; /** * Initialization support for running Xtext languages without Equinox extension registry. */ -class RegularExpressionStandaloneSetup extends RegularExpressionStandaloneSetupGenerated { +public class RegularExpressionStandaloneSetup extends RegularExpressionStandaloneSetupGenerated { - def static void doSetup() { - new RegularExpressionStandaloneSetup().createInjectorAndDoEMFRegistration() + /***/ + static public void doSetup() { + new RegularExpressionStandaloneSetup().createInjectorAndDoEMFRegistration(); } } diff --git a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/generator/RegularExpressionGenerator.java b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/generator/RegularExpressionGenerator.java new file mode 100644 index 0000000000..35eb206c3b --- /dev/null +++ b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/generator/RegularExpressionGenerator.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.regex.generator; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.generator.AbstractGenerator; +import org.eclipse.xtext.generator.IFileSystemAccess2; +import org.eclipse.xtext.generator.IGeneratorContext; + +/** + * Generates code from your model files on save. + * + * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation + */ +public class RegularExpressionGenerator extends AbstractGenerator { + + @Override + public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { + // fsa.generateFile('greetings.txt', 'People to greet: ' + + // resource.allContents + // .filter(typeof(Greeting)) + // .map[name] + // .join(', ')) + } +} diff --git a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/generator/RegularExpressionGenerator.xtend b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/generator/RegularExpressionGenerator.xtend deleted file mode 100644 index b63de1fecc..0000000000 --- a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/generator/RegularExpressionGenerator.xtend +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.regex.generator - -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.AbstractGenerator -import org.eclipse.xtext.generator.IFileSystemAccess2 -import org.eclipse.xtext.generator.IGeneratorContext - -/** - * Generates code from your model files on save. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation - */ -class RegularExpressionGenerator extends AbstractGenerator { - - override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { -// fsa.generateFile('greetings.txt', 'People to greet: ' + -// resource.allContents -// .filter(typeof(Greeting)) -// .map[name] -// .join(', ')) - } -} diff --git a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/scoping/RegularExpressionScopeProvider.xtend b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/scoping/RegularExpressionScopeProvider.java similarity index 71% rename from plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/scoping/RegularExpressionScopeProvider.xtend rename to plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/scoping/RegularExpressionScopeProvider.java index ed6c9c4794..d974dbbbdf 100644 --- a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/scoping/RegularExpressionScopeProvider.xtend +++ b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/scoping/RegularExpressionScopeProvider.java @@ -8,14 +8,13 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.regex.scoping +package org.eclipse.n4js.regex.scoping; /** * This class contains custom scoping description. * - * see : http://www.eclipse.org/Xtext/documentation.html#scoping - * on how and when to use it + * see : http://www.eclipse.org/Xtext/documentation.html#scoping on how and when to use it */ -class RegularExpressionScopeProvider extends org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider { - +public class RegularExpressionScopeProvider extends org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider { + // empty } diff --git a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/validation/RegularExpressionValidator.xtend b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/validation/RegularExpressionValidator.java similarity index 54% rename from plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/validation/RegularExpressionValidator.xtend rename to plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/validation/RegularExpressionValidator.java index 141632cdf4..2ecc798cd8 100644 --- a/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/validation/RegularExpressionValidator.xtend +++ b/plugins/org.eclipse.n4js.regex/src/org/eclipse/n4js/regex/validation/RegularExpressionValidator.java @@ -8,7 +8,7 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.regex.validation +package org.eclipse.n4js.regex.validation; //import org.eclipse.xtext.validation.Check /** @@ -16,16 +16,16 @@ * * see http://www.eclipse.org/Xtext/documentation.html#validation */ -class RegularExpressionValidator extends AbstractRegularExpressionValidator { +public class RegularExpressionValidator extends AbstractRegularExpressionValidator { -// public static val INVALID_NAME = 'invalidName' -// -// @Check -// def checkGreetingStartsWithCapital(Greeting greeting) { -// if (!Character.isUpperCase(greeting.name.charAt(0))) { -// warning('Name should start with a capital', -// MyDslPackage.Literals.GREETING__NAME, -// INVALID_NAME) -// } -// } + // public static val INVALID_NAME = 'invalidName' + // + // @Check + // def checkGreetingStartsWithCapital(Greeting greeting) { + // if (!Character.isUpperCase(greeting.name.charAt(0))) { + // warning('Name should start with a capital', + // MyDslPackage.Literals.GREETING__NAME, + // INVALID_NAME) + // } + // } } diff --git a/plugins/org.eclipse.n4js.regex/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.regex/xtend-gen/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/plugins/org.eclipse.n4js.regex/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/plugins/org.eclipse.n4js.semver.ide/.classpath b/plugins/org.eclipse.n4js.semver.ide/.classpath index d28af888cb..fc609eff89 100644 --- a/plugins/org.eclipse.n4js.semver.ide/.classpath +++ b/plugins/org.eclipse.n4js.semver.ide/.classpath @@ -2,7 +2,6 @@ - diff --git a/plugins/org.eclipse.n4js.semver.ide/build.properties b/plugins/org.eclipse.n4js.semver.ide/build.properties index bef28d3d93..ec65859a4c 100644 --- a/plugins/org.eclipse.n4js.semver.ide/build.properties +++ b/plugins/org.eclipse.n4js.semver.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/plugins/org.eclipse.n4js.semver.ide/pom.xml b/plugins/org.eclipse.n4js.semver.ide/pom.xml index 0e1fd68378..78d94531f6 100644 --- a/plugins/org.eclipse.n4js.semver.ide/pom.xml +++ b/plugins/org.eclipse.n4js.semver.ide/pom.xml @@ -30,10 +30,6 @@ Contributors: org.apache.maven.plugins maven-clean-plugin - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/plugins/org.eclipse.n4js.semver.ide/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.semver.ide/xtend-gen/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/plugins/org.eclipse.n4js.semver.ide/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/plugins/org.eclipse.n4js.semver.model/pom.xml b/plugins/org.eclipse.n4js.semver.model/pom.xml index 760864b701..7464ebfba8 100644 --- a/plugins/org.eclipse.n4js.semver.model/pom.xml +++ b/plugins/org.eclipse.n4js.semver.model/pom.xml @@ -38,11 +38,6 @@ Contributors: maven-resources-plugin ${maven-resources-plugin.version} - - org.eclipse.xtend - xtend-maven-plugin - - diff --git a/plugins/org.eclipse.n4js.transpiler.es/.classpath b/plugins/org.eclipse.n4js.transpiler.es/.classpath index 14dd6eb29b..e3378d07f0 100644 --- a/plugins/org.eclipse.n4js.transpiler.es/.classpath +++ b/plugins/org.eclipse.n4js.transpiler.es/.classpath @@ -1,7 +1,6 @@ - diff --git a/plugins/org.eclipse.n4js.transpiler.es/build.properties b/plugins/org.eclipse.n4js.transpiler.es/build.properties index aaedd5b241..17daa5b49c 100644 --- a/plugins/org.eclipse.n4js.transpiler.es/build.properties +++ b/plugins/org.eclipse.n4js.transpiler.es/build.properties @@ -1,5 +1,4 @@ -source.. = src/,\ - xtend-gen/ +source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff --git a/plugins/org.eclipse.n4js.transpiler.es/pom.xml b/plugins/org.eclipse.n4js.transpiler.es/pom.xml index cda3552311..3856d6b132 100644 --- a/plugins/org.eclipse.n4js.transpiler.es/pom.xml +++ b/plugins/org.eclipse.n4js.transpiler.es/pom.xml @@ -34,11 +34,6 @@ Contributors: maven-resources-plugin ${maven-resources-plugin.version} - - org.eclipse.xtend - xtend-maven-plugin - - diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/BlockAssistant.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/BlockAssistant.java new file mode 100644 index 0000000000..4fd6b166a4 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/BlockAssistant.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.assistants; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.voidType; + +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.transpiler.TransformationAssistant; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; + +import com.google.inject.Inject; + +/** + */ +public class BlockAssistant extends TransformationAssistant { + + @Inject + private N4JSTypeSystem ts; + + /***/ + public final boolean needsReturnInsertionForBody(ArrowFunction arrowFuncInIM) { + // unfortunately, we need a properly contained AST element below (see preconditions above) + ArrowFunction origAST = getState().tracer.getOriginalASTNodeOfSameType(arrowFuncInIM, false); + if (origAST == null) { + // this arrow function does not come from the N4JS source code but was created by some transformation + // -> we assume it was intended to return a value and hence a return must be inserted + return true; + } + if (!origAST.isSingleExprImplicitReturn()) { + return false; + } + // check if body is typed void, in which case no return will be inserted + Expression singleExpr = origAST.implicitReturnExpr(); + TypeRef implicitReturnTypeRef = ts.type(getState().G, singleExpr); + if (implicitReturnTypeRef != null && implicitReturnTypeRef.getDeclaredType() == voidType(getState().G)) { + return false; + } + return true; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/BlockAssistant.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/BlockAssistant.xtend deleted file mode 100644 index 21eec2cdcc..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/BlockAssistant.xtend +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.assistants - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.transpiler.TransformationAssistant -import org.eclipse.n4js.typesystem.N4JSTypeSystem - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - */ -class BlockAssistant extends TransformationAssistant { - - @Inject - private N4JSTypeSystem ts - - def public final boolean needsReturnInsertionForBody(ArrowFunction arrowFuncInIM) { - // unfortunately, we need a properly contained AST element below (see preconditions above) - val origAST = state.tracer.getOriginalASTNodeOfSameType(arrowFuncInIM, false); - if (origAST === null) { - // this arrow function does not come from the N4JS source code but was created by some transformation - // -> we assume it was intended to return a value and hence a return must be inserted - return true; - } - if (!origAST.isSingleExprImplicitReturn) { - return false; - } - // check if body is typed void, in which case no return will be inserted - val singleExpr = origAST.implicitReturnExpr(); - val implicitReturnTypeRef = ts.type(state.G, singleExpr); - if (implicitReturnTypeRef?.declaredType === state.G.voidType) { - return false; - } - return true; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassConstructorAssistant.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassConstructorAssistant.java new file mode 100644 index 0000000000..7ad80f6149 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassConstructorAssistant.java @@ -0,0 +1,580 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.assistants; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Argument; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._AssignmentExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ConditionalExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._EqualityExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ExprStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Fpar; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IfStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4MethodDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._OR; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ObjLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Parenthesis; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyNameValuePair; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._RelationalExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Snippet; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteralForSTE; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._SuperLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._TRUE; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ThisLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableStatement; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.errorType; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.builtInOrProvidedByRuntime; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.builtInOrProvidedByRuntimeOrShape; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.ListExtensions.map; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.Argument; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.EqualityOperator; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ExpressionStatement; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.ModifierUtils; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.N4Modifier; +import org.eclipse.n4js.n4JS.N4SetterDeclaration; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.RelationalOperator; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.n4JS.SuperLiteral; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableStatementKeyword; +import org.eclipse.n4js.transpiler.TransformationAssistant; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.MemberAccessModifier; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +/** + * Modify or create the constructor of a class declaration. + */ +public class ClassConstructorAssistant extends TransformationAssistant { + + @Inject + private TypeAssistant typeAssistant; + @Inject + private ClassifierAssistant classifierAssistant; + + private static final class SpecInfo { + /** + * The @Spec-fpar in the intermediate model. Never null. + */ + public final FormalParameter fpar; + /** + * Names of properties in the with-clause of the @Spec-fpar's type. For example, as in: + * + *

+		 * constructor(@Spec specObj: ~i~this with { addProp: string }) { ... } .
+		 */
+		public final ImmutableSet additionalProps;
+
+		public SpecInfo(FormalParameter fpar, Iterable additionalProps) {
+			this.fpar = Objects.requireNonNull(fpar);
+			this.additionalProps = ImmutableSet.copyOf(additionalProps);
+		}
+	}
+
+	/**
+	 * Amend the constructor of the given class with implicit functionality (e.g. initialization of instance fields).
+	 * Will create an implicit constructor declaration iff no constructor is defined in the N4JS source code AND an
+	 * implicit constructor is actually required.
+	 */
+	public void amendConstructor(N4ClassDeclaration classDecl, SymbolTableEntry classSTE,
+			SymbolTableEntryOriginal superClassSTE,
+			LinkedHashSet fieldsRequiringExplicitDefinition) {
+
+		N4MethodDeclaration explicitCtorDecl = classDecl.getOwnedCtor(); // the constructor defined in the N4JS source
+																			// code or 'null' if none was defined
+		N4MethodDeclaration ctorDecl = explicitCtorDecl != null ? explicitCtorDecl
+				: _N4MethodDecl(N4JSLanguageConstants.CONSTRUCTOR);
+
+		// amend formal parameters
+		SpecInfo specInfo = amendFormalParametersOfConstructor(classDecl, ctorDecl, explicitCtorDecl);
+
+		// amend body
+		boolean isNonTrivial = amendBodyOfConstructor(classDecl, classSTE, superClassSTE, ctorDecl, explicitCtorDecl,
+				specInfo, fieldsRequiringExplicitDefinition);
+
+		// add constructor to classDecl (if necessary)
+		if (ctorDecl.eContainer() == null && isNonTrivial) {
+			classDecl.getOwnedMembersRaw().add(0, ctorDecl);
+		}
+	}
+
+	private SpecInfo amendFormalParametersOfConstructor(N4ClassDeclaration classDecl, N4MethodDeclaration ctorDecl,
+			N4MethodDeclaration explicitCtorDecl) {
+		FormalParameter specFpar = null;
+		TypeRef specFparTypeRef = null;
+
+		boolean hasExplicitCtor = explicitCtorDecl != null;
+		if (hasExplicitCtor) {
+			// explicitly defined constructor
+			// --> nothing to be changed (use fpars from N4JS source code)
+
+			specFpar = head(filter(ctorDecl.getFpars(), fpar -> AnnotationDefinition.SPEC.hasAnnotation(fpar)));
+			if (specFpar != null) {
+				TypeReferenceNode typeRefNode = specFpar.getDeclaredTypeRefNode();
+				if (typeRefNode != null) {
+					specFparTypeRef = getState().info.getOriginalProcessedTypeRef(typeRefNode);
+				}
+			}
+		} else {
+			// implicit constructor
+			// --> create fpars using fpars of nearest constructor in hierarchy as template
+
+			TMethod templateCtor = getNearestConstructorInHierarchy(classDecl);
+			if (templateCtor != null) {
+				for (TFormalParameter templateFpar : templateCtor.getFpars()) {
+					boolean isSpecFpar = AnnotationDefinition.SPEC.hasAnnotation(templateFpar);
+					FormalParameter newFpar = _Fpar(templateFpar.getName(), templateFpar.isVariadic(), isSpecFpar);
+					ctorDecl.getFpars().add(newFpar);
+
+					if (isSpecFpar && specFpar == null) {
+						specFpar = newFpar;
+						specFparTypeRef = TypeUtils.copy(templateFpar.getTypeRef());
+					}
+				}
+			}
+		}
+
+		if (specFpar != null) {
+			Iterable additionalProps = Collections.emptyList();
+			if (specFparTypeRef != null && specFparTypeRef.getStructuralMembers() != null) {
+				additionalProps = map(specFparTypeRef.getStructuralMembers(), m -> m.getName());
+			}
+			return new SpecInfo(specFpar, additionalProps);
+		}
+		return null;
+	}
+
+	/**
+	 * Returns true iff the constructor is non-trivial, i.e. non-empty and containing more than just the
+	 * default super call.
+	 */
+	private boolean amendBodyOfConstructor(N4ClassDeclaration classDecl, SymbolTableEntry classSTE,
+			SymbolTableEntryOriginal superClassSTE,
+			N4MethodDeclaration ctorDecl, N4MethodDeclaration explicitCtorDecl, SpecInfo specInfo,
+			LinkedHashSet fieldsRequiringExplicitDefinition) {
+
+		boolean hasExplicitCtor = explicitCtorDecl != null;
+		Block body = ctorDecl.getBody();
+
+		boolean isDirectSubclassOfError = superClassSTE == null ? null
+				: superClassSTE.getOriginalTarget() == errorType(getState().G);
+		int superCallIndex = (explicitCtorDecl != null && explicitCtorDecl.getBody() != null)
+				? getSuperCallIndex(explicitCtorDecl)
+				: -1;
+		boolean hasExplicitSuperCall = superCallIndex >= 0;
+		ExpressionStatement defaultSuperCall = null;
+
+		int idx = (hasExplicitSuperCall) ? superCallIndex + 1 : 0;
+
+		// add/replace/modify super call
+		if (hasExplicitSuperCall) {
+			// keep existing, explicit super call unchanged
+		} else {
+			// no explicitCtorDecl OR no body OR no explicit super call (and no direct subclass of Error)
+			// --> add default super call (if required)
+			if (superClassSTE != null) {
+				List fparsOfSuperCtor = (hasExplicitCtor) ?
+				// explicit ctor without an explicit super call: only allowed if the super constructor
+				// does not have any arguments, so we can simply assume empty fpars here, without actually
+				// looking up the super constructor with #getNearestConstructorInHierarchy():
+						Collections.emptyList()
+						:
+						// no explicit ctor: the already created implicit constructor in 'ctorDecl' has the
+						// same fpars as the super constructor, so we can use those as a template:
+						ctorDecl.getFpars();
+				defaultSuperCall = createDefaultSuperCall(fparsOfSuperCtor);
+				idx = insertAt(body.getStatements(), idx, defaultSuperCall);
+			}
+		}
+		if (isDirectSubclassOfError) {
+			// special case: add oddities for sub-classing Error
+			idx = insertAt(body.getStatements(), idx, createSubclassingErrorOddities());
+		}
+
+		// if we are in a spec-constructor: prepare a local variable for the spec-object and
+		// ensure it is never 'undefined' or 'null'
+		SymbolTableEntry specObjSTE = null;
+		if (specInfo != null) {
+			// let $specObj = specFpar || {};
+			SymbolTableEntry specFparSTE = findSymbolTableEntryForElement(specInfo.fpar, true);
+			VariableDeclaration specObjVarDecl = _VariableDeclaration("$specObj",
+					_OR(_IdentRef(specFparSTE), _ObjLit()));
+			idx = insertAt(body.getStatements(), idx,
+					_VariableStatement(VariableStatementKeyword.CONST, specObjVarDecl));
+
+			specObjSTE = findSymbolTableEntryForElement(specObjVarDecl, true);
+		}
+
+		// add explicit definitions of instance fields (only for fields that actually require this)
+		idx = insertAt(body.getStatements(), idx,
+				classifierAssistant.createExplicitFieldDefinitions(classSTE, false, fieldsRequiringExplicitDefinition));
+
+		// add initialization code for instance fields
+		idx = insertAt(body.getStatements(), idx,
+				createInstanceFieldInitCode(classDecl, specInfo, specObjSTE, fieldsRequiringExplicitDefinition));
+
+		// add delegation to field initialization functions of all directly implemented interfaces
+		idx = insertAt(body.getStatements(), idx,
+				createDelegationToFieldInitOfImplementedInterfaces(classDecl, specObjSTE));
+
+		// check if constructor is non-trivial
+		EList ctorDeclStmnts = ctorDecl.getBody().getStatements();
+		boolean bodyContainsOnlyDefaultSuperCall = defaultSuperCall != null
+				&& ctorDeclStmnts.size() == 1
+				&& ctorDeclStmnts.get(0) == defaultSuperCall;
+		boolean isNonTrivialCtor = !ctorDeclStmnts.isEmpty() && !bodyContainsOnlyDefaultSuperCall;
+
+		return isNonTrivialCtor;
+	}
+
+	private List createInstanceFieldInitCode(N4ClassDeclaration classDecl, SpecInfo specInfo,
+			SymbolTableEntry specObjSTE, Set fieldsWithExplicitDefinition) {
+		List allFields = toList(filter(classDecl.getOwnedFields(), f -> !f.isStatic()
+				&& !isConsumedFromInterface(f)
+				&& !builtInOrProvidedByRuntime(getState().info.getOriginalDefinedMember(f))));
+		if (specInfo != null) {
+			// we have a spec-parameter -> we are in a spec-style constructor
+			List result = new ArrayList<>();
+
+			// step #1: initialize all fields either with data from the specFpar OR or their initializer expression
+			List currFields = new ArrayList<>();
+			boolean currFieldsAreSpecced = false;
+			for (N4FieldDeclaration field : allFields) {
+				boolean isSpecced = isPublic(field) || specInfo.additionalProps.contains(field.getName());
+				if (isSpecced == currFieldsAreSpecced) {
+					currFields.add(field);
+				} else {
+					result.addAll(createFieldInitCodeForSeveralFields(currFields, currFieldsAreSpecced, specObjSTE));
+					currFields.clear();
+					currFields.add(field);
+					currFieldsAreSpecced = isSpecced;
+				}
+			}
+			result.addAll(createFieldInitCodeForSeveralFields(currFields, currFieldsAreSpecced, specObjSTE));
+
+			// step #2: invoke setters with data from specFpar
+			// (note: in case of undefined specFpar at runtime, setters should not be invoked, so we wrap in if
+			// statement)
+			Iterable speccedSetters = filter(classDecl.getOwnedSetters(),
+					sd -> !sd.isStatic() && sd.getDeclaredModifiers().contains(N4Modifier.PUBLIC));
+			result.addAll(toList(map(speccedSetters, sd -> createFieldInitCodeForSingleSpeccedSetter(sd, specObjSTE))));
+
+			return result;
+		} else {
+			// simple: just initialize fields with data from their initializer expression
+			return toList(map(filter(allFields,
+					f -> !(f.getExpression() == null && fieldsWithExplicitDefinition.contains(f))),
+					f -> createFieldInitCodeForSingleField(f)));
+		}
+	}
+
+	private List createFieldInitCodeForSeveralFields(List fieldDecls,
+			boolean fieldsAreSpecced, SymbolTableEntry specObjSTE) {
+		if (fieldDecls.isEmpty()) {
+			return Collections.emptyList();
+		}
+		if (!fieldsAreSpecced) {
+			return toList(map(fieldDecls, fd -> createFieldInitCodeForSingleField(fd)));
+		}
+		if (fieldDecls.size() == 1) {
+			return List.of(createFieldInitCodeForSingleSpeccedField(fieldDecls.get(0), specObjSTE));
+		}
+		return List.of(createFieldInitCodeForSeveralSpeccedFields(fieldDecls, specObjSTE));
+	}
+
+	private Statement createFieldInitCodeForSingleField(N4FieldDeclaration fieldDecl) {
+		// here we create:
+		//
+		// this.fieldName = ;
+		// or
+		// this.fieldName = undefined;
+		//
+		// NOTE: we set the field to 'undefined' even in the second case, because ...
+		// 1) it makes a difference when #hasOwnProperty(), etc. is used after the constructor returns,
+		// 2) for consistency with the method #createFieldInitCodeForSeveralSpeccedFields(), because with
+		// destructuring as used by that method, the property will always be assigned (even if the
+		// value is 'undefined' and there is no default).
+		//
+		SymbolTableEntry fieldSTE = findSymbolTableEntryForElement(fieldDecl, true);
+		return _ExprStmnt(_AssignmentExpr(
+				_PropertyAccessExpr(_ThisLiteral(), fieldSTE),
+				(fieldDecl.getExpression() != null) ? fieldDecl.getExpression() // reusing the expression here
+						: undefinedRef()));
+	}
+
+	@SuppressWarnings("unchecked")
+	private Statement createFieldInitCodeForSeveralSpeccedFields(Iterable fieldDecls,
+			SymbolTableEntry specObjSTE) {
+		// here we create:
+		//
+		// ({
+		// fieldName: this.fieldName = ,
+		// ...
+		// } = spec);
+		//
+
+		Iterable> pairs = map(fieldDecls, fieldDecl -> {
+			SymbolTableEntry fieldSTE = findSymbolTableEntryForElement(fieldDecl, true);
+			ParameterizedPropertyAccessExpression_IM thisFieldName = _PropertyAccessExpr(_ThisLiteral(), fieldSTE);
+			Expression expr = hasNonTrivialInitExpression(fieldDecl)
+					? _AssignmentExpr(thisFieldName, fieldDecl.getExpression()) // reusing the expression here
+					: thisFieldName;
+			return Pair.of(fieldDecl.getName(), expr);
+		});
+
+		return _ExprStmnt(
+				_Parenthesis(
+						_AssignmentExpr(
+								_ObjLit(
+										Iterables.toArray(pairs, Pair.class)),
+								_IdentRef(specObjSTE))));
+	}
+
+	private Statement createFieldInitCodeForSingleSpeccedField(N4FieldDeclaration fieldDecl,
+			SymbolTableEntry specObjSTE) {
+		SymbolTableEntry fieldSTE = findSymbolTableEntryForElement(fieldDecl, true);
+		if (hasNonTrivialInitExpression(fieldDecl)) {
+			// here we create:
+			//
+			// this.fieldName = spec.fieldName == undefined ?  : spec.fieldName;
+			//
+			// NOTE: don't use something like "'fieldName' in spec" as the condition above, because that would
+			// not have the same behavior as destructuring in method #createFieldInitCodeForSeveralSpeccedFields()
+			// in case the property is present but has value 'undefined'!
+			//
+			return _ExprStmnt(_AssignmentExpr(
+					_PropertyAccessExpr(_ThisLiteral(), fieldSTE),
+					// ? :
+					_ConditionalExpr(
+							// spec.fieldName == undefined
+							_EqualityExpr(_PropertyAccessExpr(specObjSTE, fieldSTE), EqualityOperator.SAME,
+									undefinedRef()),
+							// 
+							(fieldDecl.getExpression() != null) ? copy(fieldDecl.getExpression()) // need to copy
+																									// expression here,
+																									// because it will
+																									// be used again!
+									: undefinedRef(),
+							// spec.fieldName
+							_PropertyAccessExpr(specObjSTE, fieldSTE))));
+		} else {
+			// we have a trivial init-expression ...
+
+			// here we create:
+			//
+			// this.fieldName = spec.fieldName;
+			//
+			return _ExprStmnt(_AssignmentExpr(
+					_PropertyAccessExpr(_ThisLiteral(), fieldSTE),
+					_PropertyAccessExpr(specObjSTE, fieldSTE)));
+		}
+	}
+
+	private Statement createFieldInitCodeForSingleSpeccedSetter(N4SetterDeclaration setterDecl,
+			SymbolTableEntry specObjSTE) {
+		// here we create:
+		//
+		// if ('fieldName' in spec) {
+		// this.fieldName = spec.fieldName;
+		// }
+		//
+
+		SymbolTableEntry setterSTE = findSymbolTableEntryForElement(setterDecl, true);
+		return _IfStmnt(
+				_RelationalExpr(
+						_StringLiteralForSTE(setterSTE), RelationalOperator.IN, _IdentRef(specObjSTE)),
+				_ExprStmnt(
+						_AssignmentExpr(
+								_PropertyAccessExpr(_ThisLiteral(), setterSTE),
+								_PropertyAccessExpr(specObjSTE, setterSTE))));
+	}
+
+	// NOTE: compare this to the super calls generated from SuperLiterals in SuperLiteralTransformation
+	private ExpressionStatement createDefaultSuperCall(List fpars) {
+
+		boolean variadicCase = !fpars.isEmpty() && last(fpars).isVariadic();
+		List args = map(fpars, fpar -> _Argument(
+				_IdentRef(findSymbolTableEntryForElement(fpar, true))));
+
+		if (variadicCase) {
+			last(args).setSpread(true);
+		}
+
+		ParameterizedCallExpression callExpr = _CallExpr();
+		callExpr.setTarget(_SuperLiteral());
+		callExpr.getArguments().addAll(args);
+		return _ExprStmnt(callExpr);
+	}
+
+	/** To be inserted where the explicit or implicit super call would be located, normally. */
+	private List createSubclassingErrorOddities() {
+		return List.of(_ExprStmnt(_Snippet("""
+					this.name = this.constructor.n4type.name;
+					if (Error.captureStackTrace) {
+					    Error.captureStackTrace(this, this.name);
+					}
+				""")));
+	}
+
+	private List createDelegationToFieldInitOfImplementedInterfaces(N4ClassDeclaration classDecl,
+			SymbolTableEntry /* nullable */ specObjSTE) {
+
+		List implementedIfcSTEs = toList(
+				filter(typeAssistant.getSuperInterfacesSTEs(classDecl), intf ->
+				// regarding the cast to TInterface: see preconditions of ClassDeclarationTransformation
+				// regarding the entire line: generate $fieldInit call only if the interface is neither built-in nor
+				// provided by runtime nor shape
+				!builtInOrProvidedByRuntimeOrShape((TInterface) intf.getOriginalTarget())));
+		if (implementedIfcSTEs.isEmpty()) {
+			return Collections.emptyList();
+		}
+
+		LinkedHashSet ownedInstanceDataFieldsSupressMixin = new LinkedHashSet<>();
+		ownedInstanceDataFieldsSupressMixin.addAll(
+				toList(map(filter(classDecl.getOwnedGetters(), g -> !isConsumedFromInterface(g)), g -> g.getName())));
+		ownedInstanceDataFieldsSupressMixin.addAll(
+				toList(map(filter(classDecl.getOwnedSetters(), s -> !isConsumedFromInterface(s)), s -> s.getName())));
+
+		SymbolTableEntry classSTE = findSymbolTableEntryForElement(classDecl, false);
+		SymbolTableEntryInternal $initFieldsFromInterfacesSTE = steFor_$initFieldsFromInterfaces();
+
+		return List.of(_ExprStmnt(
+				_CallExpr(
+						_IdentRef($initFieldsFromInterfacesSTE),
+						_ThisLiteral(),
+						_IdentRef(classSTE),
+						(specObjSTE != null) ? _IdentRef(specObjSTE)
+								: undefinedRef(),
+						_ObjLit(
+								Iterables.toArray(map(ownedInstanceDataFieldsSupressMixin,
+										str -> _PropertyNameValuePair(str, _TRUE())), PropertyNameValuePair.class)))));
+	}
+
+	// ################################################################################################################
+	// UTILITY STUFF
+
+	private int getSuperCallIndex(N4MethodDeclaration ownedCtor) {
+		if (ownedCtor != null && ownedCtor.getBody() != null && ownedCtor.getBody().getStatements() != null) {
+			EList stmnts = ownedCtor.getBody().getStatements();
+
+			for (int i = 0; i < stmnts.size(); i++) {
+				Statement stmnt = stmnts.get(i);
+				if (stmnt instanceof ExpressionStatement) {
+					Expression expr = ((ExpressionStatement) stmnt).getExpression();
+					if (expr instanceof ParameterizedCallExpression) {
+						if (((ParameterizedCallExpression) expr).getTarget() instanceof SuperLiteral) {
+							return i;
+						}
+					}
+				}
+			}
+		}
+		return -1;
+	}
+
+	private TMethod getNearestConstructorInHierarchy(N4ClassDeclaration classDecl) {
+		TClass tClass = getState().info.getOriginalDefinedType(classDecl);
+		return getNearestConstructorInHierarchy(tClass);
+	}
+
+	private TMethod getNearestConstructorInHierarchy(TClassifier clazz) {
+		TMethod ownedCtor = null;
+		if (clazz instanceof TClass) {
+			ownedCtor = clazz.getOwnedCtor();
+		}
+
+		if (ownedCtor != null) {
+			return ownedCtor;
+		} else {
+			Type superType = null;
+			if (clazz instanceof TClass) {
+				ParameterizedTypeRef superClassRef = ((TClass) clazz).getSuperClassRef();
+				superType = superClassRef == null ? null : superClassRef.getDeclaredType();
+			}
+
+			if (superType instanceof TClassifier) {
+				return getNearestConstructorInHierarchy((TClassifier) superType);
+			}
+		}
+		return null;
+	}
+
+	private boolean isConsumedFromInterface(N4MemberDeclaration memberDecl) {
+		return getState().info.isConsumedFromInterface(memberDecl);
+	}
+
+	// TODO GH-2153 use reusable utility method for computing actual accessibility
+	private boolean isPublic(N4MemberDeclaration memberDecl) {
+		// no need to bother with default accessibility, here, because 'public' is never the default
+		// (not ideal; would be better if the utility method handled default accessibility)
+		MemberAccessModifier accessModifier = ModifierUtils.convertToMemberAccessModifier(
+				memberDecl.getDeclaredModifiers(),
+				memberDecl.getAnnotations());
+		return accessModifier == MemberAccessModifier.PUBLIC;
+	}
+
+	private static  int insertAt(List list, int index, T element) {
+		list.add(index, element);
+		return index + 1;
+	}
+
+	private static  int insertAt(List list, int index, Collection elements) {
+		list.addAll(index, elements);
+		return index + elements.size();
+	}
+}
diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassConstructorAssistant.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassConstructorAssistant.xtend
deleted file mode 100644
index 2a5b95892f..0000000000
--- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassConstructorAssistant.xtend
+++ /dev/null
@@ -1,517 +0,0 @@
-/**
- * Copyright (c) 2016 NumberFour AG.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *   NumberFour AG - Initial API and implementation
- */
-package org.eclipse.n4js.transpiler.es.assistants
-
-import com.google.common.collect.ImmutableSet
-import com.google.inject.Inject
-import java.util.ArrayList
-import java.util.Collection
-import java.util.LinkedHashSet
-import java.util.List
-import java.util.Objects
-import java.util.Set
-import org.eclipse.n4js.AnnotationDefinition
-import org.eclipse.n4js.N4JSLanguageConstants
-import org.eclipse.n4js.n4JS.EqualityOperator
-import org.eclipse.n4js.n4JS.ExpressionStatement
-import org.eclipse.n4js.n4JS.FormalParameter
-import org.eclipse.n4js.n4JS.ModifierUtils
-import org.eclipse.n4js.n4JS.N4ClassDeclaration
-import org.eclipse.n4js.n4JS.N4FieldDeclaration
-import org.eclipse.n4js.n4JS.N4MemberDeclaration
-import org.eclipse.n4js.n4JS.N4MethodDeclaration
-import org.eclipse.n4js.n4JS.N4Modifier
-import org.eclipse.n4js.n4JS.N4SetterDeclaration
-import org.eclipse.n4js.n4JS.ParameterizedCallExpression
-import org.eclipse.n4js.n4JS.RelationalOperator
-import org.eclipse.n4js.n4JS.Statement
-import org.eclipse.n4js.n4JS.SuperLiteral
-import org.eclipse.n4js.n4JS.VariableStatementKeyword
-import org.eclipse.n4js.transpiler.TransformationAssistant
-import org.eclipse.n4js.transpiler.assistants.TypeAssistant
-import org.eclipse.n4js.transpiler.im.SymbolTableEntry
-import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-import org.eclipse.n4js.ts.types.MemberAccessModifier
-import org.eclipse.n4js.ts.types.TClass
-import org.eclipse.n4js.ts.types.TClassifier
-import org.eclipse.n4js.ts.types.TInterface
-import org.eclipse.n4js.ts.types.TMethod
-import org.eclipse.n4js.types.utils.TypeUtils
-
-import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.*
-
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.*
-
-/**
- * Modify or create the constructor of a class declaration.
- */
-class ClassConstructorAssistant extends TransformationAssistant {
-
-	@Inject private TypeAssistant typeAssistant;
-	@Inject private ClassifierAssistant classifierAssistant;
-
-	private static final class SpecInfo {
-		/**
-		 * The @Spec-fpar in the intermediate model. Never null.
-		 */
-		public final FormalParameter fpar;
-		/**
-		 * Names of properties in the with-clause of the @Spec-fpar's type. For example, as in:
-		 * 
-		 * constructor(@Spec specObj: ~i~this with { addProp: string }) { ... }
-		 * .
-		 */
-		public final ImmutableSet additionalProps;
-
-		new(FormalParameter fpar, Iterable additionalProps) {
-			this.fpar = Objects.requireNonNull(fpar);
-			this.additionalProps = ImmutableSet.copyOf(additionalProps);
-		}
-	}
-
-	/**
-	 * Amend the constructor of the given class with implicit functionality (e.g. initialization of instance fields).
-	 * Will create an implicit constructor declaration iff no constructor is defined in the N4JS source code AND an
-	 * implicit constructor is actually required.
-	 */
-	def public void amendConstructor(N4ClassDeclaration classDecl, SymbolTableEntry classSTE, SymbolTableEntryOriginal superClassSTE,
-		LinkedHashSet fieldsRequiringExplicitDefinition) {
-
-		val explicitCtorDecl = classDecl.ownedCtor; // the constructor defined in the N4JS source code or 'null' if none was defined
-		val ctorDecl = explicitCtorDecl ?: _N4MethodDecl(N4JSLanguageConstants.CONSTRUCTOR);
-
-		// amend formal parameters
-		val specInfo = amendFormalParametersOfConstructor(classDecl, ctorDecl, explicitCtorDecl);
-
-		// amend body
-		val isNonTrivial = amendBodyOfConstructor(classDecl, classSTE, superClassSTE,
-			ctorDecl, explicitCtorDecl, specInfo, fieldsRequiringExplicitDefinition);
-
-		// add constructor to classDecl (if necessary)
-		if (ctorDecl.eContainer === null && isNonTrivial) {
-			classDecl.ownedMembersRaw.add(0, ctorDecl);
-		}
-	}
-
-	def private SpecInfo amendFormalParametersOfConstructor(N4ClassDeclaration classDecl, N4MethodDeclaration ctorDecl, N4MethodDeclaration explicitCtorDecl) {
-		var specFpar = null as FormalParameter;
-		var specFparTypeRef = null as TypeRef;
-		
-		val hasExplicitCtor = explicitCtorDecl !== null;
-		if (hasExplicitCtor) {
-			// explicitly defined constructor
-			// --> nothing to be changed (use fpars from N4JS source code)
-
-			specFpar = ctorDecl.fpars.filter[AnnotationDefinition.SPEC.hasAnnotation(it)].head;
-			if (specFpar !== null) {
-				val typeRefNode = specFpar.declaredTypeRefNode;
-				if (typeRefNode !== null) {
-					specFparTypeRef = state.info.getOriginalProcessedTypeRef(typeRefNode);
-				}
-			}
-		} else {
-			// implicit constructor
-			// --> create fpars using fpars of nearest constructor in hierarchy as template
-
-			val templateCtor = getNearestConstructorInHierarchy(classDecl);
-			if (templateCtor !== null) {
-				for (templateFpar : templateCtor.fpars) {
-					val isSpecFpar = AnnotationDefinition.SPEC.hasAnnotation(templateFpar);
-					val newFpar = _Fpar(templateFpar.name, templateFpar.variadic, isSpecFpar);
-					ctorDecl.fpars += newFpar;
-					
-					if (isSpecFpar && specFpar === null) {
-						specFpar = newFpar;
-						specFparTypeRef = TypeUtils.copy(templateFpar.typeRef);
-					}
-				}
-			}
-		}
-
-		if (specFpar !== null) {
-			return new SpecInfo(specFpar, specFparTypeRef?.structuralMembers?.map[name] ?: #[]);
-		}
-		return null;
-	}
-
-	/**
-	 * Returns true iff the constructor is non-trivial, i.e. non-empty and containing more
-	 * than just the default super call.
-	 */
-	def private boolean amendBodyOfConstructor(N4ClassDeclaration classDecl, SymbolTableEntry classSTE, SymbolTableEntryOriginal superClassSTE,
-		N4MethodDeclaration ctorDecl, N4MethodDeclaration explicitCtorDecl, SpecInfo specInfo,
-		LinkedHashSet fieldsRequiringExplicitDefinition) {
-
-		val hasExplicitCtor = explicitCtorDecl !== null;
-		val body = ctorDecl.body;
-
-		val isDirectSubclassOfError = superClassSTE?.originalTarget===state.G.errorType;
-		val superCallIndex = if(explicitCtorDecl?.body!==null) explicitCtorDecl.superCallIndex else -1;
-		val hasExplicitSuperCall = superCallIndex>=0;
-		val explicitSuperCall = if(hasExplicitSuperCall) explicitCtorDecl.body.statements.get(superCallIndex);
-		var defaultSuperCall = null as ExpressionStatement;
-
-		var idx = if(hasExplicitSuperCall) superCallIndex + 1 else 0;
-
-		// add/replace/modify super call
-		if(hasExplicitSuperCall) {
-			// keep existing, explicit super call unchanged
-		} else {
-			// no explicitCtorDecl OR no body OR no explicit super call (and no direct subclass of Error)
-			// --> add default super call (if required)
-			if(superClassSTE!==null) {
-				val fparsOfSuperCtor = if (hasExplicitCtor) {
-					// explicit ctor without an explicit super call: only allowed if the super constructor
-					// does not have any arguments, so we can simply assume empty fpars here, without actually
-					// looking up the super constructor with #getNearestConstructorInHierarchy():
-					#[]
-				} else {
-					// no explicit ctor: the already created implicit constructor in 'ctorDecl' has the
-					// same fpars as the super constructor, so we can use those as a template:
-					ctorDecl.fpars
-				};
-				defaultSuperCall = createDefaultSuperCall(classDecl, superClassSTE, fparsOfSuperCtor);
-				idx = body.statements.insertAt(idx, defaultSuperCall);
-			}
-		}
-		if(isDirectSubclassOfError) {
-			// special case: add oddities for sub-classing Error
-			idx = body.statements.insertAt(idx,
-				createSubclassingErrorOddities(classDecl, ctorDecl.fpars, explicitSuperCall));
-		}
-
-		// if we are in a spec-constructor: prepare a local variable for the spec-object and
-		// ensure it is never 'undefined' or 'null'
-		var specObjSTE = null as SymbolTableEntry;
-		if (specInfo !== null) {
-			// let $specObj = specFpar || {};
-			val specFparSTE = findSymbolTableEntryForElement(specInfo.fpar, true);
-			val specObjVarDecl = _VariableDeclaration("$specObj", _OR(_IdentRef(specFparSTE), _ObjLit));
-			idx = body.statements.insertAt(idx, _VariableStatement(VariableStatementKeyword.CONST, specObjVarDecl));
-
-			specObjSTE = findSymbolTableEntryForElement(specObjVarDecl, true);
-		}
-
-		// add explicit definitions of instance fields (only for fields that actually require this)
-		idx = body.statements.insertAt(idx, classifierAssistant.createExplicitFieldDefinitions(classSTE, false, fieldsRequiringExplicitDefinition));
-
-		// add initialization code for instance fields
-		idx = body.statements.insertAt(idx, createInstanceFieldInitCode(classDecl, specInfo, specObjSTE, fieldsRequiringExplicitDefinition));
-
-		// add delegation to field initialization functions of all directly implemented interfaces
-		idx = body.statements.insertAt(idx, createDelegationToFieldInitOfImplementedInterfaces(classDecl, specObjSTE));
-
-		// check if constructor is non-trivial
-		val ctorDeclStmnts = ctorDecl.body.statements;
-		val bodyContainsOnlyDefaultSuperCall = defaultSuperCall !== null
-			&& ctorDeclStmnts.size === 1
-			&& ctorDeclStmnts.head === defaultSuperCall;
-		val isNonTrivialCtor = !ctorDeclStmnts.empty && !bodyContainsOnlyDefaultSuperCall;
-
-		return isNonTrivialCtor;
-	}
-
-	def private Statement[] createInstanceFieldInitCode(N4ClassDeclaration classDecl, SpecInfo specInfo, SymbolTableEntry specObjSTE, Set fieldsWithExplicitDefinition) {
-		val allFields = classDecl.ownedFields.filter[
-			!isStatic
-			&& !isConsumedFromInterface
-			&& !builtInOrProvidedByRuntime(state.info.getOriginalDefinedMember(it))
-		].toList;
-		if(specInfo!==null) {
-			// we have a spec-parameter -> we are in a spec-style constructor
-			val result = newArrayList;
-
-			// step #1: initialize all fields either with data from the specFpar OR or their initializer expression
-			val currFields = newArrayList;
-			var currFieldsAreSpecced = false;
-			for(field : allFields) {
-				val isSpecced = isPublic(field) || specInfo.additionalProps.contains(field.name);
-				if (isSpecced === currFieldsAreSpecced) {
-					currFields += field;
-				} else {
-					result += createFieldInitCodeForSeveralFields(currFields, currFieldsAreSpecced, specObjSTE);
-					currFields.clear();
-					currFields += field;
-					currFieldsAreSpecced = isSpecced;
-				}
-			}
-			result += createFieldInitCodeForSeveralFields(currFields, currFieldsAreSpecced, specObjSTE);
-
-			// step #2: invoke setters with data from specFpar
-			// (note: in case of undefined specFpar at runtime, setters should not be invoked, so we wrap in if statement)
-			val speccedSetters = classDecl.ownedSetters.filter[!isStatic && declaredModifiers.contains(N4Modifier.PUBLIC)].toList;
-			result += speccedSetters.map[createFieldInitCodeForSingleSpeccedSetter(specObjSTE)];
-
-			return result;
-		} else {
-			// simple: just initialize fields with data from their initializer expression
-			return allFields
-				.filter[!(expression===null && fieldsWithExplicitDefinition.contains(it))]
-				.map[createFieldInitCodeForSingleField];
-		}
-	}
-
-	def private Statement[] createFieldInitCodeForSeveralFields(Collection fieldDecls, boolean fieldsAreSpecced, SymbolTableEntry specObjSTE) {
-		if (fieldDecls.empty) {
-			return #[];
-		}
-		if (!fieldsAreSpecced) {
-			return fieldDecls.map[createFieldInitCodeForSingleField];
-		}
-		if (fieldDecls.size === 1) {
-			return #[ fieldDecls.head.createFieldInitCodeForSingleSpeccedField(specObjSTE) ];
-		}
-		return #[ fieldDecls.createFieldInitCodeForSeveralSpeccedFields(specObjSTE) ];
-	}
-
-	def private Statement createFieldInitCodeForSingleField(N4FieldDeclaration fieldDecl) {
-		// here we create:
-		//
-		//     this.fieldName = ;
-		// or
-		//     this.fieldName = undefined;
-		//
-		// NOTE: we set the field to 'undefined' even in the second case, because ...
-		// 1) it makes a difference when #hasOwnProperty(), etc. is used after the constructor returns,
-		// 2) for consistency with the method #createFieldInitCodeForSeveralSpeccedFields(), because with
-		//    destructuring as used by that method, the property will always be assigned (even if the
-		//    value is 'undefined' and there is no default).
-		//
-		val fieldSTE = findSymbolTableEntryForElement(fieldDecl, true);
-		return _ExprStmnt(_AssignmentExpr(
-			_PropertyAccessExpr(_ThisLiteral, fieldSTE),
-			if(fieldDecl.expression!==null) {
-				fieldDecl.expression // reusing the expression here
-			} else {
-				undefinedRef()
-			}
-		));
-	}
-
-	def private Statement createFieldInitCodeForSeveralSpeccedFields(Iterable fieldDecls, SymbolTableEntry specObjSTE) {
-		// here we create:
-		//
-		//     ({
-		//         fieldName: this.fieldName = ,
-		//         ...
-		//     } = spec);
-		//
-		return _ExprStmnt(
-			_Parenthesis(
-				_AssignmentExpr(
-					_ObjLit(
-						fieldDecls.map[fieldDecl|
-							val fieldSTE = findSymbolTableEntryForElement(fieldDecl, true);
-							val thisFieldName = _PropertyAccessExpr(_ThisLiteral, fieldSTE);
-							return fieldDecl.name -> if (fieldDecl.hasNonTrivialInitExpression) {
-								_AssignmentExpr(thisFieldName, fieldDecl.expression) // reusing the expression here
-							} else {
-								thisFieldName
-							};
-						]
-					),
-					_IdentRef(specObjSTE)
-				)
-			)
-		);
-	}
-
-	def private Statement createFieldInitCodeForSingleSpeccedField(N4FieldDeclaration fieldDecl, SymbolTableEntry specObjSTE) {
-		val fieldSTE = findSymbolTableEntryForElement(fieldDecl, true);
-		if (fieldDecl.hasNonTrivialInitExpression) {
-			// here we create:
-			//
-			//     this.fieldName = spec.fieldName === undefined ?  : spec.fieldName;
-			//
-			// NOTE: don't use something like "'fieldName' in spec" as the condition above, because that would
-			// not have the same behavior as destructuring in method #createFieldInitCodeForSeveralSpeccedFields()
-			// in case the property is present but has value 'undefined'!
-			//
-			return _ExprStmnt(_AssignmentExpr(
-				_PropertyAccessExpr(_ThisLiteral, fieldSTE),
-				// ? :
-				_ConditionalExpr(
-					// spec.fieldName === undefined
-					_EqualityExpr(_PropertyAccessExpr(specObjSTE, fieldSTE), EqualityOperator.SAME, undefinedRef()),
-					// 
-					if(fieldDecl.expression!==null) {
-						copy(fieldDecl.expression) // need to copy expression here, because it will be used again!
-					} else {
-						undefinedRef()
-					},
-					// spec.fieldName
-					_PropertyAccessExpr(specObjSTE, fieldSTE)
-				)
-			));
-		} else {
-			// we have a trivial init-expression ...
-
-			// here we create:
-			//
-			//     this.fieldName = spec.fieldName;
-			//
-			return _ExprStmnt(_AssignmentExpr(
-				_PropertyAccessExpr(_ThisLiteral, fieldSTE),
-				_PropertyAccessExpr(specObjSTE, fieldSTE)
-			));
-		}
-	}
-
-	def private Statement createFieldInitCodeForSingleSpeccedSetter(N4SetterDeclaration setterDecl, SymbolTableEntry specObjSTE) {
-		// here we create:
-		//
-		// 	if ('fieldName' in spec) {
-		//		this.fieldName = spec.fieldName;
-		// 	}
-		//
-
-		val setterSTE = findSymbolTableEntryForElement(setterDecl, true);
-		return _IfStmnt(
-			_RelationalExpr(
-				_StringLiteralForSTE(setterSTE), RelationalOperator.IN, _IdentRef(specObjSTE)
-			),
-			_ExprStmnt(
-				_AssignmentExpr(
-					_PropertyAccessExpr(_ThisLiteral, setterSTE),
-					_PropertyAccessExpr(specObjSTE, setterSTE)
-		)));
-	}
-
-	// NOTE: compare this to the super calls generated from SuperLiterals in SuperLiteralTransformation
-	def private ExpressionStatement createDefaultSuperCall(N4ClassDeclaration classDecl, SymbolTableEntry superClassSTE, FormalParameter[] fpars) {
-		val variadicCase = !fpars.empty && fpars.last.isVariadic;
-		val argsIter = fpars.map[findSymbolTableEntryForElement(it, true)].map[_Argument(_IdentRef(it))];
-		val args = new ArrayList(argsIter); // WARNING: .toList won't work!
-		if (variadicCase) {
-			args.last.spread = true;
-		}
-		return _ExprStmnt(_CallExpr() => [
-			target = _SuperLiteral();
-			arguments += args;
-		]);
-	}
-
-	/** To be inserted where the explicit or implicit super call would be located, normally. */
-	def private Statement[] createSubclassingErrorOddities(N4ClassDeclaration classDecl, FormalParameter[] fpars, Statement explicitSuperCall) {
-		return #[ _ExprStmnt(_Snippet('''
-			this.name = this.constructor.n4type.name;
-			if (Error.captureStackTrace) {
-			    Error.captureStackTrace(this, this.name);
-			}
-		''')) ];
-	}
-
-	def private Statement[] createDelegationToFieldInitOfImplementedInterfaces(N4ClassDeclaration classDecl, SymbolTableEntry /*nullable*/ specObjSTE ) {
-
-		val implementedIfcSTEs = typeAssistant.getSuperInterfacesSTEs(classDecl).filter [
-			// regarding the cast to TInterface: see preconditions of ClassDeclarationTransformation
-			// regarding the entire line: generate $fieldInit call only if the interface is neither built-in nor provided by runtime nor shape
-			!builtInOrProvidedByRuntimeOrShape(originalTarget as TInterface);
-		];
-		if (implementedIfcSTEs.empty) {
-			return #[];
-		}
-		
-
-		val LinkedHashSet ownedInstanceDataFieldsSupressMixin = newLinkedHashSet
-		ownedInstanceDataFieldsSupressMixin.addAll(classDecl.ownedGetters.filter[!isConsumedFromInterface].map[name])
-		ownedInstanceDataFieldsSupressMixin.addAll(classDecl.ownedSetters.filter[!isConsumedFromInterface].map[name])
-
-		val classSTE = findSymbolTableEntryForElement(classDecl, false);
-		val $initFieldsFromInterfacesSTE = steFor_$initFieldsFromInterfaces;
-
-		return #[ _ExprStmnt(
-			_CallExpr(
-				_IdentRef($initFieldsFromInterfacesSTE),
-				_ThisLiteral,
-				_IdentRef(classSTE),
-				if (specObjSTE !== null) {
-					_IdentRef(specObjSTE)
-				} else {
-					undefinedRef()
-				},
-				_ObjLit(
-					ownedInstanceDataFieldsSupressMixin.map[
-						_PropertyNameValuePair(it, _TRUE)
-					]
-				)
-			)
-		)];
-	}
-
-	// ################################################################################################################
-	// UTILITY STUFF
-
-	def private int getSuperCallIndex(N4MethodDeclaration ownedCtor) {
-		val stmnts = ownedCtor?.body?.statements;
-		if(stmnts!==null && !stmnts.empty) {
-			for(i : 0.. int insertAt(List list, int index, T element) {
-		list.add(index, element);
-		return index + 1;
-	}
-
-	def private static  int insertAt(List list, int index, Collection elements) {
-		list.addAll(index, elements);
-		return index + elements.size();
-	}
-}
diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassifierAssistant.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassifierAssistant.java
new file mode 100644
index 0000000000..9ac9a2eba8
--- /dev/null
+++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassifierAssistant.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) 2020 NumberFour AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   NumberFour AG - Initial API and implementation
+ */
+package org.eclipse.n4js.transpiler.es.assistants;
+
+import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._AssignmentExpr;
+import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr;
+import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ExprStmnt;
+import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef;
+import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr;
+import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral;
+import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ThisLiteral;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.n4js.AnnotationDefinition;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.ExpressionStatement;
+import org.eclipse.n4js.n4JS.N4ClassDeclaration;
+import org.eclipse.n4js.n4JS.N4ClassifierDeclaration;
+import org.eclipse.n4js.n4JS.N4FieldDeclaration;
+import org.eclipse.n4js.n4JS.N4MemberDeclaration;
+import org.eclipse.n4js.n4JS.Statement;
+import org.eclipse.n4js.n4JS.StringLiteral;
+import org.eclipse.n4js.transpiler.TransformationAssistant;
+import org.eclipse.n4js.transpiler.im.SymbolTableEntry;
+import org.eclipse.n4js.ts.types.TClass;
+import org.eclipse.n4js.ts.types.TField;
+import org.eclipse.xtext.xbase.lib.IterableExtensions;
+import org.eclipse.xtext.xbase.lib.Pair;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Helper methods for transformations of class and interface declarations.
+ */
+public class ClassifierAssistant extends TransformationAssistant {
+
+	/**
+	 * Returns a set/list of data fields that require an explicit property definition. Actual creation of those explicit
+	 * property definitions is done with method
+	 * {@link #createExplicitFieldDefinitions(SymbolTableEntry, boolean, LinkedHashSet)}.
+	 *
+	 * 

Background

+ * + * Data fields that override an accessor require an explicit property definition along the lines of + * + *
+	 * Object.defineProperty(this, "myField", {writable: true, ...});
+	 * 
+ * + * A simple initialization of the form this.myField = undefined; would throw an exception at runtime + * (in case of overriding only a getter) or would simply invoke the setter (in case of overriding a setter or an + * accessor pair). + *

+ * This applies to both instance and static fields. + */ + public LinkedHashSet findFieldsRequiringExplicitDefinition(N4ClassDeclaration classDecl) { + TClass tClass = getState().info.getOriginalDefinedType(classDecl); + Set> fieldsOverridingAnAccessor = null; + if (tClass != null) { + fieldsOverridingAnAccessor = IterableExtensions.toSet(IterableExtensions.map( + getState().memberCollector.computeOwnedFieldsOverridingAnAccessor(tClass, true), + f -> Pair.of(f.isStatic(), f.getName()))); + } + + LinkedHashSet result = new LinkedHashSet<>(); + for (N4MemberDeclaration member : classDecl.getOwnedMembers()) { + if (member instanceof N4FieldDeclaration + && AnnotationDefinition.OVERRIDE.hasAnnotation(member) + && (fieldsOverridingAnAccessor == null + || fieldsOverridingAnAccessor.contains(Pair.of(member.isStatic(), member.getName())))) { + result.add((N4FieldDeclaration) member); + } + } + + return result; + } + + /** + * Creates explicit property definitions for the fields identified by method + * {@link #findFieldsRequiringExplicitDefinition(N4ClassDeclaration)}. + */ + public List createExplicitFieldDefinitions(SymbolTableEntry steClass, boolean staticCase, + LinkedHashSet fieldsRequiringExplicitDefinition) { + + // Creates either + // $defineFields(D, "staticFieldName1", "staticFieldName2", ...); + // or + // $defineFields(this, "instanceFieldName1", "instanceFieldName2", ...); + List names = new ArrayList<>(); + for (N4FieldDeclaration fd : fieldsRequiringExplicitDefinition) { + if (fd.isStatic() == staticCase) { + names.add(_StringLiteral(fd.getName())); + } + } + + if (names.isEmpty()) { + return Collections.emptyList(); + } + + List args = new ArrayList<>(); + args.add((staticCase) ? __NSSafe_IdentRef(steClass) : _ThisLiteral()); + args.addAll(names); + + ExpressionStatement result = _ExprStmnt( + _CallExpr( + _IdentRef(steFor_$defineFields()), + Iterables.toArray(args, Expression.class))); + return List.of(result); + } + + /** + * Creates a new list of statements to initialize the static fields of the given classifier declaration. + *

+ * Clients of this method may modify the returned list. + */ + public List createStaticFieldInitializations(N4ClassifierDeclaration classifierDecl, + SymbolTableEntry classifierSTE, + Set fieldsWithExplicitDefinition) { + + List result = new ArrayList<>(); + for (N4MemberDeclaration member : classifierDecl.getOwnedMembers()) { + if (member.isStatic() && member instanceof N4FieldDeclaration + && !(((N4FieldDeclaration) member).getExpression() == null + && fieldsWithExplicitDefinition.contains(member))) { + + Statement sic = createStaticInitialiserCode((N4FieldDeclaration) member, classifierSTE); + result.add(sic); + } + } + + return result; + } + + private Statement createStaticInitialiserCode(N4FieldDeclaration fieldDecl, SymbolTableEntry classSTE) { + TField tField = (TField) getState().info.getOriginalDefinedMember(fieldDecl); + SymbolTableEntry fieldSTE = (tField != null) ? getSymbolTableEntryOriginal(tField, true) + : findSymbolTableEntryForElement(fieldDecl, true); + + ExpressionStatement exprStmnt = _ExprStmnt( + _AssignmentExpr( + _PropertyAccessExpr(__NSSafe_IdentRef(classSTE), fieldSTE), + fieldDecl.getExpression() == null ? undefinedRef() : fieldDecl.getExpression()// reuse existing + // expression + // (if present) + )); + getState().tracer.copyTrace(fieldDecl, exprStmnt); + + return exprStmnt; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassifierAssistant.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassifierAssistant.xtend deleted file mode 100644 index c5f58728dd..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ClassifierAssistant.xtend +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2020 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.assistants - -import java.util.LinkedHashSet -import java.util.List -import java.util.Set -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.transpiler.TransformationAssistant -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.ts.types.TField - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Helper methods for transformations of class and interface declarations. - */ -class ClassifierAssistant extends TransformationAssistant { - - /** - * Returns a set/list of data fields that require an explicit property definition. Actual creation of those explicit - * property definitions is done with method {@link #createExplicitFieldDefinitions(SymbolTableEntry, boolean, LinkedHashSet)}. - * - *

Background

- * - * Data fields that override an accessor require an explicit property definition along the lines of - *
-	 * Object.defineProperty(this, "myField", {writable: true, ...});
-	 * 
- * A simple initialization of the form this.myField = undefined; would throw an exception at runtime (in case of - * overriding only a getter) or would simply invoke the setter (in case of overriding a setter or an accessor pair). - *

- * This applies to both instance and static fields. - */ - def public LinkedHashSet findFieldsRequiringExplicitDefinition(N4ClassDeclaration classDecl) { - val tClass = state.info.getOriginalDefinedType(classDecl); - val fieldsOverridingAnAccessor = if (tClass !== null) { - state.memberCollector.computeOwnedFieldsOverridingAnAccessor(tClass, true) - .map[static -> name] - .toSet; - }; - val result = newLinkedHashSet; - result += classDecl.ownedMembers - .filter[AnnotationDefinition.OVERRIDE.hasAnnotation(it)] - .filter(N4FieldDeclaration) - .filter[fieldsOverridingAnAccessor === null || fieldsOverridingAnAccessor.contains(static -> name)]; - return result; - } - - /** - * Creates explicit property definitions for the fields identified by method - * {@link #findFieldsRequiringExplicitDefinition(N4ClassDeclaration)}. - */ - def public List createExplicitFieldDefinitions(SymbolTableEntry steClass, boolean staticCase, - LinkedHashSet fieldsRequiringExplicitDefinition) { - - // Creates either - // $defineFields(D, "staticFieldName1", "staticFieldName2", ...); - // or - // $defineFields(this, "instanceFieldName1", "instanceFieldName2", ...); - val names = fieldsRequiringExplicitDefinition.filter[static === staticCase].map[name].map[_StringLiteral(it)].toList; - if (names.empty) { - return #[]; - } - val result = _ExprStmnt( - _CallExpr( - _IdentRef(steFor_$defineFields), - #[ - if (staticCase) { - __NSSafe_IdentRef(steClass) - } else { - _ThisLiteral - } - ] + names - ) - ); - return #[ result ]; - } - - /** - * Creates a new list of statements to initialize the static fields of the given classifier declaration. - *

- * Clients of this method may modify the returned list. - */ - def public List createStaticFieldInitializations(N4ClassifierDeclaration classifierDecl, SymbolTableEntry classifierSTE, - Set fieldsWithExplicitDefinition) { - - return classifierDecl.ownedMembers - .filter[isStatic] - .filter(N4FieldDeclaration) - .filter[!(expression===null && fieldsWithExplicitDefinition.contains(it))] - .map[createStaticInitialiserCode(classifierSTE)] - .filterNull - .toList; - } - - def private Statement createStaticInitialiserCode(N4FieldDeclaration fieldDecl, SymbolTableEntry classSTE) { - val tField = state.info.getOriginalDefinedMember(fieldDecl) as TField; - val fieldSTE = if (tField !== null) getSymbolTableEntryOriginal(tField, true) else findSymbolTableEntryForElement(fieldDecl, true); - - val exprStmnt = _ExprStmnt( - _AssignmentExpr( - _PropertyAccessExpr(__NSSafe_IdentRef(classSTE), fieldSTE), - fieldDecl.expression ?: undefinedRef // reuse existing expression (if present) - ) - ); - state.tracer.copyTrace(fieldDecl, exprStmnt); - - return exprStmnt; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DelegationAssistant.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DelegationAssistant.java new file mode 100644 index 0000000000..bded2e2cf1 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DelegationAssistant.java @@ -0,0 +1,435 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.assistants; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Block; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ExprStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Fpar; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IndexAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._LiteralOrComputedPropertyName; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4GetterDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4MethodDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4SetterDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteralForSTE; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ThisLiteral; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.functionType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.objectType; + +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.N4Modifier; +import org.eclipse.n4js.n4JS.N4SetterDeclaration; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.transpiler.TransformationAssistant; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.im.DelegatingGetterDeclaration; +import org.eclipse.n4js.transpiler.im.DelegatingMember; +import org.eclipse.n4js.transpiler.im.DelegatingMethodDeclaration; +import org.eclipse.n4js.transpiler.im.DelegatingSetterDeclaration; +import org.eclipse.n4js.transpiler.im.ImFactory; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.types.ContainerType; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.ts.types.util.SuperInterfacesIterable; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.RecursionGuard; + +import com.google.common.collect.Lists; +import com.google.inject.Inject; + +/** + * This assistant provides helper methods to create members that delegate to some other target member (see + * {@link DelegationAssistant#createDelegatingMember(TClassifier, TMember) createDelegatingMember(TClassifier, + * TMember)}) and to create the Javascript output code to actually implement these delegating members in the transpiler + * output (see {@link DelegationAssistant#createOrdinaryMemberForDelegatingMember(DelegatingMember) }). + *

+ * Usually inherited members in a classifier do not require any special code, because they will be accessed via the + * native prototype chain mechanism of Javascript. However, there are special cases when some special code has to be + * generated for an inherited member in order to properly access that inherited member, because it is not available via + * the ordinary prototype chain. + */ +public class DelegationAssistant extends TransformationAssistant { + + @Inject + private TypeAssistant typeAssistant; + + /** + * Creates a new delegating member intended to be inserted into classifier origin in order to delegate + * from origin to the given member target. The target member is assumed to be an inherited + * or consumed member of classifier origin, i.e. it is assumed to be located in one of the ancestor + * classes of origin or one of its directly or indirectly implemented interfaces (but not in origin + * itself!). + *

+ * Throws exceptions in case of invalid arguments or an invalid internal getState(), see implementation for details. + */ + public DelegatingMember createDelegatingMember(TClassifier origin, TMember target) { + if (target.getContainingType() == origin) { + throw new IllegalArgumentException("no point in delegating to an owned member"); + } + DelegatingMember result = null; + if (target instanceof TGetter) { + result = ImFactory.eINSTANCE.createDelegatingGetterDeclaration(); + ((DelegatingGetterDeclaration) result).setDeclaredName(_LiteralOrComputedPropertyName(target.getName())); + } + if (target instanceof TSetter) { + result = ImFactory.eINSTANCE.createDelegatingSetterDeclaration(); + ((DelegatingSetterDeclaration) result).setDeclaredName(_LiteralOrComputedPropertyName(target.getName())); + } + if (target instanceof TMethod) { + result = ImFactory.eINSTANCE.createDelegatingMethodDeclaration(); + ((DelegatingMethodDeclaration) result).setDeclaredName(_LiteralOrComputedPropertyName(target.getName())); + } + if (target instanceof TField || result == null) { + throw new IllegalArgumentException("delegation to fields not supported yet"); + } + + // set simple properties + result.setDelegationTarget(getSymbolTableEntryOriginal(target, true)); + if (target.isStatic()) { + result.getDeclaredModifiers().add(N4Modifier.STATIC); + } + // set delegationBaseType and delegationSuperClassSteps + if (origin instanceof TInterface) { + // we are in an interface and the target must also be in an interface + if (!(target.eContainer() instanceof TInterface)) { + throw new IllegalArgumentException("cannot delegate from an interface to member of a class"); + } + ContainerType tSuper = getDirectSuperTypeBequestingMember(origin, target); + // we know the STE of tSuper must already exist, because it is a direct super type and must therefore be + // referenced in the declaration of classifier origin + result.setDelegationBaseType(getSymbolTableEntryOriginal(tSuper, true)); + result.setDelegationSuperClassSteps(0); + } else if (origin instanceof TClass) { + // we are in a class and the target may either be in a class or an interface + TClass tAncestor = getAncestorClassBequestingMember((TClass) origin, target); + if (tAncestor != origin) { + // we are inheriting 'target' from one of our ancestor classes -> delegate to that ancestor class + // (note: this includes the case the 'target' is contained in an interface and one of our ancestor + // classes implements that interface) + TClass tSuper = (TClass) ((TClass) origin).getSuperClassRef().getDeclaredType(); + result.setDelegationBaseType(getSymbolTableEntryOriginal(tSuper, true)); + result.setDelegationSuperClassSteps(getDistanceToAncestorClass((TClass) origin, tAncestor) - 1); + } else if (tAncestor != null) { + // we are consuming 'target' from one of our directly implemented interfaces or its extended interfaces + // (similar as case "origin instanceof TInterface" above) + ContainerType tSuper = getDirectSuperTypeBequestingMember(origin, target); + result.setDelegationBaseType(getSymbolTableEntryOriginal(tSuper, true)); + result.setDelegationSuperClassSteps(0); + } else { + throw new IllegalStateException("cannot find target (probably not an inherited member)"); + } + } else { + throw new IllegalArgumentException("unsupported subtype of TClassifier: " + origin.eClass().getName()); + } + // set some properties to let this delegating member behave more like an ordinary member of the same type + result.setDelegationTargetIsAbstract(target.isAbstract()); + if (!target.isAbstract()) { + ((FunctionOrFieldAccessor) result).setBody(_Block()); + } + return result; + } + + /** + * Convenience method for replacing each delegating member in the given declaration by an ordinary member created + * with method {@link #createOrdinaryMemberForDelegatingMember(DelegatingMember)}. Will modify the given classifier + * declaration. + */ + public void replaceDelegatingMembersByOrdinaryMembers(N4ClassifierDeclaration classifierDecl) { + for (N4MemberDeclaration currMember : Lists.newArrayList(classifierDecl.getOwnedMembersRaw())) { + if (currMember instanceof DelegatingMember) { + N4MemberDeclaration resolvedDelegatingMember = createOrdinaryMemberForDelegatingMember( + (DelegatingMember) currMember); + replace(currMember, resolvedDelegatingMember); + } + } + } + + /** Creates a {@link N4MemberDeclaration} for the given delegator */ + public N4MemberDeclaration createOrdinaryMemberForDelegatingMember(DelegatingMember delegator) { + String targetNameStr = delegator.getDelegationTarget().getName(); + LiteralOrComputedPropertyName targetName; + if (targetNameStr != null && targetNameStr.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX)) { + targetName = _LiteralOrComputedPropertyName(typeAssistant.getMemberNameAsSymbol(targetNameStr), + targetNameStr); + } else { + targetName = _LiteralOrComputedPropertyName(targetNameStr); + } + + Block body = createBodyForDelegatingMember(delegator); + if (delegator instanceof DelegatingGetterDeclaration) { + return _N4GetterDecl(targetName, body); + } + if (delegator instanceof DelegatingSetterDeclaration) { + return _N4SetterDecl(targetName, _Fpar("value"), body); + } + if (delegator instanceof DelegatingMethodDeclaration) { + return _N4MethodDecl(targetName, body); + } + return null; + } + + private Block createBodyForDelegatingMember(DelegatingMember delegator) { + SymbolTableEntryOriginal baseSTE = delegator.getDelegationBaseType(); + boolean baseIsInterface = baseSTE == null ? false : baseSTE.getOriginalTarget() instanceof TInterface; + + Expression targetAccess; + if (baseIsInterface) { + boolean targetIsStatic = delegator.isStatic(); + Expression objOfInterfaceOfTargetExpr = createAccessToInterfaceObject(baseSTE, targetIsStatic); + targetAccess = createAccessToMemberFunction(objOfInterfaceOfTargetExpr, !targetIsStatic, delegator); + } else { + Expression ctorOfClassOfTarget = createAccessToClassConstructor(baseSTE, + delegator.getDelegationSuperClassSteps()); + + // based on that constructor expression, now create an expression that evaluates to the member function of + // the target member: + targetAccess = createAccessToMemberFunction(ctorOfClassOfTarget, false, delegator); + } + + ParameterizedCallExpression callExpr = _CallExpr( + _PropertyAccessExpr( + targetAccess, + getSymbolTableEntryForMember(functionType(getState().G), "apply", false, false, true)), + _ThisLiteral(), + _IdentRef(steFor_arguments())); + + if (delegator instanceof DelegatingSetterDeclaration) { + return _Block( + _ExprStmnt(callExpr)); + } else { + return _Block( + _ReturnStmnt(callExpr)); + } + } + + /** + * Creates an expression that will evaluate to the constructor of the class denoted by the given symbol table entry + * or one of its super classes (depending on argument superClassSteps). + *

+ * For example, if classSTE denotes a class "C", then this will produce an expression like + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Cfor superClassSteps == 0
Object.getPrototypeOf(C)for superClassSteps == 1
Object.getPrototypeOf(Object.getPrototypeOf(C))for superClassSteps == 2
+ */ + private Expression createAccessToClassConstructor(SymbolTableEntry classSTE, int superClassSteps) { + TClassifier objectType = objectType(getState().G); + SymbolTableEntryOriginal objectSTE = getSymbolTableEntryOriginal(objectType, true); + Expression result = __NSSafe_IdentRef(classSTE); // this is the "C" in the above examples + // for each super-class-step wrap 'result' into "Object.getPrototypeOf(result)" + if (superClassSteps > 0) { + SymbolTableEntryOriginal getPrototypeOfSTE = getSymbolTableEntryForMember(objectType, "getPrototypeOf", + false, true, true); + for (int n = 0; n < superClassSteps; n++) { + result = _CallExpr( + _PropertyAccessExpr(objectSTE, getPrototypeOfSTE), + result); + } + } + return result; + } + + private Expression createAccessToInterfaceObject(SymbolTableEntry ifcSTE, boolean targetIsStatic) { + Expression result = __NSSafe_IdentRef(ifcSTE); + if (!targetIsStatic) { + result = _PropertyAccessExpr(result, steFor_$defaultMembers()); + } + return result; + } + + /** + * Same as {@link #createAccessToMemberDescriptor(Expression, boolean, N4MemberDeclaration)}, but will add a + * property access to the Javascript function representing the member, which is stored in the member definition (by + * appending ".get", ".set", or ".value" depending on the type of member). + *

+ * Since fields do not have such a function this will throw an exception if given member is a field. + */ + private Expression createAccessToMemberFunction(Expression protoOrCtorExpr, boolean exprIsProto, + N4MemberDeclaration member) { + if (member instanceof N4FieldDeclaration) { + throw new IllegalArgumentException("no member function available for fields"); + } + if (member instanceof N4MethodDeclaration) { + // for methods we can use a simple property access instead of Object.getOwnPropertyDescriptor() + String memberName = member.getName(); + boolean memberIsSymbol = memberName != null + && memberName.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX); + SymbolTableEntry memberSTE = findSymbolTableEntryForElement(member, true); + return (!memberIsSymbol) ? _PropertyAccessExpr(protoOrCtorExpr, memberSTE) + : _IndexAccessExpr(protoOrCtorExpr, typeAssistant.getMemberNameAsSymbol(memberName)); + } + // for other members (i.e. getters and setters) we need to retrieve the property descriptor: + Expression accessToMemberDefinition = createAccessToMemberDescriptor(protoOrCtorExpr, exprIsProto, member); + // append ".get", ".set", or ".value" depending on member type + ParameterizedPropertyAccessExpression_IM result = _PropertyAccessExpr(accessToMemberDefinition, + getPropertyDescriptorValueProperty(member)); + return result; + } + + /** + * Given an expression that will evaluate to a prototype or constructor, this method returns an expression that will + * evaluate to the property descriptor of a particular member. + * + * @param protoOrCtorExpr + * an expression that is expected to evaluate to a prototype or a constructor. + * @param exprIsProto + * tells whether argument protoOrCtorExpr will evaluate to a prototype or a constructor: if + * true, it will evaluate to a prototype, if false it will evaluate to a constructor. + * @param member + * the member. + */ + private Expression createAccessToMemberDescriptor(Expression protoOrCtorExpr, boolean exprIsProto, + N4MemberDeclaration member) { + String memberName = member.getName(); + boolean memberIsSymbol = memberName != null + && memberName.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX); + SymbolTableEntry memberSTE = findSymbolTableEntryForElement(member, true); + TClassifier objectType = objectType(getState().G); + SymbolTableEntryOriginal objectSTE = getSymbolTableEntryOriginal(objectType, true); + SymbolTableEntryOriginal getOwnPropertyDescriptorSTE = getSymbolTableEntryForMember(objectType, + "getOwnPropertyDescriptor", false, true, true); + // compute first argument to the #getOwnPropertyDescriptor() call: + boolean isStatic = member.isStatic(); + var arg0 = protoOrCtorExpr; + if (!exprIsProto && !isStatic) { + // got a constructor, but need a prototype -> append ".prototype" + SymbolTableEntryOriginal prototypeSTE = getSymbolTableEntryForMember(objectType, "prototype", false, true, + true); + arg0 = _PropertyAccessExpr(arg0, prototypeSTE); + } else if (exprIsProto && isStatic) { + // got a prototype, but need a constructor -> append ".constructor" + SymbolTableEntryOriginal constructorSTE = getSymbolTableEntryForMember(objectType, "constructor", false, + false, true); + arg0 = _PropertyAccessExpr(arg0, constructorSTE); + } + + // compute second argument to the #getOwnPropertyDescriptor() call: + Expression arg1 = (!memberIsSymbol) ? _StringLiteralForSTE(memberSTE) + : typeAssistant.getMemberNameAsSymbol(memberName); + // create #getOwnPropertyDescriptor() call + ParameterizedCallExpression result = _CallExpr(_PropertyAccessExpr(objectSTE, getOwnPropertyDescriptorSTE), + arg0, arg1); + return result; + } + + /** + * Returns the direct super type (i.e. immediate super class or directly implemented interface) of the given + * classifier through which the given classifier inherits the given inherited member. Fails fast in case of + * inconsistencies. + */ + private ContainerType getDirectSuperTypeBequestingMember(TClassifier classifier, TMember inheritedMember) { + return getState().memberCollector.directSuperTypeBequestingMember(classifier, inheritedMember); + } + + /** + * Returns the ancestor class (i.e. direct or indirect super class) of the given classifier that either contains the + * given inherited member (if given member is contained in a class) or consumes the given member (if given member is + * contained in an interface). + *

+ * For example: + * + *

+	 * interface I {
+	 * 	m(){}
+	 * }
+	 * class A implements I {
+	 * }
+	 * class B extends A {
+	 * }
+	 * class C extends B {
+	 * }
+	 * 
+ * + * With the above type declarations, for arguments C and m this method would return class + * A. + */ + private TClass getAncestorClassBequestingMember(TClass classifier, TMember inheritedOrConsumedMember) { + ContainerType containingType = inheritedOrConsumedMember.getContainingType(); + if (containingType == classifier) { + return classifier; + } else if (containingType instanceof TInterface) { + return SuperInterfacesIterable.of(classifier).findClassImplementingInterface((TInterface) containingType); + } else if (containingType instanceof TClass) { + return (TClass) containingType;// note: we do not check if containingType is actually an ancestor of + // 'classifier' + } else { + throw new IllegalArgumentException( + "unsupported subtype of TClassifier: " + containingType.eClass().getName()); + } + } + + /** + * Returns distance to given ancestor or 0 if second argument is 'base' or not an ancestor or null. + */ + private static int getDistanceToAncestorClass(TClass base, TClass ancestorClass) { + if (ancestorClass == null || ancestorClass == base) { + return 0; + } + RecursionGuard guard = new RecursionGuard<>(); + int result = 0; + TClass curr = base; + while (curr != null && curr != ancestorClass) { + if (guard.tryNext(curr)) { + result++; + curr = curr.getSuperClassRef() == null ? null : (TClass) curr.getSuperClassRef().getDeclaredType(); + // no need to call guard.done() + } + } + return (curr != null) ? result : 0; + } + + /** + * Returns symbol table entry for the property in a Javascript property descriptor that holds the actual value, i.e. + * the function expression implementing the member. + */ + private SymbolTableEntry getPropertyDescriptorValueProperty(N4MemberDeclaration delegator) { + if (delegator instanceof N4GetterDeclaration) { + return steFor_get(); + } + if (delegator instanceof N4SetterDeclaration) { + return steFor_set(); + } + if (delegator instanceof N4MethodDeclaration) { + return steFor_value(); + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DelegationAssistant.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DelegationAssistant.xtend deleted file mode 100644 index 0db5dc519a..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DelegationAssistant.xtend +++ /dev/null @@ -1,376 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.assistants - -import com.google.common.collect.Lists -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.Block -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.n4JS.N4Modifier -import org.eclipse.n4js.n4JS.N4SetterDeclaration -import org.eclipse.n4js.transpiler.TransformationAssistant -import org.eclipse.n4js.transpiler.assistants.TypeAssistant -import org.eclipse.n4js.transpiler.im.DelegatingGetterDeclaration -import org.eclipse.n4js.transpiler.im.DelegatingMember -import org.eclipse.n4js.transpiler.im.DelegatingMethodDeclaration -import org.eclipse.n4js.transpiler.im.DelegatingSetterDeclaration -import org.eclipse.n4js.transpiler.im.ImFactory -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.ts.types.ContainerType -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TSetter -import org.eclipse.n4js.ts.types.util.SuperInterfacesIterable -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.RecursionGuard - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * This assistant provides helper methods to create members that delegate to some other target member (see - * {@link DelegationAssistant#createDelegatingMember(TClassifier, TMember) createDelegatingMember(TClassifier, TMember)}) - * and to create the Javascript output code to actually implement these delegating members in the transpiler output - * (see {@link DelegationAssistant#createDelegation(DelegatingMember) createDelegation(DelegatingMember)}). - *

- * Usually inherited members in a classifier do not require any special code, because they will be accessed via the - * native prototype chain mechanism of Javascript. However, there are special cases when some special code has to be - * generated for an inherited member in order to properly access that inherited member, because it is not available - * via the ordinary prototype chain. - */ -class DelegationAssistant extends TransformationAssistant { - - @Inject private TypeAssistant typeAssistant; - - - /** - * Creates a new delegating member intended to be inserted into classifier origin in order to delegate - * from origin to the given member target. The target member is assumed to be an inherited - * or consumed member of classifier origin, i.e. it is assumed to be located in one of the ancestor - * classes of origin or one of its directly or indirectly implemented interfaces (but not in origin - * itself!). - *

- * Throws exceptions in case of invalid arguments or an invalid internal state, see implementation for details. - */ - def public DelegatingMember createDelegatingMember(TClassifier origin, TMember target) { - if(target.containingType===origin) { - throw new IllegalArgumentException("no point in delegating to an owned member"); - } - val result = switch(target) { - TField: throw new IllegalArgumentException("delegation to fields not supported yet") - TGetter: ImFactory.eINSTANCE.createDelegatingGetterDeclaration - TSetter: ImFactory.eINSTANCE.createDelegatingSetterDeclaration - TMethod: ImFactory.eINSTANCE.createDelegatingMethodDeclaration - }; - // set simple properties - result.declaredName = _LiteralOrComputedPropertyName(target.name); - result.delegationTarget = getSymbolTableEntryOriginal(target, true); - if(target.static) { - result.declaredModifiers += N4Modifier.STATIC; - } - // set delegationBaseType and delegationSuperClassSteps - if(origin instanceof TInterface) { - // we are in an interface and the target must also be in an interface - if(!(target.eContainer instanceof TInterface)) { - throw new IllegalArgumentException("cannot delegate from an interface to member of a class"); - } - val tSuper = getDirectSuperTypeBequestingMember(origin, target); - // we know the STE of tSuper must already exist, because it is a direct super type and must therefore be - // referenced in the declaration of classifier origin - result.delegationBaseType = getSymbolTableEntryOriginal(tSuper, true); - result.delegationSuperClassSteps = 0; - } else if(origin instanceof TClass) { - // we are in a class and the target may either be in a class or an interface - val tAncestor = getAncestorClassBequestingMember(origin, target); - if(tAncestor!==origin) { - // we are inheriting 'target' from one of our ancestor classes -> delegate to that ancestor class - // (note: this includes the case the 'target' is contained in an interface and one of our ancestor - // classes implements that interface) - val tSuper = origin.superClassRef.declaredType as TClass; - result.delegationBaseType = getSymbolTableEntryOriginal(tSuper, true); - result.delegationSuperClassSteps = origin.getDistanceToAncestorClass(tAncestor) - 1; - } else if(tAncestor!==null) { - // we are consuming 'target' from one of our directly implemented interfaces or its extended interfaces - // (similar as case "origin instanceof TInterface" above) - val tSuper = getDirectSuperTypeBequestingMember(origin, target); - result.delegationBaseType = getSymbolTableEntryOriginal(tSuper, true); - result.delegationSuperClassSteps = 0; - } else { - throw new IllegalStateException("cannot find target (probably not an inherited member)"); - } - } else { - throw new IllegalArgumentException("unsupported subtype of TClassifier: " + origin.eClass.name); - } - // set some properties to let this delegating member behave more like an ordinary member of the same type - result.delegationTargetIsAbstract = target.isAbstract; - if( ! target.isAbstract ) result.body = _Block(); - return result; - } - - /** - * Convenience method for replacing each delegating member in the given declaration by an ordinary member - * created with method {@link #createOrdinaryMemberForDelegatingMember(DelegatingMember)}. Will modify the - * given classifier declaration. - */ - def public void replaceDelegatingMembersByOrdinaryMembers(N4ClassifierDeclaration classifierDecl) { - for (currMember : Lists.newArrayList(classifierDecl.ownedMembersRaw)) { - if (currMember instanceof DelegatingMember) { - val resolvedDelegatingMember = createOrdinaryMemberForDelegatingMember(currMember); - replace(currMember, resolvedDelegatingMember); - } - } - } - - def public N4MemberDeclaration createOrdinaryMemberForDelegatingMember(DelegatingMember delegator) { - val targetNameStr = delegator.delegationTarget.name; - val targetName = if (targetNameStr!==null && targetNameStr.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX)) { - _LiteralOrComputedPropertyName(typeAssistant.getMemberNameAsSymbol(targetNameStr), targetNameStr) - } else { - _LiteralOrComputedPropertyName(targetNameStr) - }; - - val body = createBodyForDelegatingMember(delegator); - - return switch(delegator) { - DelegatingGetterDeclaration: - _N4GetterDecl(targetName, body) - DelegatingSetterDeclaration: - _N4SetterDecl(targetName, _Fpar("value"), body) - DelegatingMethodDeclaration: - _N4MethodDecl(targetName, body) - }; - } - - def private Block createBodyForDelegatingMember(DelegatingMember delegator) { - val baseSTE = delegator.delegationBaseType; - val baseIsInterface = baseSTE?.originalTarget instanceof TInterface; - - val targetAccess = if(baseIsInterface) { - val targetIsStatic = delegator.static; - val objOfInterfaceOfTargetExpr = createAccessToInterfaceObject(baseSTE, targetIsStatic); - createAccessToMemberFunction(objOfInterfaceOfTargetExpr, !targetIsStatic, delegator); - } else { - val ctorOfClassOfTarget = createAccessToClassConstructor(baseSTE, delegator.delegationSuperClassSteps); - - // based on that constructor expression, now create an expression that evaluates to the member function of - // the target member: - createAccessToMemberFunction(ctorOfClassOfTarget, false, delegator) - }; - - val callExpr = _CallExpr( - _PropertyAccessExpr( - targetAccess, - getSymbolTableEntryForMember(state.G.functionType, "apply", false, false, true) - ), - _ThisLiteral, - _IdentRef(steFor_arguments) - ); - - if (delegator instanceof DelegatingSetterDeclaration) { - return _Block( - _ExprStmnt(callExpr) - ); - } else { - return _Block( - _ReturnStmnt(callExpr) - ); - } - } - - /** - * Creates an expression that will evaluate to the constructor of the class denoted by the given symbol table - * entry or one of its super classes (depending on argument superClassSteps). - *

- * For example, if classSTE denotes a class "C", then this will produce an expression like - * - * - * - * - *
Cfor superClassSteps == 0
Object.getPrototypeOf(C)for superClassSteps == 1
Object.getPrototypeOf(Object.getPrototypeOf(C))for superClassSteps == 2
- */ - def private Expression createAccessToClassConstructor(SymbolTableEntry classSTE, int superClassSteps) { - val objectType = state.G.objectType; - val objectSTE = getSymbolTableEntryOriginal(objectType, true); - var Expression result = __NSSafe_IdentRef(classSTE); // this is the "C" in the above examples - // for each super-class-step wrap 'result' into "Object.getPrototypeOf(result)" - if(superClassSteps>0) { - val getPrototypeOfSTE = getSymbolTableEntryForMember(objectType, "getPrototypeOf", false, true, true); - for(n : 0.. - * Since fields do not have such a function this will throw an exception if given member is a field. - */ - def private Expression createAccessToMemberFunction(Expression protoOrCtorExpr, boolean exprIsProto, N4MemberDeclaration member) { - if(member instanceof N4FieldDeclaration) { - throw new IllegalArgumentException("no member function available for fields"); - } - if(member instanceof N4MethodDeclaration) { - // for methods we can use a simple property access instead of Object.getOwnPropertyDescriptor() - val memberName = member.name; - val memberIsSymbol = memberName!==null && memberName.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX); - val memberSTE = findSymbolTableEntryForElement(member, true); - return if(!memberIsSymbol) { - _PropertyAccessExpr(protoOrCtorExpr, memberSTE) - } else { - _IndexAccessExpr(protoOrCtorExpr, typeAssistant.getMemberNameAsSymbol(memberName)) - }; - } - // for other members (i.e. getters and setters) we need to retrieve the property descriptor: - val accessToMemberDefinition = createAccessToMemberDescriptor(protoOrCtorExpr, exprIsProto, member); - // append ".get", ".set", or ".value" depending on member type - val result = _PropertyAccessExpr(accessToMemberDefinition, getPropertyDescriptorValueProperty(member)); - return result; - } - - /** - * Given an expression that will evaluate to a prototype or constructor, this method returns an expression that - * will evaluate to the property descriptor of a particular member. - * - * @param protoOrCtorExpr - * an expression that is expected to evaluate to a prototype or a constructor. - * @param exprIsProto - * tells whether argument protoOrCtorExpr will evaluate to a prototype or a constructor: - * if true, it will evaluate to a prototype, if false it will evaluate to a constructor. - * @param member - * the member. - */ - def private Expression createAccessToMemberDescriptor(Expression protoOrCtorExpr, boolean exprIsProto, N4MemberDeclaration member) { - val memberName = member.name; - val memberIsSymbol = memberName!==null && memberName.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX); - val memberSTE = findSymbolTableEntryForElement(member, true); - val objectType = state.G.objectType; - val objectSTE = getSymbolTableEntryOriginal(objectType, true); - val getOwnPropertyDescriptorSTE = getSymbolTableEntryForMember(objectType, "getOwnPropertyDescriptor", false, true, true); - // compute first argument to the #getOwnPropertyDescriptor() call: - val isStatic = member.static; - var arg0 = protoOrCtorExpr; - if(!exprIsProto && !isStatic) { - // got a constructor, but need a prototype -> append ".prototype" - val prototypeSTE = getSymbolTableEntryForMember(objectType, "prototype", false, true, true); - arg0 = _PropertyAccessExpr(arg0, prototypeSTE); - } else if(exprIsProto && isStatic) { - // got a prototype, but need a constructor -> append ".constructor" - val constructorSTE = getSymbolTableEntryForMember(objectType, "constructor", false, false, true); - arg0 = _PropertyAccessExpr(arg0, constructorSTE); - }; - // compute second argument to the #getOwnPropertyDescriptor() call: - val arg1 = if(!memberIsSymbol) { - _StringLiteralForSTE(memberSTE) - } else { - typeAssistant.getMemberNameAsSymbol(memberName) - }; - // create #getOwnPropertyDescriptor() call - val result = _CallExpr(_PropertyAccessExpr(objectSTE, getOwnPropertyDescriptorSTE), arg0, arg1); - return result; - } - - /** - * Returns the direct super type (i.e. immediate super class or directly implemented interface) of the given - * classifier through which the given classifier inherits the given inherited member. Fails fast in case of - * inconsistencies. - */ - def private ContainerType getDirectSuperTypeBequestingMember(TClassifier classifier, TMember inheritedMember) { - return state.memberCollector.directSuperTypeBequestingMember(classifier, inheritedMember); - } - - /** - * Returns the ancestor class (i.e. direct or indirect super class) of the given classifier that either contains - * the given inherited member (if given member is contained in a class) or consumes the given member (if given - * member is contained in an interface). - *

- * For example: - *

-	 * interface I {
-	 *     m() {}
-	 * }
-	 * class A implements I {}
-	 * class B extends A {}
-	 * class C extends B {}
-	 * 
- * With the above type declarations, for arguments C and m this method would return class - * A. - */ - def private TClass getAncestorClassBequestingMember(TClass classifier, TMember inheritedOrConsumedMember) { - val containingType = inheritedOrConsumedMember.containingType; - if(containingType===classifier) { - return classifier - } else if(containingType instanceof TInterface) { - SuperInterfacesIterable.of(classifier).findClassImplementingInterface(containingType) - } else if(containingType instanceof TClass) { - containingType // note: we do not check if containingType is actually an ancestor of 'classifier' - } else { - throw new IllegalArgumentException("unsupported subtype of TClassifier: " + containingType.eClass.name); - } - } - - /** - * Returns distance to given ancestor or 0 if second argument is 'base' or not an ancestor or null. - */ - def private static int getDistanceToAncestorClass(TClass base, TClass ancestorClass) { - if(ancestorClass===null || ancestorClass===base) { - return 0; - } - val guard = new RecursionGuard(); - var result = 0; - var curr = base; - while(curr!==null && curr!==ancestorClass) { - if(guard.tryNext(curr)) { - result++; - curr = curr.superClassRef?.declaredType as TClass; - // no need to call guard.done() - } - } - return if(curr!==null) result else 0; - } - - /** - * Returns symbol table entry for the property in a Javascript property descriptor that holds the actual value, - * i.e. the function expression implementing the member. - */ - def private SymbolTableEntry getPropertyDescriptorValueProperty(N4MemberDeclaration delegator) { - switch(delegator) { - N4GetterDeclaration: steFor_get - N4SetterDeclaration: steFor_set - N4MethodDeclaration: steFor_value - } - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DestructuringAssistant.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DestructuringAssistant.java new file mode 100644 index 0000000000..ebc5b56d9b --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DestructuringAssistant.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2017 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.assistants; + +import static com.google.common.collect.Iterables.toArray; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrayElement; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrayPadding; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._AssignmentExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ObjLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyNameValuePair; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import org.eclipse.n4js.n4JS.ArrayBindingPattern; +import org.eclipse.n4js.n4JS.ArrayElement; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.BindingElement; +import org.eclipse.n4js.n4JS.BindingPattern; +import org.eclipse.n4js.n4JS.BindingProperty; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ObjectBindingPattern; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.PrimaryExpression; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableDeclarationOrBinding; +import org.eclipse.n4js.transpiler.TransformationAssistant; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; + +/** + * A {@link TransformationAssistant} providing helper functionality for dealing with ES2015 destructuring. + */ +public class DestructuringAssistant extends TransformationAssistant { + + /** + * Converts the given array or object binding pattern into an array or object literal that, if used on the + * right-hand side of an assignment expression, performs the equivalent destructuring operation. + *

+ * Expression for default values are removed from the given binding, so the given binding is incomplete after this + * method returns. It is only guaranteed that (1) the given binding is not removed from its contained and (2) it + * will still include the same variable declarations as before, which can be retrieved via + * {@link VariableDeclarationOrBinding#getAllVariableDeclarations()} on the containing variable binding. + */ + public PrimaryExpression convertBindingPatternToArrayOrObjectLiteral(BindingPattern binding) { + if (binding instanceof ArrayBindingPattern) { + return convertArrayBindingPatternToArrayLiteral((ArrayBindingPattern) binding); + } + if (binding instanceof ObjectBindingPattern) { + return convertObjectBindingPatternToObjectLiteral((ObjectBindingPattern) binding); + } + return null; + } + + /** + * Same as {@link #convertBindingPatternToArrayOrObjectLiteral(BindingPattern)}, but only for array binding + * patterns. + */ + public ArrayLiteral convertArrayBindingPatternToArrayLiteral(ArrayBindingPattern binding) { + ArrayElement[] elems = toArray(map(binding.getElements(), elem -> convertBindingElementToArrayElement(elem)), + ArrayElement.class); + + return _ArrLit(elems); + } + + /** + * Same as {@link #convertBindingPatternToArrayOrObjectLiteral(BindingPattern)}, but only for object binding + * patterns. + */ + public ObjectLiteral convertObjectBindingPatternToObjectLiteral(ObjectBindingPattern binding) { + PropertyNameValuePair[] pairs = toArray( + map(binding.getProperties(), prop -> convertBindingPropertyToPropertyNameValuePair(prop)), + PropertyNameValuePair.class); + return _ObjLit(pairs); + } + + private ArrayElement convertBindingElementToArrayElement(BindingElement element) { + BindingPattern nestedPattern = element.getNestedPattern(); + VariableDeclaration varDecl = element.getVarDecl(); + + Expression lhs; + Expression rhs; + if (nestedPattern != null) { + lhs = convertBindingPatternToArrayOrObjectLiteral(nestedPattern); + rhs = element.getExpression(); // may be null (which is ok, see below) + } else if (varDecl != null) { + SymbolTableEntry ste_varDecl = findSymbolTableEntryForElement(varDecl, true); + lhs = _IdentRef(ste_varDecl); + rhs = varDecl.getExpression(); // may be null (which is ok, see below) + } else { + return _ArrayPadding(); + } + + return _ArrayElement( + element.isRest(), + (rhs != null) ? _AssignmentExpr(lhs, rhs) : lhs); + } + + private PropertyNameValuePair convertBindingPropertyToPropertyNameValuePair(BindingProperty property) { + return _PropertyNameValuePair( + property.getName(), + convertBindingElementToArrayElement(property.getValue()).getExpression()); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DestructuringAssistant.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DestructuringAssistant.xtend deleted file mode 100644 index c0c59d0265..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/DestructuringAssistant.xtend +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright (c) 2017 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.assistants - -import org.eclipse.n4js.n4JS.ArrayBindingPattern -import org.eclipse.n4js.n4JS.ArrayElement -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.BindingElement -import org.eclipse.n4js.n4JS.BindingPattern -import org.eclipse.n4js.n4JS.BindingProperty -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ObjectBindingPattern -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.PrimaryExpression -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.VariableDeclarationOrBinding -import org.eclipse.n4js.transpiler.TransformationAssistant - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * A {@link TransformationAssistant} providing helper functionality for dealing with ES2015 destructuring. - */ -class DestructuringAssistant extends TransformationAssistant { - - - /** - * Converts the given array or object binding pattern into an array or object literal that, if used on the - * right-hand side of an assignment expression, performs the equivalent destructuring operation. - *

- * Expression for default values are removed from the given binding, so the given binding is incomplete after this - * method returns. It is only guaranteed that (1) the given binding is not removed from its contained and (2) it - * will still include the same variable declarations as before, which can be retrieved via - * {@link VariableDeclarationOrBinding#getVariableDeclarations()} on the containing variable binding. - */ - public def PrimaryExpression convertBindingPatternToArrayOrObjectLiteral(BindingPattern binding) { - return switch(binding) { - ArrayBindingPattern: convertArrayBindingPatternToArrayLiteral(binding) - ObjectBindingPattern: convertObjectBindingPatternToObjectLiteral(binding) - }; - } - - /** - * Same as {@link #convertBindingPatternToArrayOrObjectLiteral(BindingPattern)}, but only for array binding - * patterns. - */ - public def ArrayLiteral convertArrayBindingPatternToArrayLiteral(ArrayBindingPattern binding) { - return _ArrLit( - binding.elements.map[convertBindingElementToArrayElement] - ); - } - - /** - * Same as {@link #convertBindingPatternToArrayOrObjectLiteral(BindingPattern)}, but only for object binding - * patterns. - */ - public def ObjectLiteral convertObjectBindingPatternToObjectLiteral(ObjectBindingPattern binding) { - return _ObjLit( - binding.properties.map[convertBindingPropertyToPropertyNameValuePair] - ); - } - - private def ArrayElement convertBindingElementToArrayElement(BindingElement element) { - val nestedPattern = element.nestedPattern; - val varDecl = element.varDecl; - - var Expression lhs; - var Expression rhs; - if(nestedPattern!==null) { - lhs = convertBindingPatternToArrayOrObjectLiteral(nestedPattern); - rhs = element.expression; // may be null (which is ok, see below) - } else if(varDecl!==null) { - val ste_varDecl = findSymbolTableEntryForElement(varDecl, true); - lhs = _IdentRef(ste_varDecl); - rhs = varDecl.expression; // may be null (which is ok, see below) - } else { - return _ArrayPadding(); - } - - return _ArrayElement( - element.rest, - if(rhs!==null) { - _AssignmentExpr(lhs, rhs) - } else { - lhs - } - ); - } - - private def PropertyNameValuePair convertBindingPropertyToPropertyNameValuePair(BindingProperty property) { - return _PropertyNameValuePair( - property.name, - convertBindingElementToArrayElement(property.value).expression - ); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ReflectionAssistant.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ReflectionAssistant.java new file mode 100644 index 0000000000..4e6aae441d --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ReflectionAssistant.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.assistants; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Block; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._LiteralOrComputedPropertyName; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4GetterDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ThisLiteral; + +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4Modifier; +import org.eclipse.n4js.n4JS.N4TypeDeclaration; +import org.eclipse.n4js.transpiler.InformationRegistry; +import org.eclipse.n4js.transpiler.TransformationAssistant; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.utils.ResourceNameComputer; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.inject.Inject; + +/** + * Helper methods for creating output code related to reflection for classes, interfaces, and enums in N4JS. + */ +public class ReflectionAssistant extends TransformationAssistant { + @Inject + private ResourceNameComputer resourceNameComputer; + + /** + * Convenience method. Same as {@link #createN4TypeGetter(N4TypeDeclaration)}, but if creation of the 'n4type' + * reflection getter is successful, the getter will be added to the given classifier; otherwise, this method does + * nothing. + */ + public void addN4TypeGetter(N4TypeDeclaration typeDecl, N4ClassifierDeclaration addHere) { + N4GetterDeclaration getter = createN4TypeGetter(typeDecl); + if (getter != null) { + addHere.getOwnedMembersRaw().add(getter); + } + } + + /** + * Returns the 'n4type' getter for obtaining reflection information of the given class, interface, or enum + * declaration or null if it cannot be created. + *

+ * NOTE: Reflection is only supported for declarations that were given in the original source code, i.e. when method + * {@link InformationRegistry#getOriginalDefinedType(N4ClassifierDeclaration)} returns a non-null value. Otherwise, + * this method will return null as well. + * + * @return the 'n4type' getter for the given declaration or null iff the declaration does not have an + * original defined type. + */ + public N4GetterDeclaration createN4TypeGetter(N4TypeDeclaration typeDecl) { + Type originalType = getState().info.getOriginalDefinedType(typeDecl); + if (originalType == null) { + return null; + } + + SymbolTableEntry typeSTE = findSymbolTableEntryForElement(typeDecl, true); + ReflectionBuilder reflectionBuilder = new ReflectionBuilder(this, getState(), resourceNameComputer); + JsonElement reflectInfo = reflectionBuilder.createReflectionInfo(typeDecl, typeSTE); + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + String origJsonString = gson.toJson(reflectInfo); + String quotedJsonString = "'" + origJsonString.replaceAll("\'", "\\\\\'") + "'"; + + SymbolTableEntryInternal methodName = null; + if (typeDecl instanceof N4ClassDeclaration) { + methodName = steFor_$getReflectionForClass(); + } + if (typeDecl instanceof N4InterfaceDeclaration) { + methodName = steFor_$getReflectionForInterface(); + } + if (typeDecl instanceof N4EnumDeclaration) { + methodName = steFor_$getReflectionForEnum(); + } + + N4GetterDeclaration getterDecl = _N4GetterDecl( + _LiteralOrComputedPropertyName(N4JSLanguageConstants.N4TYPE_NAME), + _Block( + _ReturnStmnt( + _CallExpr( + _IdentRef(methodName), + _ThisLiteral(), + _StringLiteral(quotedJsonString, quotedJsonString))))); + + getterDecl.getDeclaredModifiers().add(N4Modifier.STATIC); + + return getterDecl; + } + +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ReflectionAssistant.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ReflectionAssistant.xtend deleted file mode 100644 index ca9cbb8fc3..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/assistants/ReflectionAssistant.xtend +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.assistants - -import com.google.gson.GsonBuilder -import com.google.inject.Inject -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4EnumDeclaration -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.n4JS.N4Modifier -import org.eclipse.n4js.n4JS.N4TypeDeclaration -import org.eclipse.n4js.transpiler.InformationRegistry -import org.eclipse.n4js.transpiler.TransformationAssistant -import org.eclipse.n4js.utils.ResourceNameComputer - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Helper methods for creating output code related to reflection for classes, interfaces, and enums in N4JS. - */ -class ReflectionAssistant extends TransformationAssistant { - @Inject private ResourceNameComputer resourceNameComputer; - - /** - * Convenience method. Same as {@link #createN4TypeGetter(N4TypeDeclaration)}, but if creation of the - * 'n4type' reflection getter is successful, the getter will be added to the given classifier; otherwise, - * this method does nothing. - */ - def public void addN4TypeGetter(N4TypeDeclaration typeDecl, N4ClassifierDeclaration addHere) { - val getter = createN4TypeGetter(typeDecl); - if (getter !== null) { - addHere.ownedMembersRaw += getter; - } - } - - /** - * Returns the 'n4type' getter for obtaining reflection information of the given class, interface, or enum declaration - * or null if it cannot be created. - *

- * NOTE: Reflection is only supported for declarations that were given in the original source code, i.e. when method - * {@link InformationRegistry#getOriginalDefinedType(N4TypeDeclaration) getOriginalDefinedType()} - * returns a non-null value. Otherwise, this method will return null as well. - * - * @return the 'n4type' getter for the given declaration or null iff the declaration does not have - * an original defined type. - */ - def public N4GetterDeclaration createN4TypeGetter(N4TypeDeclaration typeDecl) { - val originalType = state.info.getOriginalDefinedType(typeDecl); - if (originalType === null) { - return null; - } - - val typeSTE = findSymbolTableEntryForElement(typeDecl, true); - val reflectionBuilder = new ReflectionBuilder(this, state, resourceNameComputer); - val reflectInfo = reflectionBuilder.createReflectionInfo(typeDecl, typeSTE); - val gson = new GsonBuilder().disableHtmlEscaping().create(); - val origJsonString = gson.toJson(reflectInfo); - val quotedJsonString = "'" + origJsonString.replaceAll("\'", "\\\\\'") + "'"; - - val methodName = switch (typeDecl) { - N4ClassDeclaration: steFor_$getReflectionForClass() - N4InterfaceDeclaration: steFor_$getReflectionForInterface() - N4EnumDeclaration: steFor_$getReflectionForEnum() - } - - return _N4GetterDecl( - _LiteralOrComputedPropertyName(N4JSLanguageConstants.N4TYPE_NAME), - _Block( - _ReturnStmnt( - _CallExpr( - _IdentRef(methodName), - _ThisLiteral, - _StringLiteral(quotedJsonString, quotedJsonString) - ) - ) - ) - ) => [ - declaredModifiers += N4Modifier.STATIC - ]; - } - -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ApiImplStubGenerationTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ApiImplStubGenerationTransformation.java new file mode 100644 index 0000000000..6adde5476d --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ApiImplStubGenerationTransformation.java @@ -0,0 +1,356 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._AnnotationList; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._EnumDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._EnumLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ExportDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._FunDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4ClassDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4InterfaceDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4MemberDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4MethodDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NewExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ThrowStmnt; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.AnnotableScriptElement; +import org.eclipse.n4js.n4JS.ExportableElement; +import org.eclipse.n4js.n4JS.FunctionDeclaration; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.ScriptElement; +import org.eclipse.n4js.tooling.compare.ProjectComparisonEntry; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.RequiresBefore; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant; +import org.eclipse.n4js.transpiler.im.DelegatingMember; +import org.eclipse.n4js.transpiler.im.Script_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.utils.ConcreteMembersOrderedForTranspiler; +import org.eclipse.n4js.transpiler.utils.MissingApiMembersForTranspiler; +import org.eclipse.n4js.transpiler.utils.ScriptApiTracker; +import org.eclipse.n4js.transpiler.utils.ScriptApiTracker.ProjectComparisonAdapter; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.util.AccessorTuple; +import org.eclipse.n4js.ts.types.util.MemberList; +import org.eclipse.n4js.utils.ContainerTypesHelper; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind; +import org.eclipse.n4js.validation.N4JSElementKeywordProvider; + +import com.google.inject.Inject; + +/** + * Generation of code for missing implementations in projects implementing a specific API. + */ +@RequiresBefore(MemberPatchingTransformation.class) +public class ApiImplStubGenerationTransformation extends Transformation { + + @Inject + private DelegationAssistant delegationAssistant; + @Inject + private TypeAssistant typeAssistant; + @Inject + private ScriptApiTracker scriptApiTracker; + @Inject + private ContainerTypesHelper containerTypesHelper; + @Inject + private N4JSElementKeywordProvider n4jsElementKeywordProvider; + + @Override + public void assertPreConditions() { + // empty + } + + @Override + public void assertPostConditions() { + // empty + } + + @Override + public void analyze() { + // perform API/Impl compare for the resource to compile + scriptApiTracker.initApiCompare(getState().resource.getScript()); + // (reminder: code in #analyze() will be invoked once per resource to compile, so this is what we want) + } + + @Override + public void transform() { + // add stubs for missing members in EXISTING classes and interfaces + for (N4ClassifierDeclaration cd : collectNodes(getState().im, N4ClassifierDeclaration.class, false)) { + addMissingMembers(cd); + } + // add missing types (classes, interfaces, enums) and other top-level elements (declared functions, variables) + addMissingTopLevelElements(); + } + + private void addMissingMembers(N4ClassifierDeclaration classifierDecl) { + TClassifier type = getState().info.getOriginalDefinedType(classifierDecl); + MissingApiMembersForTranspiler mamft = createMAMFT(type); + + // add API/Impl method stubs + for (TMethod m : mamft.missingApiMethods) { + N4MemberDeclaration member = createApiImplStub(classifierDecl, m); + classifierDecl.getOwnedMembersRaw().add(member); + } + // add API/Impl field accessor stubs + for (AccessorTuple accTuple : mamft.missingApiAccessorTuples) { + if (accTuple.getGetter() != null) { + TGetter g = accTuple.getGetter(); + N4MemberDeclaration member = createApiImplStub(classifierDecl, g); + classifierDecl.getOwnedMembersRaw().add(member); + } + if (accTuple.getSetter() != null) { + TSetter s = accTuple.getSetter(); + N4MemberDeclaration member = createApiImplStub(classifierDecl, s); + classifierDecl.getOwnedMembersRaw().add(member); + } + } + + // add delegates to inherited fields/getters/setters shadowed by an owned setter XOR getter + // NOTE: Partial shadowing in general is disallowed by validation. However, in incomplete + // API-impl situation we still support this feature here to propagate generated stubs for + // test reporting-purposes. + for (AccessorTuple accTuple : mamft.missingApiAccessorTuples) { + if (accTuple.getInheritedGetter() != null && accTuple.getGetter() == null && accTuple.getSetter() != null) { + // an owned setter is shadowing an inherited getter -> delegate to the inherited getter + DelegatingMember delegator = delegationAssistant.createDelegatingMember(type, + accTuple.getInheritedGetter()); + classifierDecl.getOwnedMembersRaw().add(delegator); + } + if (accTuple.getInheritedSetter() != null && accTuple.getGetter() != null && accTuple.getSetter() == null) { + // an owned getter is shadowing an inherited setter -> delegate to the inherited setter + DelegatingMember delegator = delegationAssistant.createDelegatingMember(type, + accTuple.getInheritedSetter()); + classifierDecl.getOwnedMembersRaw().add(delegator); + } + } + + } + + private void addMissingTopLevelElements() { + Script script = getState().resource.getScript(); + + scriptApiTracker.initApiCompare(script); + ProjectComparisonAdapter comparison = ScriptApiTracker.firstProjectComparisonAdapter(script.eResource()) + .orElse(null); + if (null == comparison) { + return; + } + + for (ProjectComparisonEntry pce : comparison.getEntryFor(script.getModule()).allChildren().toList()) { + if (null == pce.getElementImpl(0)) {// no implementation + EObject x = pce.getElementAPI(); + if (x instanceof TMethod) { + /* do nothing */ + } + if (x instanceof TFunction) { + missing((TFunction) x); + } + if (x instanceof TClass) { + missing((TClass) x); + } + if (x instanceof TInterface) { + missing((TInterface) x); + } + if (x instanceof TEnum) { + missing((TEnum) x); + } + if (x instanceof TVariable) { + missing((TVariable) x); + } + } + } + } + + private void missing(TInterface tinter) { + N4InterfaceDeclaration stub0 = _N4InterfaceDeclaration(tinter.getName()); + + // annotations + stub0.setAnnotationList(_AnnotationList( + toList(map(tinter.getAnnotations(), a -> AnnotationDefinition.find(a.getName()))))); + + // export + ScriptElement stub = (ScriptElement) wrapExported(tinter.isDirectlyExported(), stub0); + + // members + // (in an interface stub we need member stubs for static AND non-static members) + MemberList members = getState().memberCollector.members(tinter, false, false); + for (TMember m : members) { + if (!(m instanceof TField)) { + stub0.getOwnedMembersRaw().add(createApiImplStub(stub0, m)); + } + } + + appendToScript(stub); + + getState().info.setOriginalDefinedType(stub0, tinter); + + createSymbolTableEntryIMOnly(stub0); + } + + private void missing(TClass tclass) { + N4ClassDeclaration stub0 = _N4ClassDeclaration(tclass.getName()); + + // at least a ctor throwing an exception is required here. + stub0.getOwnedMembersRaw().add(_N4MethodDecl("constructor", + _ThrowStmnt(_NewExpr( + _IdentRef(N4ApiNotImplementedErrorSTE()), + _StringLiteral("Class " + tclass.getName() + " is not implemented yet."))))); + + // annotations + stub0.setAnnotationList(_AnnotationList( + toList(map(tclass.getAnnotations(), a -> AnnotationDefinition.find(a.getName()))))); + + // export + ScriptElement stub = (ScriptElement) wrapExported(tclass.isDirectlyExported(), stub0); + + // members + // (in a class stub we need member stubs ONLY for static members) + MemberList members = getState().memberCollector.members(tclass, false, false); + for (TMember m : members) { + if (!(m instanceof TField) && m.isStatic()) { + stub0.getOwnedMembersRaw().add(createApiImplStub(stub0, m)); + } + } + + appendToScript(stub); + + getState().info.setOriginalDefinedType(stub0, tclass); + + createSymbolTableEntryIMOnly(stub0); + } + + private void missing(TEnum tenum) { + N4EnumDeclaration stub0 = _EnumDeclaration(tenum.getName(), + toList(map(tenum.getLiterals(), l -> _EnumLiteral(l.getName(), l.getName())))); + + // exported + ScriptElement stub = (ScriptElement) wrapExported(tenum.isDirectlyExported(), stub0); + + // number-/string-based + EnumKind enumKind = N4JSLanguageUtils.getEnumKind(tenum); + switch (enumKind) { + case Normal: { + // do nothing + break; + } + case NumberBased: { + ((AnnotableScriptElement) stub) + .setAnnotationList(_AnnotationList(List.of(AnnotationDefinition.NUMBER_BASED))); + break; + } + case StringBased: { + ((AnnotableScriptElement) stub) + .setAnnotationList(_AnnotationList(List.of(AnnotationDefinition.STRING_BASED))); + break; + } + } + + appendToScript(stub); + + getState().info.setOriginalDefinedType(stub0, tenum); + + createSymbolTableEntryIMOnly(stub0); + } + + private void appendToScript(ScriptElement stub) { + Script_IM script = getState().im; + if (script.getScriptElements().isEmpty()) { + script.getScriptElements().add(stub); + } else { + insertAfter(last(script.getScriptElements()), stub); + } + } + + private void missing(TVariable tvar) { + missingFuncOrVar(tvar, tvar.isDirectlyExported(), "variable"); + } + + private void missing(TFunction func) { + missingFuncOrVar(func, func.isDirectlyExported(), "function"); + } + + private SymbolTableEntryInternal N4ApiNotImplementedErrorSTE() { + return steFor_N4ApiNotImplementedError(); + } + + private void missingFuncOrVar(IdentifiableElement func, boolean exported, String description) { + SymbolTableEntryOriginal funcSTE = getSymbolTableEntryOriginal(func, true); // createSymbolTableEntry(fun); + + FunctionDeclaration funcDecl = _FunDecl(funcSTE.getName(), _ThrowStmnt(_NewExpr( + _IdentRef(N4ApiNotImplementedErrorSTE()), + _StringLiteral(description + " " + funcSTE.getName() + " is not implemented yet.")))); + EObject stub = wrapExported(exported, funcDecl); + + insertAfter(last(getState().im.getScriptElements()), stub); + } + + private EObject wrapExported(boolean exported, ExportableElement toExportOrNotToExport) { + return (exported) ? _ExportDeclaration(toExportOrNotToExport) : toExportOrNotToExport; + } + + /** + * Creates a member that servers as the stub for a missing member on implementation side, corresponding to the given + * member apiMember on API side. + */ + private N4MemberDeclaration createApiImplStub(N4ClassifierDeclaration classifierDecl, TMember apiMember) { + // here we create: + // + // public m() { // or a getter or setter + // throw new N4ApiNotImplementedError("API for method C.m not implemented yet."); + // } + // + SymbolTableEntryInternal N4ApiNotImplementedErrorSTE = steFor_N4ApiNotImplementedError(); + String typeName = classifierDecl.getName(); + String memberKeyword = n4jsElementKeywordProvider.keyword(apiMember); + String memberName = apiMember.getName(); + return _N4MemberDecl(apiMember, + _ThrowStmnt( + _NewExpr(_IdentRef(N4ApiNotImplementedErrorSTE), _StringLiteral( + "API for " + memberKeyword + " " + typeName + "." + memberName + + " not implemented yet.")))); + } + + // note: the following uses logic from old transpiler (MissingApiMembersForTranspiler, ScriptApiTracker) + private MissingApiMembersForTranspiler createMAMFT(TClassifier classifier) { + ConcreteMembersOrderedForTranspiler cmoft = typeAssistant.getOrCreateCMOFT(classifier); + return MissingApiMembersForTranspiler.create(containerTypesHelper, scriptApiTracker, + classifier, cmoft, getState().resource.getScript()); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ApiImplStubGenerationTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ApiImplStubGenerationTransformation.xtend deleted file mode 100644 index 52665e3e65..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ApiImplStubGenerationTransformation.xtend +++ /dev/null @@ -1,301 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import java.util.stream.Stream -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.AnnotableScriptElement -import org.eclipse.n4js.n4JS.ExportableElement -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.ScriptElement -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.RequiresBefore -import org.eclipse.n4js.transpiler.assistants.TypeAssistant -import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant -import org.eclipse.n4js.transpiler.utils.MissingApiMembersForTranspiler -import org.eclipse.n4js.transpiler.utils.ScriptApiTracker -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.utils.ContainerTypesHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.validation.N4JSElementKeywordProvider - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.transpiler.utils.ScriptApiTracker.firstProjectComparisonAdapter - -/** - * Generation of code for missing implementations in projects implementing a specific API. - */ -@RequiresBefore(MemberPatchingTransformation) -class ApiImplStubGenerationTransformation extends Transformation { - - @Inject private DelegationAssistant delegationAssistant; - @Inject private TypeAssistant typeAssistant; - @Inject private ScriptApiTracker scriptApiTracker; - @Inject private ContainerTypesHelper containerTypesHelper; - @Inject private N4JSElementKeywordProvider n4jsElementKeywordProvider; - - - override assertPreConditions() { - } - override assertPostConditions() { - } - - override analyze() { - // perform API/Impl compare for the resource to compile - scriptApiTracker.initApiCompare(state.resource.script); - // (reminder: code in #analyze() will be invoked once per resource to compile, so this is what we want) - } - - override transform() { - // add stubs for missing members in EXISTING classes and interfaces - collectNodes(state.im, N4ClassifierDeclaration, false).forEach[addMissingMembers]; - // add missing types (classes, interfaces, enums) and other top-level elements (declared functions, variables) - addMissingTopLevelElements(); - } - - def private void addMissingMembers(N4ClassifierDeclaration classifierDecl) { - val type = state.info.getOriginalDefinedType(classifierDecl); - val mamft = createMAMFT(type); - - // add API/Impl method stubs - for(m : mamft.missingApiMethods) { - val member = createApiImplStub(classifierDecl, m); - classifierDecl.ownedMembersRaw += member; - } - // add API/Impl field accessor stubs - for(accTuple : mamft.missingApiAccessorTuples) { - if(accTuple.getter!==null) { - val g = accTuple.getter; - val member = createApiImplStub(classifierDecl, g); - classifierDecl.ownedMembersRaw += member; - } - if(accTuple.setter!==null) { - val s = accTuple.setter; - val member = createApiImplStub(classifierDecl, s); - classifierDecl.ownedMembersRaw += member; - } - } - - - // add delegates to inherited fields/getters/setters shadowed by an owned setter XOR getter - // NOTE: Partial shadowing in general is disallowed by validation. However, in incomplete - // API-impl situation we still support this feature here to propagate generated stubs for - // test reporting-purposes. - for(accTuple : mamft.missingApiAccessorTuples) { - if(accTuple.inheritedGetter!==null && accTuple.getter===null && accTuple.setter!==null) { - // an owned setter is shadowing an inherited getter -> delegate to the inherited getter - val delegator = delegationAssistant.createDelegatingMember(type, accTuple.inheritedGetter); - classifierDecl.ownedMembersRaw += delegator; - } - if(accTuple.inheritedSetter!==null && accTuple.getter!==null && accTuple.setter===null) { - // an owned getter is shadowing an inherited setter -> delegate to the inherited setter - val delegator = delegationAssistant.createDelegatingMember(type, accTuple.inheritedSetter); - classifierDecl.ownedMembersRaw += delegator; - } - } - - - } - - def private void addMissingTopLevelElements() { - - val script = state.resource.script; - - scriptApiTracker.initApiCompare(script) - val comparison = script.eResource.firstProjectComparisonAdapter.orElse(null); - if (null === comparison) { - return; - } - - comparison.getEntryFor(script.module).allChildren.toIterable - .filter[null === getElementImpl(0)] // no implementation - .forEach[ - switch x:elementAPI{ - TMethod : { /* do nothing */ } - TFunction : missing(x) - TClass : missing(x) - TInterface : missing(x) - TEnum : missing(x) - TVariable : missing(x) - } - ]; - } - - def private dispatch missing(TInterface tinter){ - val stub0 = _N4InterfaceDeclaration( tinter.name ) ; - - // annotations - stub0.annotationList = _AnnotationList( tinter.annotations.map[ AnnotationDefinition.find(it.name) ] ) - - // export - val stub = wrapExported(tinter.isDirectlyExported, stub0) as ScriptElement - - // members - // (in an interface stub we need member stubs for static AND non-static members) - val members = state.memberCollector.members(tinter, false, false); - for(TMember m : members) { - if(!(m instanceof TField)) { - stub0.ownedMembersRaw += createApiImplStub(stub0, m); - } - } - - appendToScript(stub); - - state.info.setOriginalDefinedType(stub0, tinter); - - createSymbolTableEntryIMOnly(stub0) - } - def private dispatch missing(TClass tclass){ - val stub0 = _N4ClassDeclaration( tclass.name ) =>[ - // at least a ctor throwing an exception is required here. - it.ownedMembersRaw += _N4MethodDecl("constructor", - _ThrowStmnt( _NewExpr( - _IdentRef( N4ApiNotImplementedErrorSTE ), - _StringLiteral( '''Class «tclass.name» is not implemented yet.''') - ) ) ) - ]; - - // annotations - stub0.annotationList = _AnnotationList( tclass.annotations.map[ AnnotationDefinition.find(it.name) ] ) - - // export - val stub = wrapExported(tclass.isDirectlyExported, stub0) as ScriptElement - - // members - // (in a class stub we need member stubs ONLY for static members) - val members = state.memberCollector.members(tclass, false, false); - for(TMember m : members) { - if(!(m instanceof TField) && m.static) { - stub0.ownedMembersRaw += createApiImplStub(stub0, m); - } - } - - appendToScript(stub); - - state.info.setOriginalDefinedType(stub0, tclass); - - createSymbolTableEntryIMOnly(stub0) - } - def private dispatch missing(TEnum tenum){ - - val stub0 = _EnumDeclaration(tenum.name, tenum.literals.map[ _EnumLiteral(name, name) ] ); - - // exported - var ScriptElement stub = wrapExported( tenum.isDirectlyExported , - stub0 - ) as ScriptElement; - - // number-/string-based - val enumKind = N4JSLanguageUtils.getEnumKind(tenum); - switch (enumKind) { - case Normal: { - // do nothing - } - case NumberBased: { - (stub as AnnotableScriptElement).annotationList = _AnnotationList(#[AnnotationDefinition.NUMBER_BASED]); - } - case StringBased: { - (stub as AnnotableScriptElement).annotationList = _AnnotationList(#[AnnotationDefinition.STRING_BASED]); - } - }; - - appendToScript( stub ) - - state.info.setOriginalDefinedType(stub0, tenum); - - createSymbolTableEntryIMOnly(stub0) - } - - def private appendToScript(ScriptElement stub) { - val script = state.im; - if ( script.scriptElements.isEmpty ) script.scriptElements+=stub - else insertAfter ( script.scriptElements.last, stub ); - } - - def private dispatch missing(TVariable tvar){ - missingFuncOrVar(tvar, tvar.isDirectlyExported, "variable") - } - def private dispatch missing(TFunction func){ - missingFuncOrVar(func, func.isDirectlyExported, "function") - } - - def private N4ApiNotImplementedErrorSTE() { - steFor_N4ApiNotImplementedError; - } - - def private missingFuncOrVar(IdentifiableElement func, boolean exported, String description) { - val funcSTE = func.getSymbolTableEntryOriginal(true); // createSymbolTableEntry(func); - - val funcDecl = _FunDecl( funcSTE.name, _ThrowStmnt( _NewExpr( - _IdentRef( N4ApiNotImplementedErrorSTE ), - _StringLiteral( '''«description» «funcSTE.name» is not implemented yet.''') - ) ) ) - => [ - // Do some more configuration. - ]; - val stub = wrapExported(exported,funcDecl); - - insertAfter( state.im.scriptElements.last , stub ); - } - - def private wrapExported( boolean exported, ExportableElement toExportOrNotToExport ) - { - if( exported ) _ExportDeclaration( toExportOrNotToExport ) else toExportOrNotToExport - } - - /** - * Creates a member that servers as the stub for a missing member on implementation side, corresponding to the given - * member apiMember on API side. - */ - def private N4MemberDeclaration createApiImplStub(N4ClassifierDeclaration classifierDecl, TMember apiMember) { - // here we create: - // - // public m() { // or a getter or setter - // throw new N4ApiNotImplementedError("API for method C.m not implemented yet."); - // } - // - val N4ApiNotImplementedErrorSTE = steFor_N4ApiNotImplementedError; - val typeName = classifierDecl.name; - val memberKeyword = n4jsElementKeywordProvider.keyword(apiMember); - val memberName = apiMember.name; - return _N4MemberDecl(apiMember, - _ThrowStmnt( - _NewExpr(_IdentRef(N4ApiNotImplementedErrorSTE), _StringLiteral( - '''API for «memberKeyword» «typeName».«memberName» not implemented yet.''' - )) - ) - ); - } - - /** Converts the stream into an iterable. */ - def private Iterable toIterable(Stream stream) { - return [stream.iterator]; - } - - // note: the following uses logic from old transpiler (MissingApiMembersForTranspiler, ScriptApiTracker) - def private MissingApiMembersForTranspiler createMAMFT(TClassifier classifier) { - val cmoft = typeAssistant.getOrCreateCMOFT(classifier); - return MissingApiMembersForTranspiler.create(containerTypesHelper, scriptApiTracker, - classifier, cmoft, state.resource.script); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part1_Transformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part1_Transformation.java new file mode 100644 index 0000000000..c642e50cad --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part1_Transformation.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; + +import org.eclipse.n4js.generator.GeneratorOption; +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.ExpressionStatement; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.Optional; +import org.eclipse.n4js.transpiler.es.assistants.BlockAssistant; + +import com.google.inject.Inject; + +/** + * Transforms ES2015 arrow functions to an ES5 equivalent, using ordinary function expressions. + */ +@Optional(GeneratorOption.ArrowFunctions) +public class ArrowFunction_Part1_Transformation extends Transformation { + + @Inject + BlockAssistant blockAssistant; + + @Override + public void analyze() { + // empty + } + + @Override + public void assertPreConditions() { + // empty + } + + @Override + public void assertPostConditions() { + // empty + } + + @Override + public void transform() { + for (ArrowFunction af : collectNodes(getState().im, ArrowFunction.class, true)) { + transformArrowFunction(af); + } + } + + /** turn implicit returns into explicit ones. */ + private void transformArrowFunction(ArrowFunction arrowFunc) { + + // PART 1 + if (arrowFunc.isSingleExprImplicitReturn()) { + if (blockAssistant.needsReturnInsertionForBody(arrowFunc)) { + // Wrap in return. + ExpressionStatement exprToWrap = (ExpressionStatement) arrowFunc.getBody().getStatements().get(0); + replace(exprToWrap, _ReturnStmnt(exprToWrap.getExpression())); // reuse expression + } + } + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part1_Transformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part1_Transformation.xtend deleted file mode 100644 index 760dbfc949..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part1_Transformation.xtend +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.ExpressionStatement -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.Optional -import org.eclipse.n4js.transpiler.es.assistants.BlockAssistant - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Transforms ES2015 arrow functions to an ES5 equivalent, using ordinary function expressions. - */ -@Optional(ArrowFunctions) -class ArrowFunction_Part1_Transformation extends Transformation { - - @Inject BlockAssistant blockAssistant; - - - override analyze() { - - } - - override assertPreConditions() { - } - - override assertPostConditions() { - } - - override transform() { - collectNodes(state.im, ArrowFunction, true).forEach[transformArrowFunction]; - } - - /** turn implicit returns into explicit ones. */ - private def void transformArrowFunction(ArrowFunction arrowFunc ) { - - // PART 1 - if( arrowFunc.isSingleExprImplicitReturn ) { - if( blockAssistant.needsReturnInsertionForBody(arrowFunc)) { - // Wrap in return. - var exprToWrap = arrowFunc.body.statements.get(0) as ExpressionStatement; - replace(exprToWrap, _ReturnStmnt(exprToWrap.expression)); // reuse expression - } - } - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part2_Transformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part2_Transformation.java new file mode 100644 index 0000000000..f7bdccc043 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part2_Transformation.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._FunExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Parenthesis; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ThisLiteral; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.functionType; + +import org.eclipse.n4js.generator.GeneratorOption; +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.transpiler.AbstractTranspiler; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.Optional; +import org.eclipse.n4js.transpiler.TransformationDependency.Requires; + +/** + * Part 2 of {@link ArrowFunction_Part1_Transformation}. + */ +@Optional(GeneratorOption.ArrowFunctions) +@Requires(ArrowFunction_Part1_Transformation.class) +public class ArrowFunction_Part2_Transformation extends Transformation { + + @Override + public void analyze() { + // empty + } + + @Override + public void assertPreConditions() { + // empty + } + + @Override + public void assertPostConditions() { + if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { + assertTrue("No arrow-function left in IM", + collectNodes(getState().im, ArrowFunction.class, true).isEmpty()); + } + } + + @Override + public void transform() { + for (ArrowFunction af : collectNodes(getState().im, ArrowFunction.class, true)) { + transformArrowFunction(af); + } + } + + /** replace arrow-function by function-expression */ + private void transformArrowFunction(ArrowFunction arrowFunc) { + + // PART 2 + FunctionExpression fe = _FunExpr(arrowFunc.isAsync(), arrowFunc.getName(), + arrowFunc.getFpars().toArray(new FormalParameter[0]), arrowFunc.getBody()); + // note: arrow functions cannot be generators, so we do *not* need to do: + // fe.generator = arrowFunc.generator; + + ParameterizedCallExpression thisBinder = _CallExpr( + _PropertyAccessExpr( + _Parenthesis(fe), + getSymbolTableEntryForMember(functionType(getState().G), "bind", false, false, true)), + _ThisLiteral()); // end Call function*() + + replace(arrowFunc, thisBinder, fe); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part2_Transformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part2_Transformation.xtend deleted file mode 100644 index 7aeeae2cb5..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ArrowFunction_Part2_Transformation.xtend +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.Optional -import org.eclipse.n4js.transpiler.TransformationDependency.Requires - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* -import org.eclipse.n4js.transpiler.AbstractTranspiler - -/** - * Part 2 of {@link ArrowFunction_Part1_Transformation}. - */ -@Optional(ArrowFunctions) -@Requires(ArrowFunction_Part1_Transformation) -class ArrowFunction_Part2_Transformation extends Transformation { - - - override analyze() { - - } - - override assertPreConditions() { - - } - - override assertPostConditions() { - if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { - "No arrow-function left in IM".assertTrue( collectNodes(state.im, ArrowFunction, true).isEmpty ); - } - } - - override transform() { - collectNodes(state.im, ArrowFunction, true).toList.forEach[transformArrowFunction]; - } - - /** replace arrow-function by function-expression */ - private def void transformArrowFunction(ArrowFunction arrowFunc ) { - - // PART 2 - val fe = _FunExpr(arrowFunc.async, arrowFunc.name, arrowFunc.fpars, arrowFunc.body); - // note: arrow functions cannot be generators, so we do *not* need to do: - // fe.generator = arrowFunc.generator; - - val thisBinder = _CallExpr( - _PropertyAccessExpr( - _Parenthesis( fe ), - getSymbolTableEntryForMember(state.G.functionType, "bind", false, false, true) - ), - _ThisLiteral - ); // end Call function*() - - replace(arrowFunc, thisBinder, fe); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/BlockTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/BlockTransformation.java new file mode 100644 index 0000000000..9e32609496 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/BlockTransformation.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableStatement; + +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.types.TVariable; + +/** + */ +public class BlockTransformation extends Transformation { + + /** Name for capturing local-arguments-environment in distinct variable on entering a block. */ + public static final String $CAPTURE_ARGS = "$capturedArgs"; + + @Override + public void assertPreConditions() { + // true + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + for (FunctionOrFieldAccessor fofa : collectNodes(getState().im, FunctionOrFieldAccessor.class, true)) { + transformArguments(fofa); + } + } + + /** capture arguments-variable to be accessible in re-written arrow-functions */ + private void transformArguments(FunctionOrFieldAccessor funcOrAccess) { + FunctionOrFieldAccessor fofa = getState().tracer.getOriginalASTNodeOfSameType(funcOrAccess, false); + if (fofa == null) { + return; + } + TVariable argsVar = fofa.getImplicitArgumentsVariable(); + if (argsVar == null) { + return; + } + + // 1. rename old IdentifierRef_IM pointing to --> arguments with fresh name. + // 2. introduce new IdentifierRef_IM with (new) name 'arguments' + // 3. wire up freshname to new arguments in first line of block. + String newName = $CAPTURE_ARGS; + + SymbolTableEntryOriginal arguments_STE = getSymbolTableEntryOriginal(argsVar, false); + if (arguments_STE == null || arguments_STE.getReferencingElements().isEmpty()) { + // no references to this local arguments variable -> no capturing required + return; + } + + // arguments_STE.referencingElements.empty // + also need to check if we contain a arrow-function accessing + // arguments. + + // 1.) RENAME + rename(arguments_STE, newName); + + // skip ArrowFunctions: (this is the main reason for the capturing here :-) + if (funcOrAccess instanceof ArrowFunction) + return; + // skip empty bodies: + if (funcOrAccess.getBody().getStatements().isEmpty()) + return; + + // 2 + 3.) CAPTURE arguments: + + Statement bodyFirstStatement = funcOrAccess.getBody().getStatements().get(0); + // note, there must be something in the body otherwise the 'arguments' variable could not have been accessed! + + // new SymbolTableEntry for 'real' arguments ( the other was renamed above ) + SymbolTableEntryInternal argumentsSTE = steFor_arguments(); + + insertBefore(bodyFirstStatement, + _VariableStatement(_VariableDeclaration( + newName, _IdentRef(argumentsSTE)))); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/BlockTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/BlockTransformation.xtend deleted file mode 100644 index 6cb1afd6ca..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/BlockTransformation.xtend +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.transpiler.Transformation - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - */ -class BlockTransformation extends Transformation { - - /** Name for capturing local-arguments-environment in distinct variable on entering a block.*/ - public static final String $CAPTURE_ARGS = "$capturedArgs"; - - - override assertPreConditions() { - // true - } - - override assertPostConditions() { - // true - } - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, FunctionOrFieldAccessor, true).toList.forEach[transformArguments]; - } - - /** capture arguments-variable to be accessible in re-written arrow-functions */ - private def void transformArguments(FunctionOrFieldAccessor funcOrAccess ) { - - val argsVar = state.tracer.getOriginalASTNodeOfSameType(funcOrAccess, false)?.implicitArgumentsVariable - if(argsVar === null) { - return; - } - - // 1. rename old IdentifierRef_IM pointing to --> arguments with fresh name. - // 2. introduce new IdentifierRef_IM with (new) name 'arguments' - // 3. wire up freshname to new arguments in first line of block. - val newName = $CAPTURE_ARGS; - - val arguments_STE = getSymbolTableEntryOriginal(argsVar, false); - if(arguments_STE===null || arguments_STE.referencingElements.empty) { - // no references to this local arguments variable -> no capturing required - return; - } - - // arguments_STE.referencingElements.empty // + also need to check if we contain a arrow-function accessing arguments. - - // 1.) RENAME - rename( arguments_STE, newName ); - - // skip ArrowFunctions: (this is the main reason for the capturing here :-) - if( funcOrAccess instanceof ArrowFunction ) return; - // skip empty bodies: - if( funcOrAccess.body.statements.empty ) return; - - // 2 + 3.) CAPTURE arguments: - - val bodyFirstStatement = funcOrAccess.body.statements.get(0); - // note, there must be something in the body otherwise the 'arguments' variable could not have been accessed! - - // new SymbolTableEntry for 'real' arguments ( the other was renamed above ) - val argumentsSTE = steFor_arguments() ; - - insertBefore( bodyFirstStatement, - _VariableStatement( _VariableDeclaration( - newName, _IdentRef( argumentsSTE ) - )) - ); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ClassDeclarationTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ClassDeclarationTransformation.java new file mode 100644 index 0000000000..d30b5affbc --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ClassDeclarationTransformation.java @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Block; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._LiteralOrComputedPropertyName; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4GetterDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.exists; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; + +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.GenericDeclaration; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassExpression; +import org.eclipse.n4js.n4JS.N4FieldAccessor; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.N4Modifier; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.RequiresBefore; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.es.assistants.ClassConstructorAssistant; +import org.eclipse.n4js.transpiler.es.assistants.ClassifierAssistant; +import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant; +import org.eclipse.n4js.transpiler.es.assistants.ReflectionAssistant; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.utils.TranspilerUtils; + +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +/** + * Transforms {@link N4ClassDeclaration}s into a constructor function and a $makeClass call. + *

+ * Dependencies: + *

    + *
  • requiresBefore {@link MemberPatchingTransformation}: additional members must be in place before they are + * transformed within this transformation. + *
+ */ +@RequiresBefore(MemberPatchingTransformation.class) +public class ClassDeclarationTransformation extends Transformation { + + @Inject + private ClassConstructorAssistant classConstructorAssistant; + @Inject + private ClassifierAssistant classifierAssistant; + @Inject + private ReflectionAssistant reflectionAssistant; + @Inject + private DelegationAssistant delegationAssistant; + @Inject + private TypeAssistant typeAssistant; + + @Override + public void assertPreConditions() { + typeAssistant.assertClassifierPreConditions(); + assertFalse("class expressions are not supported yet", + exists(getState().im.eAllContents(), el -> el instanceof N4ClassExpression)); + assertFalse("only top-level classes are supported, for now", + exists(collectNodes(getState().im, N4ClassDeclaration.class, false), + cd -> !typeAssistant.isTopLevel(cd))); + } + + @Override + public void assertPostConditions() { + // none + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + for (N4ClassDeclaration cd : collectNodes(getState().im, N4ClassDeclaration.class, false)) { + transformClassDecl(cd); + } + } + + private void transformClassDecl(N4ClassDeclaration classDecl) { + SymbolTableEntry classSTE = findSymbolTableEntryForElement(classDecl, false); + SymbolTableEntryOriginal superClassSTE = typeAssistant.getSuperClassSTE(classDecl); + LinkedHashSet fieldsRequiringExplicitDefinition = classifierAssistant + .findFieldsRequiringExplicitDefinition(classDecl); + + reflectionAssistant.addN4TypeGetter(classDecl, classDecl); + + classConstructorAssistant.amendConstructor(classDecl, classSTE, superClassSTE, + fieldsRequiringExplicitDefinition); + + List belowClassDecl = new ArrayList<>(); + belowClassDecl.addAll( + classifierAssistant.createExplicitFieldDefinitions(classSTE, true, fieldsRequiringExplicitDefinition)); + belowClassDecl.addAll(classifierAssistant.createStaticFieldInitializations(classDecl, classSTE, + fieldsRequiringExplicitDefinition)); + belowClassDecl.addAll(createAdditionalClassDeclarationCode()); + insertAfter(TranspilerUtils.orContainingExportDeclaration(classDecl), belowClassDecl.toArray(new Statement[0])); + + removeFieldsAndAbstractMembers(classDecl); + delegationAssistant.replaceDelegatingMembersByOrdinaryMembers(classDecl); + removeTypeInformation(classDecl); + + ArrayLiteral implementedInterfaces = createDirectlyImplementedInterfaces(classDecl); + String $implements = steFor_$implementsInterfaces().getName(); + if (!implementedInterfaces.getElements().isEmpty()) { + N4GetterDeclaration newGetter = _N4GetterDecl( + _LiteralOrComputedPropertyName($implements), + _Block( + _ReturnStmnt(implementedInterfaces))); + newGetter.getDeclaredModifiers().add(N4Modifier.STATIC); + classDecl.getOwnedMembersRaw().add(newGetter); + } + + // change superClassRef to an equivalent extends-expression + // (this is a minor quirk required because superClassRef is not supported by the PrettyPrinterSwitch; + // for details see PrettyPrinterSwitch#caseN4ClassDeclaration()) + classDecl.setSuperClassRef(null); + classDecl.setSuperClassExpression(__NSSafe_IdentRef(superClassSTE)); + } + + /** Removes fields and abstract members (they do not have a representation in the output code). */ + private void removeFieldsAndAbstractMembers(N4ClassDeclaration classDecl) { + classDecl.getOwnedMembersRaw().removeIf(m -> { + if (m instanceof N4FieldDeclaration) { + return true; + } + if (m instanceof N4FieldAccessor) { + return m.isAbstract(); + } + if (m instanceof N4MethodDeclaration) { + return m.isAbstract(); + } + return false; + }); + } + + private void removeTypeInformation(N4ClassDeclaration classDecl) { + for (N4MemberDeclaration currMember : classDecl.getOwnedMembersRaw()) { + if (currMember instanceof GenericDeclaration) { + ((GenericDeclaration) currMember).getTypeVars().clear(); + } + if (currMember instanceof N4GetterDeclaration) { + ((N4GetterDeclaration) currMember).setDeclaredTypeRefNode(null); + } + if (currMember instanceof N4MethodDeclaration) { + ((N4MethodDeclaration) currMember).setDeclaredReturnTypeRefNode(null); + } + } + } + + /** Override to add additional output code directly after the default class declaration output code. */ + private List createAdditionalClassDeclarationCode() { + return Collections.emptyList(); // no additional statements by default + } + + private ArrayLiteral createDirectlyImplementedInterfaces(N4ClassDeclaration classDecl) { + List interfaces = typeAssistant.getSuperInterfacesSTEs(classDecl); + + // the return value of this method is intended for default method patching; for this purpose, we have to + // filter out some of the directly implemented interfaces: + Iterable directlyImplementedInterfacesFiltered = TranspilerUtils + .filterNominalInterfaces(interfaces); + return _ArrLit(Iterables.toArray(map(directlyImplementedInterfacesFiltered, ste -> _IdentRef(ste)), + IdentifierRef.class)); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ClassDeclarationTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ClassDeclarationTransformation.xtend deleted file mode 100644 index 309077e2b7..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ClassDeclarationTransformation.xtend +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import java.util.List -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.GenericDeclaration -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassExpression -import org.eclipse.n4js.n4JS.N4FieldAccessor -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.n4JS.N4Modifier -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.RequiresBefore -import org.eclipse.n4js.transpiler.assistants.TypeAssistant -import org.eclipse.n4js.transpiler.es.assistants.ClassConstructorAssistant -import org.eclipse.n4js.transpiler.es.assistants.ClassifierAssistant -import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant -import org.eclipse.n4js.transpiler.es.assistants.ReflectionAssistant -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.transpiler.utils.TranspilerUtils - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.transpiler.utils.TranspilerUtils.* - -/** - * Transforms {@link N4ClassDeclaration}s into a constructor function and a $makeClass call. - *

- * Dependencies: - *

    - *
  • requiresBefore {@link MemberPatchingTransformation}: - * additional members must be in place before they are transformed within this transformation. - *
- */ -@RequiresBefore(MemberPatchingTransformation) -class ClassDeclarationTransformation extends Transformation { - - @Inject private ClassConstructorAssistant classConstructorAssistant; - @Inject private ClassifierAssistant classifierAssistant; - @Inject private ReflectionAssistant reflectionAssistant; - @Inject private DelegationAssistant delegationAssistant; - @Inject private TypeAssistant typeAssistant; - - override assertPreConditions() { - typeAssistant.assertClassifierPreConditions(); - assertFalse("class expressions are not supported yet", - state.im.eAllContents.exists[it instanceof N4ClassExpression]); - assertFalse("only top-level classes are supported, for now", - collectNodes(state.im, N4ClassDeclaration, false).exists[!typeAssistant.isTopLevel(it)]); - } - - override assertPostConditions() { - // none - } - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, N4ClassDeclaration, false).forEach[transformClassDecl]; - } - - def private void transformClassDecl(N4ClassDeclaration classDecl) { - val classSTE = findSymbolTableEntryForElement(classDecl, false); - val superClassSTE = typeAssistant.getSuperClassSTE(classDecl); - val fieldsRequiringExplicitDefinition = classifierAssistant.findFieldsRequiringExplicitDefinition(classDecl); - - reflectionAssistant.addN4TypeGetter(classDecl, classDecl); - - classConstructorAssistant.amendConstructor(classDecl, classSTE, superClassSTE, fieldsRequiringExplicitDefinition); - - val belowClassDecl = newArrayList; - belowClassDecl += classifierAssistant.createExplicitFieldDefinitions(classSTE, true, fieldsRequiringExplicitDefinition); - belowClassDecl += classifierAssistant.createStaticFieldInitializations(classDecl, classSTE, fieldsRequiringExplicitDefinition); - belowClassDecl += createAdditionalClassDeclarationCode(classDecl, classSTE); - insertAfter(classDecl.orContainingExportDeclaration, belowClassDecl); - - removeFieldsAndAbstractMembers(classDecl); - delegationAssistant.replaceDelegatingMembersByOrdinaryMembers(classDecl); - removeTypeInformation(classDecl); - - val implementedInterfaces = createDirectlyImplementedInterfaces(classDecl); - val $implements = steFor_$implementsInterfaces.name; - if (!implementedInterfaces.elements.empty) { - classDecl.ownedMembersRaw += _N4GetterDecl( - _LiteralOrComputedPropertyName($implements), - _Block( - _ReturnStmnt(implementedInterfaces) - ) - ) => [ - declaredModifiers += N4Modifier.STATIC; - ]; - } - - // change superClassRef to an equivalent extends-expression - // (this is a minor quirk required because superClassRef is not supported by the PrettyPrinterSwitch; - // for details see PrettyPrinterSwitch#caseN4ClassDeclaration()) - classDecl.superClassRef = null; - classDecl.superClassExpression = __NSSafe_IdentRef(superClassSTE); - } - - /** Removes fields and abstract members (they do not have a representation in the output code). */ - def private void removeFieldsAndAbstractMembers(N4ClassDeclaration classDecl) { - classDecl.ownedMembersRaw.removeIf[m| - switch (m) { - N4FieldDeclaration: true - N4FieldAccessor: m.isAbstract() - N4MethodDeclaration: m.isAbstract() - default: false - } - ]; - } - - def private void removeTypeInformation(N4ClassDeclaration classDecl) { - for (currMember : classDecl.ownedMembersRaw) { - if (currMember instanceof GenericDeclaration) { - currMember.typeVars.clear(); - } - switch (currMember) { - N4GetterDeclaration: - currMember.declaredTypeRefNode = null - N4MethodDeclaration: - currMember.declaredReturnTypeRefNode = null - } - } - } - - /** Override to add additional output code directly after the default class declaration output code. */ - def protected List createAdditionalClassDeclarationCode(N4ClassDeclaration classDecl, SymbolTableEntry classSTE) { - return #[]; // no additional statements by default - } - - def public ArrayLiteral createDirectlyImplementedInterfaces(N4ClassDeclaration classDecl) { - val interfaces = typeAssistant.getSuperInterfacesSTEs(classDecl); - - // the return value of this method is intended for default method patching; for this purpose, we have to - // filter out some of the directly implemented interfaces: - val directlyImplementedInterfacesFiltered = TranspilerUtils.filterNominalInterfaces(interfaces); - return _ArrLit( directlyImplementedInterfacesFiltered.map[ _IdentRef(it) ] ); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.java new file mode 100644 index 0000000000..57b9c7168e --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.java @@ -0,0 +1,291 @@ +/** + * Copyright (c) 2021 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ConditionalExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._LiteralOrComputedPropertyName; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Parenthesis; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableBinding; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableStatement; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.drop; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.n4JS.BindingElement; +import org.eclipse.n4js.n4JS.BindingProperty; +import org.eclipse.n4js.n4JS.DefaultImportSpecifier; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.ModuleSpecifierForm; +import org.eclipse.n4js.n4JS.N4JSFactory; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.n4JS.VariableStatementKeyword; +import org.eclipse.n4js.packagejson.PackageJsonProperties; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesAfter; +import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesBefore; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.utils.N4JSLanguageHelper; +import org.eclipse.n4js.utils.ProjectDescriptionUtils; +import org.eclipse.n4js.utils.Strings; +import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot; +import org.eclipse.n4js.workspace.WorkspaceAccess; + +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableListMultimap; +import com.google.inject.Inject; + +/** + * Since switching to node's native support for ES6 modules, we have to re-write all import declarations that import + * from an old CommonJS module. Note that this behavior can be turned on/off in the package.json file, see + * {@link PackageJsonProperties#GENERATOR_REWRITE_CJS_IMPORTS}. + */ +@ExcludesAfter(SanitizeImportsTransformation.class) // CommonJsImportsTransformation must not run before + // SanitizeImportsTransformation +@ExcludesBefore(ModuleWrappingTransformation.class) // CommonJsImportsTransformation must not run after + // ModuleWrappingTransformation +public class CommonJsImportsTransformation extends Transformation { + + @Inject + private N4JSLanguageHelper n4jsLanguageHelper; + + @Inject + private WorkspaceAccess workspaceAccess; + + @Override + public void assertPreConditions() { + // true + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + // nothing to be done + } + + @Override + public void transform() { + if (!getState().project.getProjectDescription().isGeneratorEnabledRewriteCjsImports()) { + // rewriting of CJS imports is not enabled for the containing project + return; + } + + ImmutableListMultimap importDeclsPerImportedModule = FluentIterable + .from(getState().im.getScriptElements()) + .filter(ImportDeclaration.class) + .filter(id -> !id.isBare()) // ignore bare imports + .index(importDecl -> getState().info.getImportedModule(importDecl)); + + List varStmnts = new ArrayList<>(); + for (TModule targetModule : importDeclsPerImportedModule.keySet()) { + varStmnts.addAll(transformImportDecl(targetModule, importDeclsPerImportedModule.get(targetModule))); + } + + ImportDeclaration lastImportDecl = last(filter(getState().im.getScriptElements(), ImportDeclaration.class)); + insertAfter(lastImportDecl, varStmnts.toArray(new VariableStatement[0])); + } + + /** + * For those of the given imports that actually require rewriting, this method will change them in place *and* + * return one or more variable statements that have to be inserted (by the client code) after all imports. + *

+ * For example: this method will rewrite the following imports + * + *

+	 * import defaultImport+ from "plainJsModule"
+	 * import {namedImport1+} from "plainJsModule"
+	 * import {namedImport2+} from "plainJsModule"
+	 * import * as NamespaceImport+ from "plainJsModule"
+	 * 
+ * + * to this import: + * + *
+	 * import $tempVar from './plainJsModule.cjs'
+	 * 
+ * + * and will return these variable statements: + * + *
+	 * const defaultImport = ($tempVar?.__esModule ? $tempVar.default : $tempVar);
+	 * const NamespaceImport = $tempVar;
+	 * const {
+	 *     namedImport1,
+	 *     namedImport2
+	 * } = $tempVar;
+	 * 
+ */ + private List transformImportDecl(TModule targetModule, + List allImportDeclsForThisModule) { + if (allImportDeclsForThisModule.isEmpty()) { + return Collections.emptyList(); + } + if (!requiresRewrite(targetModule)) { + return Collections.emptyList(); + } + + List importDeclsToRewrite = new ArrayList<>(allImportDeclsForThisModule); + if (exists(importDeclsToRewrite, id -> id.getModuleSpecifierForm() == ModuleSpecifierForm.PROJECT)) { + N4JSProjectConfigSnapshot targetProject = n4jsLanguageHelper.replaceDefinitionProjectByDefinedProject( + getState().resource, workspaceAccess.findProjectContaining(targetModule), true); + if (targetProject != null && targetProject.getProjectDescription().hasModuleProperty()) { + // don't rewrite project imports in case the target project has a top-level property "module" in its + // package.json, + // because in that case project imports will be redirected to an esm-ready file: + importDeclsToRewrite.removeIf(id -> id.getModuleSpecifierForm() == ModuleSpecifierForm.PROJECT); + } + } + if (importDeclsToRewrite.isEmpty()) { + return Collections.emptyList(); + } + + // special case with a simpler approach: + if (importDeclsToRewrite.size() == 1 && importDeclsToRewrite.get(0).getImportSpecifiers().size() == 1) { + ImportSpecifier importSpec = importDeclsToRewrite.get(0).getImportSpecifiers().get(0); + if (importSpec instanceof NamespaceImportSpecifier) { + // in this case we can simply replace the namespace import by a default import + String namespaceName = ((NamespaceImportSpecifier) importSpec).getAlias(); + DefaultImportSpecifier newImportSpec = N4JSFactory.eINSTANCE.createDefaultImportSpecifier(); + newImportSpec.setImportedElementAsText(namespaceName); + newImportSpec.setFlaggedUsedInCode(true); + newImportSpec.setRetainedAtRuntime(true); + // NOTE: don't copy the following lines to other places! Use methods #replace() instead! + getState().tracer.copyTrace(importSpec, newImportSpec); + insertAfter(importSpec, newImportSpec); + remove(importSpec); + return Collections.emptyList(); + } + } + + String tempVarName = computeNameForIntermediateDefaultImport(targetModule); + SymbolTableEntryInternal tempVarSTE = getSymbolTableEntryInternal(tempVarName, true); + + List varDecls = new ArrayList<>(); + List bindingProps = new ArrayList<>(); + + createVarDeclsOrBindings(importDeclsToRewrite, tempVarSTE, varDecls, bindingProps); + + List result = new ArrayList<>(); + for (VariableDeclaration varDecl : varDecls) { + result.add(_VariableStatement(VariableStatementKeyword.CONST, varDecl)); + } + if (!bindingProps.isEmpty()) { + result.add(_VariableStatement(VariableStatementKeyword.CONST, + _VariableBinding(bindingProps, _IdentRef(tempVarSTE)))); + } + + ImportDeclaration firstImportDecl = importDeclsToRewrite.get(0); + removeAll(firstImportDecl.getImportSpecifiers()); + DefaultImportSpecifier dsi = N4JSFactory.eINSTANCE.createDefaultImportSpecifier(); + firstImportDecl.getImportSpecifiers().add(dsi); + dsi.setImportedElementAsText(tempVarSTE.getName()); + dsi.setFlaggedUsedInCode(true); + dsi.setRetainedAtRuntime(true); + removeAll(drop(importDeclsToRewrite, 1)); + + return result; + } + + private void createVarDeclsOrBindings(List importDecls, SymbolTableEntry steTempVar, + List varDecls, List bindingProps) { + + SymbolTableEntry steEsModule = null; + SymbolTableEntry steDefault = null; + for (ImportDeclaration importDecl : importDecls) { + for (ImportSpecifier importSpec : importDecl.getImportSpecifiers()) { + if (importSpec instanceof NamedImportSpecifier) { + NamedImportSpecifier nis = (NamedImportSpecifier) importSpec; + String importedName = nis.getImportedElementAsText(); + String localName = nis.getAlias() != null ? nis.getAlias() : importedName; + boolean isDefaultImport = nis.isDefaultImport() || importedName == "default"; + if (isDefaultImport) { + if (steEsModule == null) { + steEsModule = steFor_interopProperty_esModule(); + } + if (steDefault == null) { + steDefault = getSymbolTableEntryInternal("default", true); + } + ParameterizedPropertyAccessExpression_IM pae = _PropertyAccessExpr(steTempVar, + steFor_interopProperty_esModule()); + pae.setOptionalChaining(true); + varDecls.add(_VariableDeclaration(localName, _Parenthesis(_ConditionalExpr( + pae, + _PropertyAccessExpr(steTempVar, steDefault), + _IdentRef(steTempVar))))); + } else { + BindingProperty bp = N4JSFactory.eINSTANCE.createBindingProperty(); + bindingProps.add(bp); + if (localName != importedName) { + bp.setDeclaredName(_LiteralOrComputedPropertyName(importedName)); + } + BindingElement be = N4JSFactory.eINSTANCE.createBindingElement(); + bp.setValue(be); + be.setVarDecl(_VariableDeclaration(localName)); + } + } else if (importSpec instanceof NamespaceImportSpecifier) { + NamespaceImportSpecifier nis = (NamespaceImportSpecifier) importSpec; + String namespaceName = nis.getAlias(); + varDecls.add(_VariableDeclaration(namespaceName, _IdentRef(steTempVar))); + } else { + throw new IllegalStateException( + "unsupported subclass of ImportSpecifier: " + importSpec.eClass().getName()); + } + } + } + } + + private boolean requiresRewrite(TModule targetModule) { + return !n4jsLanguageHelper.isES6Module(getState().index, targetModule); + } + + private String computeNameForIntermediateDefaultImport(TModule targetModule) { + String packageNameRaw = targetModule.getPackageName(); + if (packageNameRaw != null) { + String n4jsdScopeWithSep = N4JSGlobals.N4JSD_SCOPE + ProjectDescriptionUtils.NPM_SCOPE_SEPARATOR; + if (packageNameRaw.startsWith(n4jsdScopeWithSep)) { + packageNameRaw = packageNameRaw.substring(n4jsdScopeWithSep.length()); + } + } + + String packageName = Strings.toIdentifier(packageNameRaw, '_'); + String moduleName = Strings.toIdentifier(targetModule.getQualifiedName(), '_'); + String baseName = "$cjsImport__" + packageName + "__" + moduleName; + + int idx = 0; + String candidate = baseName; + // TODO: we won't find name conflicts with SymbolTableEntryOriginals (requires refactoring in + // TranspilerState.STECache) + while (getSymbolTableEntryInternal(candidate, false) != null) { + idx++; + candidate = baseName + idx; + } + return candidate; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.xtend deleted file mode 100644 index a7de33852e..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.xtend +++ /dev/null @@ -1,254 +0,0 @@ -/** - * Copyright (c) 2021 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.common.collect.FluentIterable -import com.google.inject.Inject -import java.util.ArrayList -import java.util.List -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.n4JS.BindingProperty -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ModuleSpecifierForm -import org.eclipse.n4js.n4JS.N4JSFactory -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.n4JS.VariableStatementKeyword -import org.eclipse.n4js.packagejson.PackageJsonProperties -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesAfter -import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesBefore -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.utils.N4JSLanguageHelper -import org.eclipse.n4js.utils.ProjectDescriptionUtils -import org.eclipse.n4js.utils.Strings -import org.eclipse.n4js.workspace.WorkspaceAccess - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Since switching to node's native support for ES6 modules, we have to re-write all import declarations - * that import from an old CommonJS module. Note that this behavior can be turned on/off in the package.json - * file, see {@link PackageJsonProperties#GENERATOR_CJS_DEFAULT_IMPORTS}. - */ -@ExcludesAfter(SanitizeImportsTransformation) // CommonJsImportsTransformation must not run before SanitizeImportsTransformation -@ExcludesBefore(ModuleWrappingTransformation) // CommonJsImportsTransformation must not run after ModuleWrappingTransformation -class CommonJsImportsTransformation extends Transformation { - - @Inject - private N4JSLanguageHelper n4jsLanguageHelper; - - @Inject - private WorkspaceAccess workspaceAccess; - - override void assertPreConditions() { - // true - } - - override void assertPostConditions() { - // true - } - - override void analyze() { - // nothing to be done - } - - override void transform() { - if (!state.project.projectDescription.generatorEnabledRewriteCjsImports) { - // rewriting of CJS imports is not enabled for the containing project - return; - } - - val importDeclsPerImportedModule = FluentIterable.from(state.im.scriptElements) - .filter(ImportDeclaration) - .filter[!bare] // ignore bare imports - .index[importDecl | state.info.getImportedModule(importDecl)]; - - val varStmnts = newArrayList; - for (targetModule : importDeclsPerImportedModule.keySet) { - varStmnts += transformImportDecl(targetModule, importDeclsPerImportedModule.get(targetModule)); - } - - val lastImportDecl = state.im.scriptElements.filter(ImportDeclaration).last(); - insertAfter(lastImportDecl, varStmnts); - } - - /** - * For those of the given imports that actually require rewriting, this method will change them in place *and* return one or more - * variable statements that have to be inserted (by the client code) after all imports. - *

- * For example: this method will rewrite the following imports - *

-	 * import defaultImport+ from "plainJsModule"
-	 * import {namedImport1+} from "plainJsModule"
-	 * import {namedImport2+} from "plainJsModule"
-	 * import * as NamespaceImport+ from "plainJsModule"
-	 * 
- * to this import: - *
-	 * import $tempVar from './plainJsModule.cjs'
-	 * 
- * and will return these variable statements: - *
-	 * const defaultImport = ($tempVar?.__esModule ? $tempVar.default : $tempVar);
-	 * const NamespaceImport = $tempVar;
-	 * const {
-	 *     namedImport1,
-	 *     namedImport2
-	 * } = $tempVar;
-	 * 
- */ - def private List transformImportDecl(TModule targetModule, List allImportDeclsForThisModule) { - if (allImportDeclsForThisModule.empty) { - return #[]; - } - if (!requiresRewrite(targetModule)) { - return #[]; - } - - val importDeclsToRewrite = new ArrayList(allImportDeclsForThisModule); - if (importDeclsToRewrite.exists[moduleSpecifierForm === ModuleSpecifierForm.PROJECT]) { - val targetProject = n4jsLanguageHelper.replaceDefinitionProjectByDefinedProject(state.resource, - workspaceAccess.findProjectContaining(targetModule), true); - if (targetProject !== null && targetProject.projectDescription.hasModuleProperty) { - // don't rewrite project imports in case the target project has a top-level property "module" in its package.json, - // because in that case project imports will be redirected to an esm-ready file: - importDeclsToRewrite.removeIf[moduleSpecifierForm === ModuleSpecifierForm.PROJECT]; - } - } - if (importDeclsToRewrite.empty) { - return #[]; - } - - // special case with a simpler approach: - if (importDeclsToRewrite.size === 1 && importDeclsToRewrite.head.importSpecifiers.size === 1) { - val importSpec = importDeclsToRewrite.head.importSpecifiers.head; - if (importSpec instanceof NamespaceImportSpecifier) { - // in this case we can simply replace the namespace import by a default import - val namespaceName = importSpec.alias; - val newImportSpec = N4JSFactory.eINSTANCE.createDefaultImportSpecifier() => [ - importedElementAsText = namespaceName; - flaggedUsedInCode = true; - retainedAtRuntime = true; - ]; - // NOTE: don't copy the following lines to other places! Use methods #replace() instead! - state.tracer.copyTrace(importSpec, newImportSpec); - insertAfter(importSpec, newImportSpec); - remove(importSpec); - return #[] - } - } - - val tempVarName = computeNameForIntermediateDefaultImport(targetModule); - val tempVarSTE = getSymbolTableEntryInternal(tempVarName, true); - - val varDecls = newArrayList; - val bindingProps = newArrayList; - - createVarDeclsOrBindings(importDeclsToRewrite, tempVarSTE, varDecls, bindingProps); - - val result = newArrayList; - for (varDecl : varDecls) { - result += _VariableStatement(VariableStatementKeyword.CONST, varDecl); - } - if (!bindingProps.empty) { - result += _VariableStatement(VariableStatementKeyword.CONST, _VariableBinding(bindingProps, _IdentRef(tempVarSTE))); - } - - val firstImportDecl = importDeclsToRewrite.head; - removeAll(firstImportDecl.importSpecifiers); - firstImportDecl.importSpecifiers += N4JSFactory.eINSTANCE.createDefaultImportSpecifier() => [ - importedElementAsText = tempVarSTE.name; - flaggedUsedInCode = true; - retainedAtRuntime = true; - ]; - removeAll(importDeclsToRewrite.drop(1)); - - return result; - } - - def private void createVarDeclsOrBindings(List importDecls, SymbolTableEntry steTempVar, - List varDecls, List bindingProps) { - - var steEsModule = null as SymbolTableEntry; - var steDefault = null as SymbolTableEntry; - for (importDecl : importDecls) { - for (importSpec : importDecl.importSpecifiers) { - switch (importSpec) { - NamedImportSpecifier: { // including DefaultImportSpecifier - val importedName = importSpec.importedElementAsText; - val localName = importSpec.alias ?: importedName; - val isDefaultImport = importSpec.isDefaultImport || importedName == "default"; - if (isDefaultImport) { - if (steEsModule === null) { - steEsModule = steFor_interopProperty_esModule(); - } - if (steDefault === null) { - steDefault = getSymbolTableEntryInternal("default", true); - } - varDecls += _VariableDeclaration(localName, _Parenthesis(_ConditionalExpr( - _PropertyAccessExpr(steTempVar, steFor_interopProperty_esModule) => [ optionalChaining = true ], - _PropertyAccessExpr(steTempVar, steDefault), - _IdentRef(steTempVar) - ))); - } else { - bindingProps += N4JSFactory.eINSTANCE.createBindingProperty => [ newBindingProp | - if (localName != importedName) { - newBindingProp.declaredName = _LiteralOrComputedPropertyName(importedName); - } - newBindingProp.value = N4JSFactory.eINSTANCE.createBindingElement => [ newBindingElem | - newBindingElem.varDecl = _VariableDeclaration(localName); - ] - ]; - } - } - NamespaceImportSpecifier: { - val namespaceName = importSpec.alias; - varDecls += _VariableDeclaration(namespaceName, _IdentRef(steTempVar)); - } - default: { - throw new IllegalStateException("unsupported subclass of ImportSpecifier: " + importSpec.eClass.name); - } - } - } - } - } - - def private boolean requiresRewrite(TModule targetModule) { - return !n4jsLanguageHelper.isES6Module(state.index, targetModule); - } - - def private String computeNameForIntermediateDefaultImport(TModule targetModule) { - var packageNameRaw = targetModule.packageName; - if (packageNameRaw !== null) { - val n4jsdScopeWithSep = N4JSGlobals.N4JSD_SCOPE + ProjectDescriptionUtils.NPM_SCOPE_SEPARATOR; - if (packageNameRaw.startsWith(n4jsdScopeWithSep)) { - packageNameRaw = packageNameRaw.substring(n4jsdScopeWithSep.length); - } - } - - val packageName = Strings.toIdentifier(packageNameRaw, '_'); - val moduleName = Strings.toIdentifier(targetModule.qualifiedName, '_'); - val baseName = "$cjsImport__" + packageName + "__" + moduleName; - - var idx = 0; - var candidate = baseName; - // TODO: we won't find name conflicts with SymbolTableEntryOriginals (requires refactoring in TranspilerState.STECache) - while (getSymbolTableEntryInternal(candidate, false) !== null) { - idx++; - candidate = baseName + idx; - } - return candidate; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DependencyInjectionTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DependencyInjectionTransformation.java new file mode 100644 index 0000000000..77cf19b81a --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DependencyInjectionTransformation.java @@ -0,0 +1,296 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static com.google.common.collect.Iterables.toArray; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.findParentDIC; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.hasParentInjector; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.isBinder; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.isDIComponent; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.isInjectedClass; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.isSingleton; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.resolveBinders; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrayElement; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ExprStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ObjLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyGetterDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyNameValuePair; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteralForSTE; +import static org.eclipse.n4js.transpiler.utils.TranspilerUtils.orContainingExportDeclaration; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.objectType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.symbolObjectType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.tooling.organizeImports.DIUtility; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TAnnotationTypeRefArgument; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TN4Classifier; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.utils.DeclMergingHelper; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.inject.Inject; + +/** + * Generates DI meta information for the injector. Information is attached to the compiled type in the + * {@link N4JSLanguageConstants#DI_SYMBOL_KEY DI_SYMBOL_KEY} property. + * + * TODO the DI code below makes heavy use of TModule (probably) without true need; refactor this + */ +public class DependencyInjectionTransformation extends Transformation { + + @Inject + private DeclMergingHelper declMergingHelper; + + @Override + public void assertPreConditions() { + // true + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + for (N4ClassDeclaration classDecl : collectNodes(getState().im, N4ClassDeclaration.class, false)) { + TClass tClass = getState().info.getOriginalDefinedType(classDecl); + if (tClass != null) { + Statement codeForDI = generateCodeForDI(tClass, classDecl); + if (codeForDI != null) { + insertAfter(orContainingExportDeclaration(classDecl), codeForDI); + } + } + } + } + + /** + * Generates hooks for DI mechanisms. Mainly N4Injector (part of n4js libraries) depends on those hooks. Note that + * those in many cases require proper types to be imported, which makes those hooks indirectly depended on behavior + * of the script transpiler policy that decides to remove "unused" imported symbols. That one has to leave imported + * symbols that are refereed to in code generated here. + * + * Also, note second parameter passed is name or alias of the super type. passing it from caller, even if we don't + * use it, is a shortcut to avoid computing it again here. + */ + @SuppressWarnings("unchecked") + private Statement generateCodeForDI(TClass tClass, N4ClassDeclaration classDecl) { + List> propertiesForDI = createPropertiesForDI(tClass); + if (propertiesForDI.isEmpty()) { + return null; + } + // Object.defineProperty(C, Symbol.for(''), {value: { ... }} + SymbolTableEntry classSTE = findSymbolTableEntryForElement(classDecl, true); + SymbolTableEntryOriginal objectSTE = getSymbolTableEntryOriginal(objectType(getState().G), true); + SymbolTableEntryOriginal definePropertySTE = getSymbolTableEntryForMember(objectType(getState().G), + "defineProperty", false, true, true); + SymbolTableEntryOriginal symbolObjectSTE = getSymbolTableEntryOriginal(symbolObjectType(getState().G), true); + SymbolTableEntryOriginal forSTE = getSymbolTableEntryForMember(symbolObjectType(getState().G), "for", false, + true, true); + return _ExprStmnt(_CallExpr( + _PropertyAccessExpr(objectSTE, definePropertySTE), + _IdentRef(classSTE), + _CallExpr(_PropertyAccessExpr(symbolObjectSTE, forSTE), + _StringLiteral(N4JSLanguageConstants.DI_SYMBOL_KEY)), + _ObjLit(Pair.of("value", _ObjLit(propertiesForDI.toArray(new Pair[0])))))); + } + + private List> createPropertiesForDI(TClass tClass) { + List> result = new ArrayList<>(); + if (isBinder(tClass)) { + result.add(Pair.of("bindings", (Expression) generateBindingPairs(tClass))); + result.add(Pair.of("methodBindings", (Expression) generateMethodBindings(tClass))); + result.addAll(injectionPointsMetaInfo(tClass)); + } else if (isDIComponent(tClass)) { + if (hasParentInjector(tClass)) { + SymbolTableEntryOriginal parentDIC_STE = getSymbolTableEntryOriginal(findParentDIC(tClass), true); + result.add(Pair.of("parent", (Expression) __NSSafe_IdentRef(parentDIC_STE))); + } + result.add(Pair.of("binders", (Expression) generateBinders(tClass))); + result.addAll(injectionPointsMetaInfo(tClass)); + } else if (isInjectedClass(tClass, declMergingHelper)) { + result.addAll(injectionPointsMetaInfo(tClass)); + } + return result; + } + + /** + * Generate DI hooks for scopes, super type, injected ctor, injected fields + */ + private List> injectionPointsMetaInfo(TClass tClass) { + ArrayLiteral fieldInjectionValue = fieldInjection(tClass); + List> result = new ArrayList<>(); + if (isSingleton(tClass)) { + result.add(Pair.of("scope", _StringLiteral("Singleton"))); + } + TMethod ownedCtor = tClass.getOwnedCtor(); + if (ownedCtor != null && AnnotationDefinition.INJECT.hasAnnotation(ownedCtor)) { + result.add(Pair.of("injectCtorParams", methodInjectedParams(ownedCtor))); + } + if (!fieldInjectionValue.getElements().isEmpty()) { + result.add(Pair.of("fieldsInjectedTypes", fieldInjection(tClass))); + } + return result; + } + + /** + * Generate injection info for method annotated with {@link AnnotationDefinition#INJECT}. If method has no method + * parameters returned value is empty string, otherwise description of parameters is returned. + */ + private ArrayLiteral methodInjectedParams(TMethod tMethod) { + ArrayLiteral result = _ArrLit(); + for (TFormalParameter fpar : tMethod.getFpars()) { + SymbolTableEntryOriginal fparSTE = getSymbolTableEntryOriginal(fpar, true); + List pAs = new ArrayList<>(); + pAs.add(_PropertyNameValuePair("name", _StringLiteralForSTE(fparSTE))); + pAs.addAll(generateTypeInfo(fpar.getTypeRef())); + result.getElements().add(_ArrayElement(_ObjLit(pAs.toArray(new PropertyAssignment[0])))); + } + return result; + } + + /** + * Generate injection info for fields annotated with {@link AnnotationDefinition#INJECT}. + */ + private ArrayLiteral fieldInjection(TClass tClass) { + ArrayLiteral result = _ArrLit(); + for (TField field : getOwnedInejctedFields(tClass)) { + SymbolTableEntryOriginal fieldSTE = getSymbolTableEntryOriginal(field, true); + List pAs = new ArrayList<>(); + pAs.add(_PropertyNameValuePair("name", _StringLiteralForSTE(fieldSTE))); + pAs.addAll(generateTypeInfo(field.getTypeRef())); + result.getElements().add(_ArrayElement(_ObjLit(pAs.toArray(new PropertyAssignment[0])))); + } + return result; + } + + /** + * Generate injection info from {@link AnnotationDefinition#BIND} annotations on the provided class. + */ + @SuppressWarnings("unchecked") + private ArrayLiteral generateBindingPairs(TClass tClass) { + ArrayLiteral result = _ArrLit(); + for (Pair binding : getBindingPairs(tClass)) { + SymbolTableEntryOriginal keySTE = getSymbolTableEntryOriginal(binding.getKey(), true); + SymbolTableEntryOriginal valueSTE = getSymbolTableEntryOriginal(binding.getValue(), true); + result.getElements().add(_ArrayElement(_ObjLit( + Pair.of("from", __NSSafe_IdentRef(keySTE)), + Pair.of("to", __NSSafe_IdentRef(valueSTE))))); + } + return result; + } + + /** + * Generate injection info for methods annotated with {@link AnnotationDefinition#PROVIDES}. Returned information + * contains returned type, name and formal parameters of the method. + */ + private ArrayLiteral generateMethodBindings(TClass tClass) { + ArrayLiteral result = _ArrLit(); + for (TMethod method : getOwnedProviderMethods(tClass)) { + result.getElements().add(_ArrayElement(_ObjLit( + generateTypeInfo(method.getReturnTypeRef(), "to").get(0), + _PropertyNameValuePair("name", _StringLiteral(method.getName())), + _PropertyNameValuePair("args", methodInjectedParams(method))))); + } + return result; + } + + private ArrayLiteral generateBinders(TClass tClass) { + ArrayLiteral result = _ArrLit(); + for (Type binderType : resolveBinders(tClass)) { + SymbolTableEntryOriginal binderTypeSTE = getSymbolTableEntryOriginal(binderType, true); + result.getElements().add(_ArrayElement( + __NSSafe_IdentRef(binderTypeSTE))); + } + return result; + } + + /** + * Generate type information for DI. Mainly FQN of the {@link TypeRef}, or composed information if given type is + * generic. + */ + private List generateTypeInfo(TypeRef typeRef) { + return generateTypeInfo(typeRef, "type"); + } + + private List generateTypeInfo(TypeRef typeRef, String propertyName) { + if (!DIUtility.isProviderType(typeRef)) { + SymbolTableEntryOriginal declaredTypeSTE = getSymbolTableEntryOriginal(typeRef.getDeclaredType(), true); + return List.of( + _PropertyGetterDecl(propertyName, _ReturnStmnt(__NSSafe_IdentRef(declaredTypeSTE)))); + } else if (typeRef instanceof ParameterizedTypeRef) { + // typeRef should be N4Provider, fqn needs to be obtained at runtime + SymbolTableEntryOriginal declaredTypeSTE = getSymbolTableEntryOriginal(typeRef.getDeclaredType(), true); + return List.of( + _PropertyGetterDecl(propertyName, _ReturnStmnt(__NSSafe_IdentRef(declaredTypeSTE))), + _PropertyNameValuePair("typeVar", _ObjLit( + toArray(generateTypeInfo(head(filter(typeRef.getDeclaredTypeArgs(), TypeRef.class))), + PropertyAssignment.class)))); + } else { + String msg = typeRef == null ? "" + : (typeRef.getDeclaredType() == null ? "" : typeRef.getDeclaredType().getName()); + throw new IllegalStateException("cannot generate type info for " + msg); + // note: at this point, the old transpiler did only log the error and returned something like: + // return #[]; + } + } + + /** + * Get list of {@link Pair}s of first and second argument of the {@link AnnotationDefinition#BIND} annotation. + */ + private Iterable> getBindingPairs(TClass clazz) { + return map(AnnotationDefinition.BIND.getAllAnnotations(clazz), a -> Pair.of( + (TN4Classifier) (((TAnnotationTypeRefArgument) a.getArgs().get(0)).getTypeRef().getDeclaredType()), + (TN4Classifier) ((TAnnotationTypeRefArgument) last(a.getArgs())).getTypeRef().getDeclaredType())); + } + + private Iterable getOwnedProviderMethods(TClass clazz) { + return filter(filter(clazz.getOwnedMembers(), TMethod.class), + m -> AnnotationDefinition.PROVIDES.hasAnnotation(m)); + } + + private Iterable getOwnedInejctedFields(TN4Classifier clazz) { + return filter(filter(clazz.getOwnedMembers(), TField.class), f -> AnnotationDefinition.INJECT.hasAnnotation(f)); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DependencyInjectionTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DependencyInjectionTransformation.xtend deleted file mode 100644 index 249acf26c1..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DependencyInjectionTransformation.xtend +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TAnnotationTypeRefArgument -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TN4Classifier -import org.eclipse.n4js.utils.DeclMergingHelper - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.tooling.organizeImports.DIUtility.* -import static extension org.eclipse.n4js.transpiler.utils.TranspilerUtils.* -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Generates DI meta information for the injector. - * Information is attached to the compiled type in the {@link N4JSLanguageConstants#DI_SYMBOL_KEY DI_SYMBOL_KEY} - * property. - * - * TODO the DI code below makes heavy use of TModule (probably) without true need; refactor this - */ -class DependencyInjectionTransformation extends Transformation { - - @Inject private DeclMergingHelper declMergingHelper; - - override assertPreConditions() { - // true - } - - override assertPostConditions() { - // true - } - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, N4ClassDeclaration, false).forEach[classDecl| - val tClass = state.info.getOriginalDefinedType(classDecl); - if( tClass !== null ) { - val codeForDI = generateCodeForDI(tClass, classDecl); - if(codeForDI!==null) { - insertAfter(classDecl.orContainingExportDeclaration, codeForDI); - } - } - ]; - } - - /** - * Generates hooks for DI mechanisms. Mainly N4Injector (part of n4js libraries) - * depends on those hooks. - * Note that those in many cases require proper types to be imported, which makes those - * hooks indirectly depended on behavior of the script transpiler policy that decides to - * remove "unused" imported symbols. That one has to leave imported symbols that are refereed to - * in code generated here. - * - * Also, note second parameter passed is name or alias of the super type. - * passing it from caller, even if we don't use it, is a shortcut to avoid computing it again here. - */ - def private Statement generateCodeForDI(TClass tClass, N4ClassDeclaration classDecl) { - val propertiesForDI = createPropertiesForDI(tClass, classDecl); - if(propertiesForDI.empty) { - return null; - } - // Object.defineProperty(C, Symbol.for(''), {value: { ... }} - val classSTE = findSymbolTableEntryForElement(classDecl, true); - val objectSTE = getSymbolTableEntryOriginal(state.G.objectType, true); - val definePropertySTE = getSymbolTableEntryForMember(state.G.objectType, "defineProperty", false, true, true); - val symbolObjectSTE = getSymbolTableEntryOriginal(state.G.symbolObjectType, true); - val forSTE = getSymbolTableEntryForMember(state.G.symbolObjectType, "for", false, true, true); - return _ExprStmnt(_CallExpr( - _PropertyAccessExpr(objectSTE, definePropertySTE), - _IdentRef(classSTE), - _CallExpr(_PropertyAccessExpr(symbolObjectSTE, forSTE), _StringLiteral(N4JSLanguageConstants.DI_SYMBOL_KEY)), - _ObjLit( - "value" -> _ObjLit( - propertiesForDI - ) - ) - )); - } - - def private Pair[] createPropertiesForDI(TClass tClass, N4ClassDeclaration classDecl) { - val result = >newArrayList; - if(isBinder(tClass)) { - result += "bindings" -> generateBindingPairs(tClass) as Expression; - result += "methodBindings" -> generateMethodBindings(tClass) as Expression; - result += injectionPointsMetaInfo(tClass); - } else if(isDIComponent(tClass)) { - if(hasParentInjector(tClass)) { - val parentDIC_STE = getSymbolTableEntryOriginal(findParentDIC(tClass), true); - result += "parent" -> __NSSafe_IdentRef(parentDIC_STE) as Expression; - } - result += "binders" -> generateBinders(tClass) as Expression; - result += injectionPointsMetaInfo(tClass); - } else if(isInjectedClass(tClass, declMergingHelper)) { - result += injectionPointsMetaInfo(tClass); - } - return result; - } - - /** - * Generate DI hooks for scopes, super type, injected ctor, injected fields - */ - def private Pair[] injectionPointsMetaInfo(TClass tClass) { - val fieldInjectionValue = fieldInjection(tClass); - val result = >newArrayList; - if(isSingleton(tClass)) { - result += "scope" -> _StringLiteral("Singleton") as Expression; - } - val ownedCtor = tClass.ownedCtor; - if(ownedCtor!==null && AnnotationDefinition.INJECT.hasAnnotation(ownedCtor)) { - result += "injectCtorParams" -> ownedCtor.methodInjectedParams as Expression; - } - if(!fieldInjectionValue.elements.empty) { - result += "fieldsInjectedTypes" -> fieldInjection(tClass) as Expression; - } - return result; - } - - /** - * Generate injection info for method annotated with {@link AnnotationDefinition#INJECT}. - * If method has no method parameters returned value is empty string, - * otherwise description of parameters is returned. - */ - def private ArrayLiteral methodInjectedParams(TMethod tMethod) { - val result = _ArrLit(); - for(fpar : tMethod.fpars) { - val fparSTE = getSymbolTableEntryOriginal(fpar, true); - result.elements += _ArrayElement(_ObjLit( - #[ _PropertyNameValuePair("name", _StringLiteralForSTE(fparSTE)) ] - + fpar.typeRef.generateTypeInfo - )); - } - return result; - } - - /** - * Generate injection info for fields annotated with {@link AnnotationDefinition#INJECT}. - */ - def private ArrayLiteral fieldInjection(TClass tClass) { - val result = _ArrLit(); - for(field : getOwnedInejctedFields(tClass)) { - val fieldSTE = getSymbolTableEntryOriginal(field, true); - result.elements += _ArrayElement(_ObjLit( - #[ _PropertyNameValuePair("name", _StringLiteralForSTE(fieldSTE)) ] - + field.typeRef.generateTypeInfo - )); - } - return result; - } - - /** - * Generate injection info from {@link AnnotationDefinition#BIND} annotations on the provided class. - */ - private def ArrayLiteral generateBindingPairs(TClass tClass) { - val result = _ArrLit(); - for(binding : getBindingPairs(tClass)) { - val keySTE = getSymbolTableEntryOriginal(binding.key, true); - val valueSTE = getSymbolTableEntryOriginal(binding.value, true); - result.elements += _ArrayElement(_ObjLit( - "from" -> __NSSafe_IdentRef(keySTE), - "to" -> __NSSafe_IdentRef(valueSTE) - )); - } - return result; - } - - /** - * Generate injection info for methods annotated with {@link AnnotationDefinition#PROVIDES}. - * Returned information contains returned type, name and formal parameters of the method. - */ - def private ArrayLiteral generateMethodBindings(TClass tClass) { - val result = _ArrLit(); - for(method : getOwnedProviderMethods(tClass)) { - result.elements += _ArrayElement(_ObjLit( - method.returnTypeRef.generateTypeInfo("to").head, - _PropertyNameValuePair("name", _StringLiteral(method.name)), - _PropertyNameValuePair("args", method.methodInjectedParams) - )); - } - return result; - } - - def private ArrayLiteral generateBinders(TClass tClass) { - val result = _ArrLit(); - for(binderType : resolveBinders(tClass)) { - val binderTypeSTE = getSymbolTableEntryOriginal(binderType, true); - result.elements += _ArrayElement( - __NSSafe_IdentRef(binderTypeSTE) - ); - } - return result; - } - - /** - * Generate type information for DI. Mainly FQN of the {@link TypeRef}, or composed information - * if given type is generic. - */ - def private PropertyAssignment[] generateTypeInfo(TypeRef typeRef) { - typeRef.generateTypeInfo("type") - } - def private PropertyAssignment[] generateTypeInfo(TypeRef typeRef, String propertyName) { - if (!typeRef.providerType) { - val declaredTypeSTE = getSymbolTableEntryOriginal(typeRef.declaredType, true); - return #[ - _PropertyGetterDecl(propertyName, _ReturnStmnt(__NSSafe_IdentRef(declaredTypeSTE))) - ]; - } else if (typeRef instanceof ParameterizedTypeRef) { - // typeRef should be N4Provider, fqn needs to be obtained at runtime - val declaredTypeSTE = getSymbolTableEntryOriginal(typeRef.declaredType, true); - return #[ - _PropertyGetterDecl(propertyName, _ReturnStmnt(__NSSafe_IdentRef(declaredTypeSTE))), - _PropertyNameValuePair("typeVar", _ObjLit( - typeRef.declaredTypeArgs.filter(TypeRef).head.generateTypeInfo - )) - ]; - } else { - throw new IllegalStateException("cannot generate type info for " + typeRef?.declaredType?.name); -// note: at this point, the old transpiler did only log the error and returned something like: -// return #[]; - } - } - - - - - /** - * Get list of {@link Pair}s of first and second argument of the {@link AnnotationDefinition#BIND} annotation. - */ - def private getBindingPairs(TClass clazz) { - AnnotationDefinition.BIND.getAllAnnotations(clazz).map [ - ((args.head as TAnnotationTypeRefArgument).getTypeRef.declaredType) as TN4Classifier - -> ((args.last as TAnnotationTypeRefArgument).getTypeRef.declaredType) as TN4Classifier - ] - } - - def private getOwnedProviderMethods(TClass clazz) { - clazz.ownedMembers.filter(TMethod).filter[AnnotationDefinition.PROVIDES.hasAnnotation(it)]; - } - - def private getOwnedInejctedFields(TN4Classifier clazz) { - clazz.ownedMembers.filter(TField).filter[AnnotationDefinition.INJECT.hasAnnotation(it)] - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DestructuringTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DestructuringTransformation.java new file mode 100644 index 0000000000..f004873997 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DestructuringTransformation.java @@ -0,0 +1,478 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static com.google.common.collect.Iterables.toArray; +import static org.eclipse.n4js.n4JS.DestructureUtils.containsDestructuringPattern; +import static org.eclipse.n4js.n4JS.DestructureUtils.isRoot; +import static org.eclipse.n4js.n4JS.DestructureUtils.isTopOfDestructuringAssignment; +import static org.eclipse.n4js.n4JS.DestructureUtils.isTopOfDestructuringForStatement; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._AssignmentExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Block; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ConditionalExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._EqualityExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ExprStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._FormalParameter; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._FunExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IndexAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NumericLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Parenthesis; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Snippet; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableStatement; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.generator.GeneratorOption; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.AssignmentExpression; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.CatchBlock; +import org.eclipse.n4js.n4JS.DestructNode; +import org.eclipse.n4js.n4JS.EqualityOperator; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ForStatement; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.n4JS.VariableBinding; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableDeclarationOrBinding; +import org.eclipse.n4js.n4JS.VariableEnvironmentElement; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.n4JS.VariableStatementKeyword; +import org.eclipse.n4js.n4JS.WithStatement; +import org.eclipse.n4js.transpiler.AbstractTranspiler; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesAfter; +import org.eclipse.n4js.transpiler.TransformationDependency.Optional; +import org.eclipse.n4js.transpiler.im.IdentifierRef_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryIMOnly; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.common.base.Strings; + +/** + * Transforms ES6 destructuring patterns into equivalent ES5 code. If the target engine supports ES6 destructuring + * patterns, this transformation can simply be deactivated. + *

+ * For details on destructuring patterns, see documentation of class {@link DestructNode}. + */ +@Optional(GeneratorOption.Destructuring) +@ExcludesAfter(StaticPolyfillTransformation.class) // otherwise destructuring patterns from filling module won't be + // processed! +public class DestructuringTransformation extends Transformation { + + /** Counts destructuring patterns per variable environment. Used to avoid name clashes of helper variables. */ + private final Map destructsPerScope = new HashMap<>(); + + @Override + public void assertPreConditions() { + // true + // (however, this transformation should run as early as possible) + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + + // compute nodes we have to transform before changing anything + List destructBindings = toList(filter( + collectNodes(getState().im, VariableStatement.class, true), vs -> containsDestructuringPattern(vs))); + List destructAssignments = toList(filter( + collectNodes(getState().im, AssignmentExpression.class, true), + ae -> isTopOfDestructuringAssignment(ae) && isRoot(ae))); + List destructForStmnts = toList(filter(collectNodes(getState().im, ForStatement.class, true), + fs -> containsDestructuringPattern(fs) || isTopOfDestructuringForStatement(fs))); + + // now perform the changes + for (VariableStatement vs : destructBindings) { + transformDestructuringBindings(vs); + } + for (AssignmentExpression ae : destructAssignments) { + transformDestructuringAssignment(ae); + } + for (ForStatement fs : destructForStmnts) { + transformForStatementWithDestructuring(fs); + } + } + + /** + * Transforms not a single destructuring binding but all destructuring bindings in the given variable statement. + */ + private void transformDestructuringBindings(VariableStatement stmnt) { + List newVarDecls = computeVariableDeclarations(stmnt.getVarDeclsOrBindings()); + stmnt.getVarDeclsOrBindings().clear(); + stmnt.getVarDeclsOrBindings().addAll(newVarDecls); + } + + /** + * Transforms (a single) destructuring assignment. + */ + public void transformDestructuringAssignment(AssignmentExpression expr) { + // We pass the value of the expression to the function call. GHOLD-407 + String fparName; + FunctionExpression helperFun; + + fparName = "$destruct" + "Param0"; + FormalParameter fpar = _FormalParameter(fparName); + helperFun = _FunExpr(false, null, fpar); + + EList helperFunContents = helperFun.getBody().getStatements(); + DestructNode rootNode = DestructNode.unify(expr); + List helperVars = new ArrayList<>(); + List> simpleAssignments = new ArrayList<>(); + traverse(helperVars, simpleAssignments, rootNode, expr.getRhs(), fparName); + + helperFunContents.addAll(toList(map(helperVars, vd -> _VariableStatement(vd)))); + helperFunContents.addAll(toList(map(simpleAssignments, sa -> _ExprStmnt(_AssignmentExpr( + __NSSafe_IdentRef(sa.getKey()), sa.getValue()))))); + + // the following return statement is required to make sure entire expression evaluates to the rhs of + // the assignment expression 'expr' (without evaluating rhs again!) + SymbolTableEntry firstHelperVarSTE = findSymbolTableEntryForElement(helperVars.get(0), false); + helperFunContents.add(_ReturnStmnt(_IdentRef(firstHelperVarSTE))); + + // parentheses required because the expression might appear as a statement (to disambiguate from function + // declaration) replace(expr, callExpr); + ParameterizedCallExpression callExpr = _CallExpr(_Parenthesis(helperFun), expr.getRhs()); + replace(expr, callExpr); + } + + private void transformForStatementWithDestructuring(ForStatement stmnt) { + + if (stmnt.isForPlain()) { + + if (!stmnt.getVarDeclsOrBindings().isEmpty()) { + // something like: for( var [a,b]=[1,2] ;;) {} + + // note: pretty much the same case as method #transformDestructuringBindings() above + List newVarDecls = computeVariableDeclarations(stmnt.getVarDeclsOrBindings()); + stmnt.getVarDeclsOrBindings().clear(); + stmnt.getVarDeclsOrBindings().addAll(newVarDecls); + + } else { + // something like: for( [a,b]=[1,2] ;;) {} + + // here, stmnt.initExpr is an ordinary destructuring assignment that was already handled by method + // #transformDestructuringAssignment(AssignmentExpression) above -> nothing to do here! + } + + } else { + // now: for..in OR for..of + + int depth = getNestingDepth(stmnt); + + VariableDeclaration iterVar = _VariableDeclaration("$destructStep$" + depth); + SymbolTableEntryIMOnly iterVarSTE = createSymbolTableEntryIMOnly(iterVar); + + boolean needDeclarations = false; + VariableStatementKeyword varStmtKeyword = VariableStatementKeyword.VAR; + List helperVars = new ArrayList<>(); + List> simpleAssignments = new ArrayList<>(); + if (!stmnt.getVarDeclsOrBindings().isEmpty()) { + // something like: for( var [a,b] of [ [1,2], [3,4] ] ) {} + + if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { + assertTrue("there should be exactly one VariableBinding in stmnt.varDeclsOrBindings", + stmnt.getVarDeclsOrBindings().size() == 1 + && stmnt.getVarDeclsOrBindings().get(0) instanceof VariableBinding); + } + + DestructNode rootNode = DestructNode.unify((VariableBinding) stmnt.getVarDeclsOrBindings().get(0)); + // fparname = null since we do not generate any function. + traverse(helperVars, simpleAssignments, rootNode, _IdentRef(iterVarSTE), null); + needDeclarations = true; + varStmtKeyword = stmnt.getVarStmtKeyword(); + + } else if (stmnt.getInitExpr() instanceof ArrayLiteral || stmnt.getInitExpr() instanceof ObjectLiteral) { + // something like: for( [a,b] of [ [1,2], [3,4] ] ) {} + + DestructNode rootNode = DestructNode.unify(stmnt); + // fparname = null since we do not generate any function. + traverse(helperVars, simpleAssignments, rootNode, _IdentRef(iterVarSTE), null); + needDeclarations = false; + + } else { + throw new IllegalArgumentException(); + } + + if (!(stmnt.getStatement() instanceof Block)) { + stmnt.setStatement(_Block(stmnt.getStatement())); + } + Block body = (Block) stmnt.getStatement(); + + List toBeInserted = new ArrayList<>(); + if (needDeclarations) { + toBeInserted.add(_VariableStatement(varStmtKeyword, toArray(map(simpleAssignments, sa -> { + VariableDeclaration varDecl = getVariableDeclarationFromSTE(sa.getKey()); + varDecl.setExpression(sa.getValue()); + return varDecl; + }), VariableDeclarationOrBinding.class))); + } else { + toBeInserted.add(_VariableStatement(toArray(helperVars, VariableDeclaration.class))); + toBeInserted.addAll(toList(map(simpleAssignments, sa -> _ExprStmnt(_AssignmentExpr( + __NSSafe_IdentRef(sa.getKey()), sa.getValue()))))); + } + + body.getStatements().addAll(0, toBeInserted); + + // initExpr has been move to beginning of body (if any) + stmnt.setInitExpr(null); + // variable declarations have been moved to beginning of body (if any) + stmnt.getVarDeclsOrBindings().clear(); + // only declared variable in the for statement is the iteration variable + stmnt.getVarDeclsOrBindings().add(iterVar); + } + } + + /** + * Breaks down all VariableBindings in the given list into simple variable declarations and returns a list + * containing the variable declarations that were contained in the given list from the beginning plus those created + * when breaking down the variable bindings. The order of the elements in the given list is preserved! + */ + private List computeVariableDeclarations( + List varDeclsOrBindings) { + List result = new ArrayList<>(); + for (VariableDeclarationOrBinding vdeclOrBinding : varDeclsOrBindings) { + if (vdeclOrBinding instanceof VariableDeclaration) { + result.add((VariableDeclaration) vdeclOrBinding); + } else if (vdeclOrBinding instanceof VariableBinding) { + DestructNode rootNode = DestructNode.unify(vdeclOrBinding); + List helperVars = new ArrayList<>(); + List> simpleAssignments = new ArrayList<>(); + // fparname = null since we do not generate any function. + traverse(helperVars, simpleAssignments, rootNode, vdeclOrBinding.getExpression(), null); + result.addAll(toList(map(simpleAssignments, sa -> { + VariableDeclaration varDecl = getVariableDeclarationFromSTE(sa.getKey()); + varDecl.setExpression(sa.getValue()); + return varDecl; + }))); + } + } + return result; + } + + private void traverse(List helperVars, + List> simpleAssignments, + DestructNode rootNode, Expression value, String fparName) { + + VariableEnvironmentElement scope = N4JSASTUtils.getScope(rootNode.astElement, false); + if (scope == null) { + scope = getState().im; + } + int n = destructsPerScope.merge(scope, 1, (i, j) -> i + j) - 1; + traverse(helperVars, simpleAssignments, rootNode.getNestedNodes(), value, fparName, Integer.toString(n)); + } + + /** + * Breaks down the destructuring pattern, represented by the given {@link DestructNode}s, into a number of simple + * assignments (without destructuring) that will be added to argument 'simpleAssignments'. The order of those simple + * assignments matters. Nested patterns as returned by {@link DestructNode#getNestedNodes()} are also broken down. + * fparName, if not null, is the parameter name of the enclosing function. + */ + private void traverse(List helperVars, + List> simpleAssignments, + List nodes, Expression value, String fparName, String helperVarSuffix) { + + int len = nodes.size(); + boolean isPositionalPattern = IterableExtensions.exists(nodes, n -> n.isPositional()); + boolean isRest = isPositionalPattern && len > 0 && nodes.get(len - 1).rest; + + // STEP 1: create code to prepare the value to be destructured and to assign it to a helper variable + + // creating a new helper variable + String currHelperVarName = "$destruct" + helperVarSuffix; + VariableDeclaration currHelperVarDecl = _VariableDeclaration(currHelperVarName); + helperVars.add(currHelperVarDecl); + SymbolTableEntry currHelperVarSTE = findSymbolTableEntryForElement(currHelperVarDecl, true); + if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { + assertTrue("", getVariableDeclarationFromSTE(currHelperVarSTE) == currHelperVarDecl); + assertTrue("", currHelperVarSTE.getElementsOfThisName().contains(currHelperVarDecl)); + } + var $sliceToArrayForDestructSTE = steFor_$sliceToArrayForDestruct(); + + if (isRest) { + // result.add(currHelperVar+" = function(arr){return Array.isArray(arr) ? arr : + // Array.from(arr);}("+value+")"); + simpleAssignments.add(Pair.of(currHelperVarSTE, _CallExpr( + _Snippet("function(arr){return Array.isArray(arr) ? arr : Array.from(arr);}"), + value))); + } else { + Expression passValue; + if (Strings.isNullOrEmpty(fparName)) { + passValue = value; + } else { + // If the fparName is not empty, the generated function for destructuring has a parameter and we should + // use that parameter instead. GHOLD-407 + SymbolTableEntryInternal fparSTE = getSymbolTableEntryInternal(fparName, true); + passValue = _IdentRef(fparSTE); + } + if (isPositionalPattern) { + // result.add(currHelperVar+" = $sliceToArrayForDestruct(("+value+"), "+len+")"); + simpleAssignments.add(Pair.of(currHelperVarSTE, _CallExpr( + _IdentRef($sliceToArrayForDestructSTE), + _Parenthesis(passValue), + _NumericLiteral(len)))); + } else { + // result.add(currHelperVar+" = ("+value+")"); + simpleAssignments.add(Pair.of(currHelperVarSTE, _Parenthesis( + passValue))); + } + } + + // STEP 2: create code to perform the actual destructuring + + SymbolTableEntryOriginal sliceSTE = getSymbolTableEntryForMember(arrayType(getState().G), "slice", false, false, + true); + + var nestedPatternsCounter = 0; + for (var i = 0; i < len; i++) { + DestructNode currNode = nodes.get(i); + + // get the current element or property out of 'value' (i.e. the one that corresponds to 'currNode') + Expression currValueRaw; + if (isRest && i == len - 1) { + // currHelperVar.slice(i) + currValueRaw = _CallExpr(_PropertyAccessExpr(currHelperVarSTE, sliceSTE), _NumericLiteral(i)); + } else if (isPositionalPattern) { + // currHelperVar[i] + currValueRaw = _IndexAccessExpr(currHelperVarSTE, _NumericLiteral(i)); + } else { + // currHelperVar['propName'] + currValueRaw = _IndexAccessExpr(currHelperVarSTE, _StringLiteral(currNode.propName)); + } + + Expression currValue; + if (currNode.defaultExpr != null) { + // currValueRaw+" == undefined ? ("+transformAST.doTransform(currNode.defaultExpr)+") : "+currValueRaw + currValue = _ConditionalExpr( + _EqualityExpr( + copy(currValueRaw), // must copy because used twice in this conditional expression! + EqualityOperator.SAME, + undefinedRef()), + _Parenthesis( + currNode.defaultExpr), + currValueRaw); + } else { + currValue = currValueRaw; + } + + if (currNode.varRef != null || currNode.varDecl != null) { + // actual destructuring + // (assigning an element or property from 'value', i.e. the 'currValue', to the variable with name + // currNode.varName) + TypableElement varSource = currNode.varRef != null ? currNode.varRef : currNode.varDecl; + SymbolTableEntry varSTE = null; + + if (varSource instanceof IdentifierRef_IM) { + varSTE = ((IdentifierRef_IM) varSource).getId_IM(); + } else if (varSource instanceof VariableDeclaration) { + varSTE = findSymbolTableEntryForElement((VariableDeclaration) varSource, true); + } + simpleAssignments.add(Pair.of(varSTE, currValue)); + } else if (currNode.getNestedNodes() != null && currNode.getNestedNodes().size() != 0) { + // nested destructuring + // (assigning the current value in 'currValue' to the nested destructuring pattern) + nestedPatternsCounter++; + // fparname = null since we do not generate any function + traverse(helperVars, simpleAssignments, currNode.getNestedNodes(), currValue, null, + helperVarSuffix + "$" + nestedPatternsCounter); + } else { + // padding entry (from elision) + // -> do nothing (but consume the current index 'i') + } + } + } + + private static final int getNestingDepth(ForStatement stmnt) { + int d = 0; + EObject obj = stmnt; + while ((obj = obj.eContainer()) != null) { + if (obj instanceof ForStatement) { + d++; + } + } + return d; + } + + /** + * Returns a list of statements that are the root statements of the next outer variable environment directly + * containing the given AST node. + */ + static EList getContainingVariableEnvironmentContent(EObject astNode) { + VariableEnvironmentElement vee = EcoreUtil2.getContainerOfType(astNode.eContainer(), + VariableEnvironmentElement.class); + if (vee == null) { + throw new IllegalArgumentException("given AST node does not have an outer variable environment"); + } + if (vee instanceof Script) { + return ((Script) vee).getScriptElements(); + } + if (vee instanceof FunctionOrFieldAccessor) { + return ((FunctionOrFieldAccessor) vee).getBody().getStatements(); + } + if (vee instanceof CatchBlock) { + return ((CatchBlock) vee).getBlock().getStatements(); + } + if (vee instanceof PropertyAssignment) { + return getContainingVariableEnvironmentContent(vee); + } + if (vee instanceof WithStatement) { + WithStatement ws = (WithStatement) vee; + if (!(ws.getStatement() instanceof Block)) { + ws.setStatement(_Block(ws.getStatement())); + } + return ((Block) ws.getStatement()).getStatements(); + } + throw new IllegalArgumentException("given AST node does not have an outer variable environment"); + } + + private static VariableDeclaration getVariableDeclarationFromSTE(SymbolTableEntry ste) { + return head(filter(ste.getElementsOfThisName(), VariableDeclaration.class)); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DestructuringTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DestructuringTransformation.xtend deleted file mode 100644 index 20edd7c310..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/DestructuringTransformation.xtend +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import java.util.ArrayList -import java.util.List -import java.util.Map -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.DestructNode -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.AssignmentExpression -import org.eclipse.n4js.n4JS.Block -import org.eclipse.n4js.n4JS.CatchBlock -import org.eclipse.n4js.n4JS.EqualityOperator -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ForStatement -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.n4JS.VariableBinding -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableDeclarationOrBinding -import org.eclipse.n4js.n4JS.VariableEnvironmentElement -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.n4JS.VariableStatementKeyword -import org.eclipse.n4js.n4JS.WithStatement -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesAfter -import org.eclipse.n4js.transpiler.TransformationDependency.Optional -import org.eclipse.n4js.transpiler.im.IdentifierRef_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.xtext.EcoreUtil2 - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.n4JS.DestructureUtils.* -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* -import org.eclipse.n4js.transpiler.AbstractTranspiler - -/** - * Transforms ES6 destructuring patterns into equivalent ES5 code. If the target engine supports ES6 destructuring - * patterns, this transformation can simply be deactivated. - *

- * For details on destructuring patterns, see documentation of class {@link DestructNode}. - */ -@Optional(Destructuring) -@ExcludesAfter(StaticPolyfillTransformation) // otherwise destructuring patterns from filling module won't be processed! -class DestructuringTransformation extends Transformation { - - /** Counts destructuring patterns per variable environment. Used to avoid name clashes of helper variables. */ - private final Map destructsPerScope = newHashMap; - - - override assertPreConditions() { - // true - // (however, this transformation should run as early as possible) - } - override assertPostConditions() { - // true - } - - override analyze() { - // ignore - } - - override transform() { - - // compute nodes we have to transform before changing anything - val destructBindings = collectNodes(state.im, VariableStatement, true) - .filter[containsDestructuringPattern].toList; - val destructAssignments = collectNodes(state.im, AssignmentExpression, true) - .filter[isTopOfDestructuringAssignment].filter[isRoot].toList; - val destructForStmnts = collectNodes(state.im, ForStatement, true) - .filter[containsDestructuringPattern || isTopOfDestructuringForStatement].toList; - - // now perform the changes - destructBindings.forEach[transformDestructuringBindings]; - destructAssignments.forEach[transformDestructuringAssignment]; - destructForStmnts.forEach[transformForStatementWithDestructuring]; - } - - /** - * Transforms not a single destructuring binding but all destructuring bindings in the given variable statement. - */ - def private void transformDestructuringBindings(VariableStatement stmnt) { - val newVarDecls = computeVariableDeclarations(stmnt.varDeclsOrBindings); - stmnt.varDeclsOrBindings.clear(); - stmnt.varDeclsOrBindings += newVarDecls; - } - - /** - * Transforms (a single) destructuring assignment. - */ - def public void transformDestructuringAssignment(AssignmentExpression expr) { - // We pass the value of the expression to the function call. GHOLD-407 - var String fparName; - var FunctionExpression helperFun; - - fparName = "$destruct" + "Param0"; - val fpar = _FormalParameter(fparName); - helperFun = _FunExpr(false, null, fpar); - - val helperFunContents = helperFun.body.statements; - val rootNode = DestructNode.unify(expr); - val helperVars = newArrayList; - val simpleAssignments = >newArrayList; - traverse(helperVars, simpleAssignments, rootNode, expr.rhs, fparName); - - helperFunContents += helperVars.map[_VariableStatement(it)]; - helperFunContents += simpleAssignments.map[ - _ExprStmnt(_AssignmentExpr( - __NSSafe_IdentRef(it.key), - it.value - )) - ]; - - // the following return statement is required to make sure entire expression evaluates to the rhs of - // the assignment expression 'expr' (without evaluating rhs again!) - val firstHelperVarSTE = findSymbolTableEntryForElement(helperVars.get(0), false); - helperFunContents += _ReturnStmnt(_IdentRef(firstHelperVarSTE)); - - val callExpr = _CallExpr(_Parenthesis(helperFun), expr.rhs) // parentheses required because the expression might appear as a statement (to disambiguate from function declaration) - replace(expr, callExpr); - } - - def private void transformForStatementWithDestructuring(ForStatement stmnt) { - - if(stmnt.forPlain) { - - if(!stmnt.varDeclsOrBindings.empty) { - // something like: for( var [a,b]=[1,2] ;;) {} - - // note: pretty much the same case as method #transformDestructuringBindings() above - val newVarDecls = computeVariableDeclarations(stmnt.varDeclsOrBindings); - stmnt.varDeclsOrBindings.clear(); - stmnt.varDeclsOrBindings += newVarDecls; - - } else { - // something like: for( [a,b]=[1,2] ;;) {} - - // here, stmnt.initExpr is an ordinary destructuring assignment that was already handled by method - // #transformDestructuringAssignment(AssignmentExpression) above -> nothing to do here! - } - - } else { - // now: for..in OR for..of - - val depth = stmnt.nestingDepth; - - val iterVar = _VariableDeclaration("$destructStep$"+depth); - val iterVarSTE = createSymbolTableEntryIMOnly(iterVar); - - var needDeclarations = false; - var varStmtKeyword = VariableStatementKeyword.VAR; - val helperVars = newArrayList; - val simpleAssignments = >newArrayList; - if(!stmnt.varDeclsOrBindings.empty) { - // something like: for( var [a,b] of [ [1,2], [3,4] ] ) {} - - if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { - assertTrue("there should be exactly one VariableBinding in stmnt.varDeclsOrBindings", - stmnt.varDeclsOrBindings.size===1 && stmnt.varDeclsOrBindings.get(0) instanceof VariableBinding); - } - - val rootNode = DestructNode.unify(stmnt.varDeclsOrBindings.head as VariableBinding); - traverse(helperVars, simpleAssignments, rootNode, _IdentRef(iterVarSTE), null); // fparname = null since we do not generate any function. - needDeclarations = true; - varStmtKeyword = stmnt.varStmtKeyword; - - } else if(stmnt.initExpr instanceof ArrayLiteral || stmnt.initExpr instanceof ObjectLiteral) { - // something like: for( [a,b] of [ [1,2], [3,4] ] ) {} - - val rootNode = DestructNode.unify(stmnt); - traverse(helperVars, simpleAssignments, rootNode, _IdentRef(iterVarSTE), null); // fparname = null since we do not generate any function. - needDeclarations = false; - - } else { - throw new IllegalArgumentException(); - } - - if(!(stmnt.statement instanceof Block)) { - stmnt.statement = _Block(stmnt.statement); - } - val body = stmnt.statement as Block; - - val toBeInserted = newArrayList; - if(needDeclarations) { - toBeInserted += _VariableStatement(varStmtKeyword, simpleAssignments.map[ - val varDecl = key.getVariableDeclarationFromSTE; - varDecl.expression = value; - return varDecl; - ]); - } else { - toBeInserted += _VariableStatement(helperVars); - toBeInserted += simpleAssignments.map[ - _ExprStmnt(_AssignmentExpr( - __NSSafe_IdentRef(it.key), - it.value - )) - ]; - } - - body.statements.addAll(0, toBeInserted); - - stmnt.initExpr = null; // initExpr has been move to beginning of body (if any) - stmnt.varDeclsOrBindings.clear(); // variable declarations have been moved to beginning of body (if any) - stmnt.varDeclsOrBindings += iterVar; // only declared variable in the for statement is the iteration variable - } - } - - /** - * Breaks down all VariableBindings in the given list into simple variable declarations and returns a list containing - * the variable declarations that were contained in the given list from the beginning plus those created when - * breaking down the variable bindings. The order of the elements in the given list is preserved! - */ - def private List computeVariableDeclarations(List varDeclsOrBindings) { - val result = newArrayList; - for(VariableDeclarationOrBinding vdeclOrBinding : new ArrayList(varDeclsOrBindings)) { - if(vdeclOrBinding instanceof VariableDeclaration) { - result += vdeclOrBinding; - } else if(vdeclOrBinding instanceof VariableBinding) { - val rootNode = DestructNode.unify(vdeclOrBinding); - val helperVars = newArrayList; - val simpleAssignments = >newArrayList; - traverse(helperVars, simpleAssignments, rootNode, vdeclOrBinding.expression, null); // fparname = null since we do not generate any function. - result += simpleAssignments.map[ - var varDecl = key.getVariableDeclarationFromSTE; - varDecl.expression = value; - return varDecl; - ]; - } - } - return result; - } - def private void traverse(List helperVars, List> simpleAssignments, - DestructNode rootNode, Expression value, String fparName) { - val scope = N4JSASTUtils.getScope(rootNode.astElement, false) ?: state.im; - val n = destructsPerScope.merge(scope, 1, [i,j|i+j]) - 1; - traverse(helperVars, simpleAssignments, rootNode.nestedNodes, value, fparName, Integer.toString(n)); - } - /** - * Breaks down the destructuring pattern, represented by the given {@link DestructNode}s, into a number of simple - * assignments (without destructuring) that will be added to argument 'simpleAssignments'. The order of those simple - * assignments matters. Nested patterns as returned by {@link DestructNode#getNestedNodes()} are also broken down. - * fparName, if not null, is the parameter name of the enclosing function. - */ - def private void traverse(List helperVars, List> simpleAssignments, - DestructNode[] nodes, Expression value, String fparName, String helperVarSuffix) { - val len = nodes.length; - val isPositionalPattern = nodes.exists[positional]; - val isRest = isPositionalPattern && !nodes.empty && nodes.last.rest; - - // STEP 1: create code to prepare the value to be destructured and to assign it to a helper variable - - // creating a new helper variable - val currHelperVarName = "$destruct"+helperVarSuffix; - val currHelperVarDecl = _VariableDeclaration(currHelperVarName); - helperVars += currHelperVarDecl; - val SymbolTableEntry currHelperVarSTE = findSymbolTableEntryForElement(currHelperVarDecl, true); - if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { - assertTrue("", currHelperVarSTE.getVariableDeclarationFromSTE === currHelperVarDecl); - assertTrue("", currHelperVarSTE.elementsOfThisName.contains(currHelperVarDecl)); - } - var $sliceToArrayForDestructSTE = steFor_$sliceToArrayForDestruct; - - if(isRest) { - //result.add(currHelperVar+" = function(arr){return Array.isArray(arr) ? arr : Array.from(arr);}("+value+")"); - simpleAssignments += currHelperVarSTE -> _CallExpr( - _Snippet("function(arr){return Array.isArray(arr) ? arr : Array.from(arr);}"), - value - ); - } else { - val passValue = if (fparName.isNullOrEmpty) { - value - } else { - // If the fparName is not empty, the generated function for destructuring has a parameter and we should use that parameter instead. GHOLD-407 - val fparSTE = getSymbolTableEntryInternal(fparName, true); - _IdentRef(fparSTE) - } - if(isPositionalPattern) { - //result.add(currHelperVar+" = $sliceToArrayForDestruct(("+value+"), "+len+")"); - simpleAssignments += currHelperVarSTE -> _CallExpr( - _IdentRef($sliceToArrayForDestructSTE), - _Parenthesis(passValue), - _NumericLiteral(len) - ); - } else { - //result.add(currHelperVar+" = ("+value+")"); - simpleAssignments += currHelperVarSTE -> _Parenthesis( - passValue - ); - } - } - - // STEP 2: create code to perform the actual destructuring - - val sliceSTE = getSymbolTableEntryForMember(state.G.arrayType, "slice", false, false, true); - - var nestedPatternsCounter = 0; - for(var i=0;i currValue; - } - else if(currNode.nestedNodes!==null && !currNode.nestedNodes.empty) { - // nested destructuring - // (assigning the current value in 'currValue' to the nested destructuring pattern) - nestedPatternsCounter++; - // fparname = null since we do not generate any function - traverse(helperVars, simpleAssignments, currNode.nestedNodes, currValue, null, helperVarSuffix+"$"+nestedPatternsCounter); - } - else { - // padding entry (from elision) - // -> do nothing (but consume the current index 'i') - } - } - } - - def private static final int getNestingDepth(ForStatement stmnt) { - var d = 0; - var EObject obj = stmnt; - while((obj = obj.eContainer)!==null) { - if(obj instanceof ForStatement) - d++; - } - return d; - } - - /** - * Returns a list of statements that are the root statements of the next outer variable environment directly - * containing the given AST node. - */ - def static EList getContainingVariableEnvironmentContent(EObject astNode) { - val vee = EcoreUtil2.getContainerOfType(astNode.eContainer, VariableEnvironmentElement); - if(vee===null) { - throw new IllegalArgumentException("given AST node does not have an outer variable environment"); - } - return switch(vee) { - Script: vee.scriptElements - FunctionOrFieldAccessor: vee.body.statements - CatchBlock: vee.block.statements - PropertyAssignment: getContainingVariableEnvironmentContent(vee) - WithStatement: { - if(!(vee.statement instanceof Block)) { - vee.statement = _Block(vee.statement); - } - (vee.statement as Block).statements - } - }; - } - - def private static VariableDeclaration getVariableDeclarationFromSTE(SymbolTableEntry ste) { - return ste.elementsOfThisName.filter(VariableDeclaration).head; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumAccessTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumAccessTransformation.java new file mode 100644 index 0000000000..814f6d0e24 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumAccessTransformation.java @@ -0,0 +1,221 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableStatement; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4NumberBasedEnumType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4StringBasedEnumType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toSet; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.Literal; +import org.eclipse.n4js.n4JS.ParenExpression; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.im.IdentifierRef_IM; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.utils.TranspilerUtils; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TEnumLiteral; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind; + +/** + * Transforms references to literals of @StringBased enums by replacing them with a plain string literal. + * References to ordinary enums (i.e. not string-based) are not touched by this transformation. + */ +public class EnumAccessTransformation extends Transformation { + + private final Map literalsConstants = new HashMap<>(); // need not be linked + private TMember member_NumberBasedEnum_literals; + private TMember member_StringBasedEnum_literals; + + @Override + public void assertPreConditions() { + assertNotNull("member of built-in type not found: NumberBasedEnum#literals", member_NumberBasedEnum_literals); + assertNotNull("member of built-in type not found: StringBasedEnum#literals", member_StringBasedEnum_literals); + } + + @Override + public void assertPostConditions() { + // empty + } + + @Override + public void analyze() { + member_NumberBasedEnum_literals = n4NumberBasedEnumType(getState().G).findOwnedMember("literals", false, true); + member_StringBasedEnum_literals = n4StringBasedEnumType(getState().G).findOwnedMember("literals", false, true); + } + + @Override + public void transform() { + for (ParameterizedPropertyAccessExpression_IM ppae : collectNodes(getState().im, + ParameterizedPropertyAccessExpression_IM.class, true)) { + transformEnumAccess(ppae); + } + } + + private void transformEnumAccess(ParameterizedPropertyAccessExpression_IM expr) { + SymbolTableEntry propSTE = expr.getProperty_IM(); + if (propSTE instanceof SymbolTableEntryOriginal) { + IdentifiableElement prop = ((SymbolTableEntryOriginal) propSTE).getOriginalTarget(); + if (prop instanceof TEnumLiteral) { + transformEnumLiteralAccess(resolveOriginalNumberOrStringBasedEnum(expr), expr, (TEnumLiteral) prop); + } + if (prop == member_StringBasedEnum_literals || prop == member_NumberBasedEnum_literals) + transformEnumLiteralsConstantAccess(resolveOriginalNumberOrStringBasedEnum(expr), expr); + } + + } + + /** + * Assumes {@code originalEnum} was resolved to string based enum, e,g, + * @StringBased enum Color {RED : "red", SOME}, transforms access to Color.RED to + * "red" and Color.SOME to "SOME".It is low level transformation, assumes + * caller did all necessary checks and did provide proper data. Replacement is applied only if none of the provided + * parameters is {@code null}. + * + * @param originalEnum + * string based enum from AST for which literal access is generated. + * @param expr + * expression which will have value generated. + * @param prop + * enum literal from which value is generated. + */ + private void transformEnumLiteralAccess(TEnum originalEnum, ParameterizedPropertyAccessExpression_IM expr, + TEnumLiteral prop) { + if (originalEnum != null && expr != null && prop != null) { + replace(expr, TranspilerUtils.enumLiteralToNumericOrStringLiteral(prop)); + } + } + + /** + * Assumes {@code originalEnum} was resolved to string based enum, e,g, + * @StringBased enum Color {RED : "red", SOME}, transforms access to Color.literals to + * ["red", "SOME". It is low level transformation, assumes caller did all necessary checks and did + * provide proper data. Replacement is applied only if none of the provided parameters is {@code null}. + * + * @param originalEnum + * string based enum from AST for which literals access is generated. + * @param expr + * expression which will have values generated. + */ + private void transformEnumLiteralsConstantAccess(TEnum originalEnum, + ParameterizedPropertyAccessExpression_IM expr) { + if (originalEnum != null && expr != null) { + replace(expr, getReferenceToLiteralsConstant(originalEnum)); + } + } + + /** + * Resolves left hand side of the {@code ParameterizedPropertyAccessExpression_IM} to the original + * {@link AnnotationDefinition#STRING_BASED} enum. + * + * @return resolved original string based enum or null. + */ + private TEnum resolveOriginalNumberOrStringBasedEnum(ParameterizedPropertyAccessExpression_IM pex) { + SymbolTableEntry targetEnumSTE = resolveOriginalExpressionTarget(pex.getTarget()); + if (targetEnumSTE instanceof SymbolTableEntryOriginal) { + IdentifiableElement original = ((SymbolTableEntryOriginal) targetEnumSTE).getOriginalTarget(); + if (original instanceof TEnum) { + if (N4JSLanguageUtils.getEnumKind((TEnum) original) != EnumKind.Normal) { + return (TEnum) original; + } + } + } + return null; + } + + /** + * Resolve target of the {@code ParameterizedPropertyAccessExpression_IM} when left hand side is an simple access or + * nested expression, e.g. for + *

    + *
  • Enum.EnumLiteral
  • + *
  • Enum.literals
  • + *
  • (((((Enum)))).EnumLiteral
  • + *
  • (((((Enum)))).literals
  • + *
+ * resolves to {@code SymbolTableEntry} of the Enum. + */ + private SymbolTableEntry resolveOriginalExpressionTarget(Expression ex) { + if (ex instanceof IdentifierRef_IM) { + return ((IdentifierRef_IM) ex).getRewiredTarget(); + } + if (ex instanceof ParameterizedPropertyAccessExpression_IM) { + return ((ParameterizedPropertyAccessExpression_IM) ex).getProperty_IM(); + } + if (ex instanceof ParenExpression) { + return resolveOriginalExpressionTarget(((ParenExpression) ex).getExpression()); + } + return null; + } + + private IdentifierRef_IM getReferenceToLiteralsConstant(TEnum tEnum) { + var vdecl = literalsConstants.get(tEnum); + if (vdecl == null) { + vdecl = createLiteralsConstant(tEnum); + literalsConstants.put(tEnum, vdecl); + } + // note: always have to create a new reference + return _IdentRef(findSymbolTableEntryForElement(vdecl, false)); + } + + private VariableDeclaration createLiteralsConstant(TEnum tEnum) { + String name = findUniqueNameForLiteralsConstant(tEnum); + + List literals = toList(map( + tEnum.getLiterals(), l -> TranspilerUtils.enumLiteralToNumericOrStringLiteral(l))); + VariableDeclaration vdecl = _VariableDeclaration(name, _ArrLit(literals.toArray(new Literal[0]))); + VariableStatement vstmnt = _VariableStatement(vdecl); + ImportDeclaration lastImport = last(filter(getState().im.getScriptElements(), ImportDeclaration.class)); + if (lastImport != null) { + insertAfter(lastImport, vstmnt); + } else if (!getState().im.getScriptElements().isEmpty()) { + insertBefore(getState().im.getScriptElements().get(0), vstmnt); + } else { + getState().im.getScriptElements().add(vstmnt); + } + createSymbolTableEntryIMOnly(vdecl); + return vdecl; + } + + private String findUniqueNameForLiteralsConstant(TEnum tEnum) { + Set names = toSet(map(literalsConstants.values(), lc -> lc.getName())); + var newName = "$enumLiteralsOf" + (tEnum != null && tEnum.getName() != null ? tEnum.getName() : "Unnamed"); + if (names.contains(newName)) { + int cnt = 1; + while (names.contains(newName + cnt)) { + cnt++; + } + newName = newName + cnt; + } + return newName; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumAccessTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumAccessTransformation.xtend deleted file mode 100644 index 8d62712e14..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumAccessTransformation.xtend +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import java.util.Map -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ParenExpression -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.im.IdentifierRef_IM -import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.transpiler.utils.TranspilerUtils -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TEnumLiteral -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Transforms references to literals of @StringBased enums by replacing them with a plain string literal. - * References to ordinary enums (i.e. not string-based) are not touched by this transformation. - */ -class EnumAccessTransformation extends Transformation { - - private final Map literalsConstants = newHashMap; // need not be linked - private TMember member_NumberBasedEnum_literals; - private TMember member_StringBasedEnum_literals; - - - override assertPreConditions() { - assertNotNull("member of built-in type not found: NumberBasedEnum#literals", member_NumberBasedEnum_literals); - assertNotNull("member of built-in type not found: StringBasedEnum#literals", member_StringBasedEnum_literals); - } - - override assertPostConditions() { - } - - override analyze() { - member_NumberBasedEnum_literals = state.G.n4NumberBasedEnumType.findOwnedMember("literals", false, true); - member_StringBasedEnum_literals = state.G.n4StringBasedEnumType.findOwnedMember("literals", false, true); - } - - override transform() { - collectNodes(state.im, ParameterizedPropertyAccessExpression_IM, true).forEach[transformEnumAccess]; - } - - def private void transformEnumAccess(ParameterizedPropertyAccessExpression_IM expr) { - val propSTE = expr.property_IM; - val prop = if (propSTE instanceof SymbolTableEntryOriginal) propSTE.originalTarget; - switch prop { - TEnumLiteral: - resolveOriginalNumberOrStringBasedEnum(expr).transformEnumLiteralAccess(expr, prop) - case prop === member_StringBasedEnum_literals || prop === member_NumberBasedEnum_literals: - resolveOriginalNumberOrStringBasedEnum(expr).transformEnumLiteralsConstantAccess(expr) - } - } - - /** - * Assumes {@code originalEnum} was resolved to string based enum, e,g, @StringBased enum Color {RED : "red", SOME}, - * transforms access to Color.RED to "red" and Color.SOME to "SOME".It is low level transformation, assumes - * caller did all necessary checks and did provide proper data. Replacement is applied only if none of the provided parameters is {@code null}. - * - * @param originalEnum string based enum from AST for which literal access is generated. - * @param expr expression which will have value generated. - * @param prop enum literal from which value is generated. - */ - private def transformEnumLiteralAccess(TEnum originalEnum, ParameterizedPropertyAccessExpression_IM expr, TEnumLiteral prop) { - if (originalEnum !== null && expr !== null && prop !== null) { - replace(expr, TranspilerUtils.enumLiteralToNumericOrStringLiteral(prop)); - } - } - - /** - * Assumes {@code originalEnum} was resolved to string based enum, e,g, @StringBased enum Color {RED : "red", SOME}, - * transforms access to Color.literals to ["red", "SOME". It is low level transformation, assumes - * caller did all necessary checks and did provide proper data. Replacement is applied only if none of the provided parameters is {@code null}. - * - * @param originalEnum string based enum from AST for which literals access is generated. - * @param expr expression which will have values generated. - */ - private def transformEnumLiteralsConstantAccess(TEnum originalEnum, ParameterizedPropertyAccessExpression_IM expr) { - if (originalEnum !== null && expr !== null) { - replace(expr, getReferenceToLiteralsConstant(originalEnum)); - } - } - - /** - * Resolves left hand side of the {@code ParameterizedPropertyAccessExpression_IM} to the original {@link AnnotationDefinition#STRING_BASED} enum. - * - * @return resolved original string based enum or null. - */ - def private TEnum resolveOriginalNumberOrStringBasedEnum(ParameterizedPropertyAccessExpression_IM pex) { - val targetEnumSTE = resolveOriginalExpressionTarget(pex.target) - if (targetEnumSTE instanceof SymbolTableEntryOriginal) { - val original = targetEnumSTE.originalTarget; - if (original instanceof TEnum) { - if (N4JSLanguageUtils.getEnumKind(original) !== EnumKind.Normal) { - return original; - } - } - } - return null; - } - - /** - * Resolve target of the {@code ParameterizedPropertyAccessExpression_IM} when left hand side is an simple access or nested expression, e.g. for - *
    - *
  • Enum.EnumLiteral
  • - *
  • Enum.literals
  • - *
  • (((((Enum)))).EnumLiteral
  • - *
  • (((((Enum)))).literals
  • - *
- * resolves to {@code SymbolTableEntry} of the Enum. - */ - def private SymbolTableEntry resolveOriginalExpressionTarget(Expression ex){ - switch ex { - IdentifierRef_IM : ex.rewiredTarget - ParameterizedPropertyAccessExpression_IM : ex.property_IM - ParenExpression : resolveOriginalExpressionTarget(ex.expression) - default : null - } - } - - def private getReferenceToLiteralsConstant(TEnum tEnum) { - var vdecl = literalsConstants.get(tEnum); - if (vdecl === null) { - vdecl = createLiteralsConstant(tEnum); - literalsConstants.put(tEnum, vdecl); - } - // note: always have to create a new reference - return _IdentRef(findSymbolTableEntryForElement(vdecl, false)); - } - - def private VariableDeclaration createLiteralsConstant(TEnum tEnum) { - val name = findUniqueNameForLiteralsConstant(tEnum); - val vdecl = _VariableDeclaration(name, _ArrLit(tEnum.literals.map[TranspilerUtils.enumLiteralToNumericOrStringLiteral(it)])); - val vstmnt = _VariableStatement(vdecl); - val lastImport = state.im.scriptElements.filter(ImportDeclaration).last; - if (lastImport !== null) { - insertAfter(lastImport, vstmnt); - } else if (!state.im.scriptElements.empty) { - insertBefore(state.im.scriptElements.head, vstmnt); - } else { - state.im.scriptElements.add(vstmnt); - } - createSymbolTableEntryIMOnly(vdecl); - return vdecl; - } - - def private String findUniqueNameForLiteralsConstant(TEnum tEnum) { - val names = literalsConstants.values.map[name].toSet; - var newName = "$enumLiteralsOf" + (tEnum?.name ?: "Unnamed"); - if (names.contains(newName)) { - var cnt = 1; - while (names.contains(newName + cnt)) { - cnt++; - } - newName = newName + cnt; - } - return newName; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumDeclarationTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumDeclarationTransformation.java new file mode 100644 index 0000000000..2622ab216a --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumDeclarationTransformation.java @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ExprStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Fpar; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4FieldDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4MethodDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NewExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._SuperLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._TypeReferenceNode; +import static org.eclipse.n4js.transpiler.utils.TranspilerUtils.orContainingExportDeclaration; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4EnumTypeRef; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4EnumLiteral; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4JSFactory; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.RequiresAfter; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.es.assistants.ReflectionAssistant; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryIMOnly; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind; + +import com.google.inject.Inject; + +/** + * Transforms {@link N4EnumDeclaration}s into a corresponding class declaration (for ordinary enums) or removes them + * entirely (for @StringBased enums). + */ +@RequiresAfter(ClassDeclarationTransformation.class) +public class EnumDeclarationTransformation extends Transformation { + + @Inject + private ReflectionAssistant reflectionAssistant; + @Inject + private TypeAssistant typeAssistant; + + @Override + public void assertPreConditions() { + assertFalse("only top-level enums are supported, for now", + exists(filter(getState().im.eAllContents(), N4EnumDeclaration.class), + ed -> !typeAssistant.isTopLevel(ed))); + } + + @Override + public void assertPostConditions() { + assertFalse("there should not be any N4EnumDeclarations in the intermediate model", + exists(getState().im.eAllContents(), elem -> elem instanceof N4EnumDeclaration)); + } + + @Override + public void analyze() { + // nothing to do + } + + @Override + public void transform() { + for (N4EnumDeclaration ed : collectNodes(getState().im, N4EnumDeclaration.class, false)) { + transformEnumDecl(ed); + } + } + + private void transformEnumDecl(N4EnumDeclaration enumDecl) { + EnumKind enumKind = N4JSLanguageUtils.getEnumKind(enumDecl); + if (enumKind != EnumKind.Normal) { + // declarations of number/string-based enums are simply removed + // (they do not have a representation in the output code) + EObject root = orContainingExportDeclaration(enumDecl); + remove(root); + return; + } + + N4ClassDeclaration classDecl = N4JSFactory.eINSTANCE.createN4ClassDeclaration(); + classDecl.setName(enumDecl.getName()); // need to set name before creating a symbol table entry + SymbolTableEntryIMOnly classSTE = createSymbolTableEntryIMOnly(classDecl); + + List fieldsForLiterals = toList( + map(enumDecl.getLiterals(), l -> convertLiteralToField(l, classSTE))); + + classDecl.getDeclaredModifiers().addAll(enumDecl.getDeclaredModifiers()); // reuse existing modifiers + classDecl.setSuperClassRef(_TypeReferenceNode(getState(), n4EnumTypeRef(getState().G))); + + classDecl.getOwnedMembersRaw().add(createEnumConstructor()); + classDecl.getOwnedMembersRaw().addAll(fieldsForLiterals); + classDecl.getOwnedMembersRaw().add(_N4FieldDecl( + true, + "literals", + _ArrLit(toList(map(fieldsForLiterals, + fd -> _PropertyAccessExpr(classSTE, findSymbolTableEntryForElement(fd, true)))) + .toArray(new ParameterizedPropertyAccessExpression_IM[0])))); + + // add 'n4type' getter for reflection + reflectionAssistant.addN4TypeGetter(enumDecl, classDecl); + + getState().tracer.copyTrace(enumDecl, classDecl); + + replace(enumDecl, classDecl); + } + + private N4MethodDeclaration createEnumConstructor() { + // constructor(name, value) { + // super(name, value); + // } + FormalParameter nameFpar = _Fpar("name"); + FormalParameter valueFpar = _Fpar("value"); + SymbolTableEntryIMOnly nameSTE = createSymbolTableEntryIMOnly(nameFpar); + SymbolTableEntryIMOnly valueSTE = createSymbolTableEntryIMOnly(valueFpar); + return _N4MethodDecl( + N4JSLanguageConstants.CONSTRUCTOR, + new FormalParameter[] { nameFpar, valueFpar }, + new Statement[] { + _ExprStmnt( + _CallExpr(_SuperLiteral(), _IdentRef(nameSTE), _IdentRef(valueSTE))) + }); + } + + private N4FieldDeclaration convertLiteralToField(N4EnumLiteral literal, SymbolTableEntry classSTE) { + Object value = N4JSLanguageUtils.getEnumLiteralValue(literal); + return _N4FieldDecl( + true, + literal.getName(), + _NewExpr( + _IdentRef(classSTE), + _StringLiteral(literal.getName()), + (value instanceof String) ? _StringLiteral((String) value) : null)); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumDeclarationTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumDeclarationTransformation.xtend deleted file mode 100644 index 095a53fd62..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/EnumDeclarationTransformation.xtend +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.N4EnumDeclaration -import org.eclipse.n4js.n4JS.N4EnumLiteral -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4JSFactory -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.RequiresAfter -import org.eclipse.n4js.transpiler.assistants.TypeAssistant -import org.eclipse.n4js.transpiler.es.assistants.ReflectionAssistant -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.transpiler.utils.TranspilerUtils.* -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Transforms {@link N4EnumDeclaration}s into a corresponding class declaration (for ordinary enums) or - * removes them entirely (for @StringBased enums). - */ -@RequiresAfter(ClassDeclarationTransformation) -class EnumDeclarationTransformation extends Transformation { - - @Inject private ReflectionAssistant reflectionAssistant; - @Inject private TypeAssistant typeAssistant; - - - override assertPreConditions() { - assertFalse("only top-level enums are supported, for now", - state.im.eAllContents.filter(N4EnumDeclaration).exists[!typeAssistant.isTopLevel(it)]); - } - - override assertPostConditions() { - assertFalse("there should not be any N4EnumDeclarations in the intermediate model", - state.im.eAllContents.exists[it instanceof N4EnumDeclaration]); - } - - override analyze() { - // nothing to do - } - - override transform() { - collectNodes(state.im, N4EnumDeclaration, false).forEach[transformEnumDecl]; - } - - def private void transformEnumDecl(N4EnumDeclaration enumDecl) { - val enumKind = N4JSLanguageUtils.getEnumKind(enumDecl); - if(enumKind !== EnumKind.Normal) { - // declarations of number/string-based enums are simply removed - // (they do not have a representation in the output code) - val root = enumDecl.orContainingExportDeclaration; - remove(root); - return; - } - - val classDecl = N4JSFactory.eINSTANCE.createN4ClassDeclaration; - classDecl.name = enumDecl.name; // need to set name before creating a symbol table entry - val classSTE = createSymbolTableEntryIMOnly(classDecl); - - val fieldsForLiterals = enumDecl.literals.map[convertLiteralToField(it, classSTE)]; - - classDecl.declaredModifiers += enumDecl.declaredModifiers; // reuse existing modifiers - classDecl.superClassRef = _TypeReferenceNode(state, state.G.n4EnumTypeRef); - - classDecl.ownedMembersRaw += createEnumConstructor(); - classDecl.ownedMembersRaw += fieldsForLiterals; - classDecl.ownedMembersRaw += _N4FieldDecl( - true, - "literals", - _ArrLit(fieldsForLiterals.map[ - _PropertyAccessExpr(classSTE, findSymbolTableEntryForElement(it, true)) - ]) - ); - - // add 'n4type' getter for reflection - reflectionAssistant.addN4TypeGetter(enumDecl, classDecl); - - state.tracer.copyTrace(enumDecl, classDecl); - - replace(enumDecl, classDecl); - } - - def private N4MethodDeclaration createEnumConstructor() { - // constructor(name, value) { - // super(name, value); - // } - val nameFpar = _Fpar("name"); - val valueFpar = _Fpar("value"); - val nameSTE = createSymbolTableEntryIMOnly(nameFpar); - val valueSTE = createSymbolTableEntryIMOnly(valueFpar); - return _N4MethodDecl( - N4JSLanguageConstants.CONSTRUCTOR, - #[ nameFpar, valueFpar ], - #[ - _ExprStmnt( - _CallExpr(_SuperLiteral, _IdentRef(nameSTE), _IdentRef(valueSTE)) - ) - ] - ); - } - - def private N4FieldDeclaration convertLiteralToField(N4EnumLiteral literal, SymbolTableEntry classSTE) { - val value = N4JSLanguageUtils.getEnumLiteralValue(literal); - return _N4FieldDecl( - true, - literal.name, - _NewExpr( - _IdentRef(classSTE), - _StringLiteral(literal.name), - if (value instanceof String) _StringLiteral(value) - ) - ); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ExpressionTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ExpressionTransformation.java new file mode 100644 index 0000000000..74bdee6966 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ExpressionTransformation.java @@ -0,0 +1,314 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrayElement; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._BooleanLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteralForSTE; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isIterableN; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4ElementType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4NamedElementType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4ObjectType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4TypeType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.ListExtensions.reverse; + +import java.util.List; + +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.ArrayElement; +import org.eclipse.n4js.n4JS.AwaitExpression; +import org.eclipse.n4js.n4JS.CastExpression; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ExpressionWithTarget; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.PromisifyExpression; +import org.eclipse.n4js.scoping.builtin.N4Scheme; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.im.IdentifierRef_IM; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.utils.PromisifyHelper; +import org.eclipse.n4js.utils.ResourceNameComputer; + +import com.google.inject.Inject; + +/** + * Some expressions need special handling, this is done in this transformation. + */ +public class ExpressionTransformation extends Transformation { + + @Inject + private ResourceNameComputer resourceNameComputer; + @Inject + private PromisifyHelper promisifyHelper; + + private TGetter n4Object_n4type; + private TGetter n4NamedElement_name; + private TGetter n4Element_origin; + private TGetter n4Type_fqn; + + @Override + public void assertPreConditions() { + // true + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + n4Object_n4type = (TGetter) n4ObjectType(getState().G).findOwnedMember(N4JSLanguageConstants.N4TYPE_NAME, false, + true); + n4NamedElement_name = (TGetter) n4NamedElementType(getState().G).findOwnedMember("name", false, false); + n4Element_origin = (TGetter) n4ElementType(getState().G).findOwnedMember("origin", false, false); + n4Type_fqn = (TGetter) n4TypeType(getState().G).findOwnedMember("fqn", false, false); + + if (n4Object_n4type == null + || n4NamedElement_name == null + || n4Element_origin == null + || n4Type_fqn == null) { + throw new IllegalStateException("could not find required members of built-in types"); + } + } + + @Override + public void transform() { + for (Expression expr : reverse(collectNodes(getState().im, Expression.class, true))) { + // transforming expressions in bottom-up order (it's more natural and simplifies some nesting cases) + if (expr instanceof CastExpression) { + transformExpression((CastExpression) expr); + } else if (expr instanceof ExpressionWithTarget) { + transformExpression((ExpressionWithTarget) expr); + } else if (expr instanceof AwaitExpression) { + transformExpression((AwaitExpression) expr); + } else if (expr instanceof PromisifyExpression) { + transformExpression((PromisifyExpression) expr); + } + } + } + + private void transformExpression(CastExpression castExpr) { + replace(castExpr, castExpr.getExpression()); // simply remove the cast + } + + private void transformExpression(ExpressionWithTarget exprWithTarget) { + if (exprWithTarget instanceof ParameterizedPropertyAccessExpression_IM) { + if (transformTrivialUsageOfReflection((ParameterizedPropertyAccessExpression_IM) exprWithTarget)) { + return; + } + } + } + + /** + * IMPORTANT: here we only do some special handling for the auto-promisify case within an AwaitExpression; + * the main handling of the AwaitExpression itself is done in a later transformation + * {@code BlockTransformation.transformBlockAsync(Block)}!!! + *

+ * Changes + * + *

+	 * await cls.meth(a, b)
+	 * 
+ * + * to + * + *
+	 * await $n4promisifyMethod(cls, 'meth', [a, b])
+	 * 
+ * + * OR + * + *
+	 * await fun(a, b)
+	 * 
+ * + * to + * + *
+	 * await $n4promisifyFunction(fun, [a, b])
+	 * 
+ * + * assuming that method 'meth' and function 'fun' are annotated with @Promisifiable. + */ + private void transformExpression(AwaitExpression awaitExpr) { + AwaitExpression awaitExprOrig = getState().tracer.getOriginalASTNodeOfSameType(awaitExpr, false); + if (promisifyHelper.isAutoPromisify(awaitExprOrig)) { + // cast is safe because isPromisifiableExpression() returned true + ParameterizedCallExpression callExpr = (ParameterizedCallExpression) awaitExpr.getExpression(); + ParameterizedCallExpression replacement = promisify(callExpr); + // note: leaving awaitExpr in the IM; only replacing its contained expression!! + replace(callExpr, replacement); + } + } + + /** + * Changes + * + *
+	 * @Promisify cls.meth(a, b)
+	 * 
+ * + * to + * + *
+	 * $n4promisifyMethod(cls, 'meth', [a, b])
+	 * 
+ * + * OR + * + *
+	 * @Promisify fun(a, b)
+	 * 
+ * + * to + * + *
+	 * $n4promisifyFunction(fun, [a, b])
+	 * 
+ */ + private void transformExpression(PromisifyExpression promiExpr) { + // cast is safe because of validations + ParameterizedCallExpression callExpr = (ParameterizedCallExpression) promiExpr.getExpression(); + ParameterizedCallExpression replacement = promisify(callExpr); + replace(promiExpr, replacement); + } + + private ParameterizedCallExpression promisify(ParameterizedCallExpression callExpr) { + Expression target = callExpr.getTarget(); + SymbolTableEntry targetSTE = null; + if (target instanceof ParameterizedPropertyAccessExpression_IM) { + targetSTE = ((ParameterizedPropertyAccessExpression_IM) target).getProperty_IM(); + } + if (target instanceof IdentifierRef_IM) { + targetSTE = ((IdentifierRef_IM) target).getId_IM(); + } + if (targetSTE instanceof SymbolTableEntryOriginal) { + SymbolTableEntryOriginal steo = (SymbolTableEntryOriginal) targetSTE; + IdentifiableElement originalTarget = steo.getOriginalTarget(); + if (originalTarget instanceof TFunction) { // could be a method + FunctionTypeRef originalTargetTypeRef = (FunctionTypeRef) TypeUtils + .createTypeRef((TFunction) originalTarget); + var returnTypeRef = promisifyHelper.extractPromisifiedReturnType(getState().G, originalTargetTypeRef); + if (returnTypeRef != null) { + List returnTypeRefTypeArgs = returnTypeRef.getTypeArgsWithDefaults(); + // isUndefined() is null-safe + boolean hasErrorValue = !TypeUtils.isUndefined(returnTypeRefTypeArgs.get(1)); + // isIterableN() is null-safe + boolean hasMoreThan1SuccessValue = isIterableN(getState().G, returnTypeRefTypeArgs.get(0)); + + ArrayElement[] arrayElements = toList(map(callExpr.getArguments(), + arg -> _ArrayElement(arg.isSpread(), arg.getExpression()))).toArray(new ArrayElement[0]); + + if (target instanceof ParameterizedPropertyAccessExpression_IM + && steo.getOriginalTarget() instanceof TMethod) { + // we have a method invocation, so we need to preserve the 'this' argument: + + // @Promisify cls.meth(a, b) + // --> + // $n4promisifyMethod(cls, 'meth', [a, b]) + return _CallExpr( + _IdentRef(steFor_$n4promisifyMethod()), + // here we take the "cls" part of "cls.meth" as first argument + ((ParameterizedPropertyAccessExpression_IM) target).getTarget(), + _StringLiteralForSTE(targetSTE), + _ArrLit(arrayElements), // reuse arguments while preserving spread + _BooleanLiteral(hasMoreThan1SuccessValue), + _BooleanLiteral(!hasErrorValue)); + } else { + // in all other cases, we do not preserve the 'this' argument: + + // @Promisify fun(a, b) + // --> + // $n4promisifyFunction(fun, [a, b]) + return _CallExpr( + _IdentRef(steFor_$n4promisifyFunction()), + callExpr.getTarget(), // reuse target as first argument + _ArrLit(arrayElements), // reuse arguments while preserving spread + _BooleanLiteral(hasMoreThan1SuccessValue), + _BooleanLiteral(!hasErrorValue)); + } + } + } + } + // if anything goes awry, we just return callExpr as replacement, which means we simply remove the @Promisify + return callExpr; + } + + /** + * Replaces the following trivial uses of the reflection APIs by the resulting value (i.e. a string literal): + * + *
+	 * MyClass.n4type.name
+	 * MyClass.n4type.origin
+	 * MyClass.n4type.fqn
+	 * 
+ * + * Thus, reflection will not actually be used in the output code in the above cases. + */ + private boolean transformTrivialUsageOfReflection(ParameterizedPropertyAccessExpression_IM propAccessExpr) { + IdentifiableElement property = propAccessExpr.getOriginalTargetOfRewiredTarget(); + if (property == n4NamedElement_name + || property == n4Element_origin + || property == n4Type_fqn) { + + Expression target = propAccessExpr.getTarget(); + if (target instanceof ParameterizedPropertyAccessExpression_IM) { + ParameterizedPropertyAccessExpression_IM ppae = (ParameterizedPropertyAccessExpression_IM) target; + IdentifiableElement propertyOfTarget = ppae.getOriginalTargetOfRewiredTarget(); + if (propertyOfTarget == n4Object_n4type) { + Expression targetOfTarget = ppae.getTarget(); + if (targetOfTarget instanceof IdentifierRef_IM) { + IdentifiableElement id = ((IdentifierRef_IM) targetOfTarget).getOriginalTargetOfRewiredTarget(); + if (id instanceof TClass) { + String value = null; + if (property == n4NamedElement_name) { + value = id.getName(); + } else if (property == n4Element_origin) { + value = resourceNameComputer.generateProjectDescriptor(id.eResource()); + } else if (property == n4Type_fqn) { + // avoid optimizing this case for built-in types + // (we cannot know for sure the value of the 'fqn' property set in the .js files) + if (!N4Scheme.isFromResourceWithN4Scheme(id)) { + value = resourceNameComputer.getFullyQualifiedTypeName((TClass) id); + } + } else { + throw new IllegalStateException(); // should not happen (see above) + } + if (value != null) { + replace(propAccessExpr, _StringLiteral(value)); + return true; + } + } + } + } + } + } + return false; + } + +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ExpressionTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ExpressionTransformation.xtend deleted file mode 100644 index e5e6ed2454..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ExpressionTransformation.xtend +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.AwaitExpression -import org.eclipse.n4js.n4JS.CastExpression -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ExpressionWithTarget -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.n4JS.PromisifyExpression -import org.eclipse.n4js.scoping.builtin.N4Scheme -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.im.IdentifierRef_IM -import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.ts.typeRefs.FunctionTypeRef -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.utils.PromisifyHelper -import org.eclipse.n4js.utils.ResourceNameComputer - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Some expressions need special handling, this is done in this transformation. - */ -class ExpressionTransformation extends Transformation { - - @Inject private ResourceNameComputer resourceNameComputer; - @Inject private PromisifyHelper promisifyHelper; - - private TGetter n4Object_n4type; - private TGetter n4NamedElement_name; - private TGetter n4Element_origin; - private TGetter n4Type_fqn; - - override assertPreConditions() { - // true - } - - override assertPostConditions() { - // true - } - - override analyze() { - n4Object_n4type = state.G.n4ObjectType.findOwnedMember(N4JSLanguageConstants.N4TYPE_NAME, false, true) as TGetter; - n4NamedElement_name = state.G.n4NamedElementType.findOwnedMember("name", false, false) as TGetter; - n4Element_origin = state.G.n4ElementType.findOwnedMember("origin", false, false) as TGetter; - n4Type_fqn = state.G.n4TypeType.findOwnedMember("fqn", false, false) as TGetter; - if (n4Object_n4type === null - || n4NamedElement_name === null - || n4Element_origin === null - || n4Type_fqn === null) { - throw new IllegalStateException("could not find required members of built-in types"); - } - } - - override transform() { - collectNodes(state.im, Expression, true) - .reverse // transforming expressions in bottom-up order (it's more natural and simplifies some nesting cases) - .forEach[transformExpression]; - } - - def private dispatch void transformExpression(Expression expr) { - // default case -> do nothing - } - - def private dispatch void transformExpression(CastExpression castExpr) { - replace(castExpr, castExpr.expression); // simply remove the cast - } - - def private dispatch void transformExpression(ExpressionWithTarget exprWithTarget) { - if (exprWithTarget instanceof ParameterizedPropertyAccessExpression_IM) { - if (transformTrivialUsageOfReflection(exprWithTarget)) { - return; - } - } - } - - /** - * IMPORTANT: - * here we only do some special handling for the auto-promisify case within an AwaitExpression; the main - * handling of the AwaitExpression itself is done in a later transformation {@code BlockTransformation.transformBlockAsync(Block)}!!! - *

- * Changes - *

await cls.meth(a, b)
- * to - *
await $n4promisifyMethod(cls, 'meth', [a, b])
- * OR - *
await fun(a, b)
- * to - *
await $n4promisifyFunction(fun, [a, b])
- * assuming that method 'meth' and function 'fun' are annotated with @Promisifiable. - */ - def private dispatch void transformExpression(AwaitExpression awaitExpr) { - val awaitExprOrig = state.tracer.getOriginalASTNodeOfSameType(awaitExpr, false); - if(promisifyHelper.isAutoPromisify(awaitExprOrig)) { - val callExpr = awaitExpr.expression as ParameterizedCallExpression; // cast is safe because isPromisifiableExpression() returned true - val replacement = promisify(callExpr); - replace(callExpr, replacement); // note: leaving awaitExpr in the IM; only replacing its contained expression!! - } - } - - /** - * Changes - *
@Promisify cls.meth(a, b)
- * to - *
$n4promisifyMethod(cls, 'meth', [a, b])
- * OR - *
@Promisify fun(a, b)
- * to - *
$n4promisifyFunction(fun, [a, b])
- */ - def private dispatch void transformExpression(PromisifyExpression promiExpr) { - val callExpr = promiExpr.expression as ParameterizedCallExpression; // cast is safe because of validations - val replacement = promisify(callExpr); - replace(promiExpr, replacement); - } - - def private ParameterizedCallExpression promisify(ParameterizedCallExpression callExpr) { - val target = callExpr.target; - val targetSTE = switch(target) { - ParameterizedPropertyAccessExpression_IM: target.property_IM - IdentifierRef_IM: target.id_IM - }; - if(targetSTE instanceof SymbolTableEntryOriginal) { - val originalTarget = targetSTE.originalTarget; - if(originalTarget instanceof TFunction) { // could be a method - val originalTargetTypeRef = TypeUtils.createTypeRef(originalTarget) as FunctionTypeRef; - var returnTypeRef = promisifyHelper.extractPromisifiedReturnType(state.G, originalTargetTypeRef); - if (returnTypeRef !== null) { - val returnTypeRefTypeArgs = returnTypeRef.typeArgsWithDefaults; - val hasErrorValue = !TypeUtils.isUndefined(returnTypeRefTypeArgs.drop(1).head); // isUndefined() is null-safe - val hasMoreThan1SuccessValue = state.G.isIterableN(returnTypeRefTypeArgs.head); // isIterableN() is null-safe - if(target instanceof ParameterizedPropertyAccessExpression_IM && targetSTE.originalTarget instanceof TMethod) { - // we have a method invocation, so we need to preserve the 'this' argument: - - // @Promisify cls.meth(a, b) - // --> - // $n4promisifyMethod(cls, 'meth', [a, b]) - return _CallExpr( - _IdentRef(steFor_$n4promisifyMethod()), - (target as ParameterizedPropertyAccessExpression_IM).target, // here we take the "cls" part of "cls.meth" as first argument - _StringLiteralForSTE(targetSTE), - _ArrLit(callExpr.arguments.map[_ArrayElement(spread, expression)]), // reuse arguments while preserving spread - _BooleanLiteral(hasMoreThan1SuccessValue), - _BooleanLiteral(!hasErrorValue) - ); - } else { - // in all other cases, we do not preserve the 'this' argument: - - // @Promisify fun(a, b) - // --> - // $n4promisifyFunction(fun, [a, b]) - return _CallExpr( - _IdentRef(steFor_$n4promisifyFunction()), - callExpr.target, // reuse target as first argument - _ArrLit(callExpr.arguments.map[_ArrayElement(spread,expression)]), // reuse arguments while preserving spread - _BooleanLiteral(hasMoreThan1SuccessValue), - _BooleanLiteral(!hasErrorValue) - ); - } - } - } - } - // if anything goes awry, we just return callExpr as replacement, which means we simply remove the @Promisify - return callExpr; - } - - /** - * Replaces the following trivial uses of the reflection APIs by the resulting value (i.e. a string literal): - *
-	 * MyClass.n4type.name
-	 * MyClass.n4type.origin
-	 * MyClass.n4type.fqn
-	 * 
- * Thus, reflection will not actually be used in the output code in the above cases. - */ - def private boolean transformTrivialUsageOfReflection(ParameterizedPropertyAccessExpression_IM propAccessExpr) { - val property = propAccessExpr.originalTargetOfRewiredTarget; - if (property === n4NamedElement_name - || property === n4Element_origin - || property === n4Type_fqn) { - val target = propAccessExpr.target; - if (target instanceof ParameterizedPropertyAccessExpression_IM) { - val propertyOfTarget = target.originalTargetOfRewiredTarget; - if (propertyOfTarget === n4Object_n4type) { - val targetOfTarget = target.target; - if (targetOfTarget instanceof IdentifierRef_IM) { - val id = targetOfTarget.originalTargetOfRewiredTarget; - if (id instanceof TClass) { - val value = switch(property) { - case n4NamedElement_name: - id.name - case n4Element_origin: - resourceNameComputer.generateProjectDescriptor(id.eResource) - case n4Type_fqn: - // avoid optimizing this case for built-in types - // (we cannot know for sure the value of the 'fqn' property set in the .js files) - if (!N4Scheme.isFromResourceWithN4Scheme(id)) { - resourceNameComputer.getFullyQualifiedTypeName(id) - } - default: - throw new IllegalStateException() // should not happen (see above) - } - if (value !== null) { - replace(propAccessExpr, _StringLiteral(value)); - return true; - } - } - } - } - } - } - return false; - } - -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/InterfaceDeclarationTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/InterfaceDeclarationTransformation.java new file mode 100644 index 0000000000..2b61c314d5 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/InterfaceDeclarationTransformation.java @@ -0,0 +1,443 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrowFunc; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Block; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Fpar; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._LiteralOrComputedPropertyName; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4MethodDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ObjLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Parenthesis; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAssignmentAnnotationList; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyGetterDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyNameValuePair; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Snippet; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.utils.TranspilerUtils.orContainingExportDeclaration; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getGlobalObjectScope; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.symbolObjectType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.AdditiveExpression; +import org.eclipse.n4js.n4JS.AnnotablePropertyAssignment; +import org.eclipse.n4js.n4JS.Annotation; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.AwaitExpression; +import org.eclipse.n4js.n4JS.BinaryBitwiseExpression; +import org.eclipse.n4js.n4JS.BinaryLogicalExpression; +import org.eclipse.n4js.n4JS.BooleanLiteral; +import org.eclipse.n4js.n4JS.CastExpression; +import org.eclipse.n4js.n4JS.CommaExpression; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.GetterDeclaration; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.MultiplicativeExpression; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4JSFactory; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.NullLiteral; +import org.eclipse.n4js.n4JS.NumericLiteral; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.ParenExpression; +import org.eclipse.n4js.n4JS.PromisifyExpression; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.PropertyGetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyMethodDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameOwner; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.PropertySetterDeclaration; +import org.eclipse.n4js.n4JS.SetterDeclaration; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.n4JS.StringLiteral; +import org.eclipse.n4js.n4JS.UnaryExpression; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.YieldExpression; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.es.assistants.ClassifierAssistant; +import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant; +import org.eclipse.n4js.transpiler.es.assistants.ReflectionAssistant; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.utils.TranspilerUtils; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TypingStrategy; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.ResourceNameComputer; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.inject.Inject; + +/** + */ +public class InterfaceDeclarationTransformation extends Transformation { + + @Inject + private ClassifierAssistant classifierAssistant; + @Inject + private ReflectionAssistant reflectionAssistant; + @Inject + private DelegationAssistant delegationAssistant; + @Inject + private TypeAssistant typeAssistant; + @Inject + private ResourceNameComputer resourceNameComputer; + + @Override + public void assertPreConditions() { + typeAssistant.assertClassifierPreConditions(); + } + + @Override + public void assertPostConditions() { + // none + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + for (N4InterfaceDeclaration id : collectNodes(getState().im, N4InterfaceDeclaration.class, false)) { + transformInterfaceDecl(id); + } + } + + private void transformInterfaceDecl(N4InterfaceDeclaration ifcDecl) { + if (ifcDecl.getTypingStrategy() == TypingStrategy.STRUCTURAL) { + // structural interfaces are shapes, i.e. do only exist at compile time + EObject ifcOrParent = ifcDecl.eContainer() instanceof ExportDeclaration ? ifcDecl.eContainer() : ifcDecl; + remove(ifcOrParent); + return; + } + SymbolTableEntry ifcSTE = findSymbolTableEntryForElement(ifcDecl, true); + + // add 'Symbol.hasInstance' function for supporting the 'instanceof' operator + ifcDecl.getOwnedMembersRaw().add(createHasInstanceMethod(ifcDecl)); + + reflectionAssistant.addN4TypeGetter(ifcDecl, ifcDecl); + + List belowIfcDecl = new ArrayList<>(); + belowIfcDecl.addAll(createStaticFieldInitializations(ifcDecl, ifcSTE)); + insertAfter(orContainingExportDeclaration(ifcDecl), belowIfcDecl.toArray(new Statement[0])); + + ifcDecl.getOwnedMembersRaw().removeIf(m -> m.isStatic() && m instanceof N4FieldDeclaration); + delegationAssistant.replaceDelegatingMembersByOrdinaryMembers(ifcDecl); + + ObjectLiteral ifcObjLit = createInterfaceObject(ifcDecl); + VariableDeclaration varDecl = _VariableDeclaration(ifcDecl.getName(), ifcObjLit); + getState().tracer.copyTrace(ifcDecl, varDecl); + + replace(ifcDecl, varDecl); + } + + /** Creates an object literal for the object representing the interface of the given ifcDecl. */ + private ObjectLiteral createInterfaceObject(N4InterfaceDeclaration ifcDecl) { + List props = new ArrayList<>(); + ArrayLiteral extendedInterfaces = createDirectlyImplementedOrExtendedInterfacesArgument(ifcDecl); + if (!extendedInterfaces.getElements().isEmpty()) { + String $extends = steFor_$extendsInterfaces().getName(); + props.add(_PropertyGetterDecl( + $extends, + _ReturnStmnt(extendedInterfaces))); + } + props.addAll(createInstanceFieldDefaultsProperty(ifcDecl)); + props.addAll(createInstanceMemberPropertiesExceptFields(ifcDecl)); + props.addAll(createStaticMemberPropertiesExceptFields(ifcDecl)); + + return _ObjLit(props.toArray(new PropertyAssignment[0])); + } + + private ArrayLiteral createDirectlyImplementedOrExtendedInterfacesArgument(N4ClassifierDeclaration typeDecl) { + List interfaces = typeAssistant.getSuperInterfacesSTEs(typeDecl); + + // the return value of this method is intended for default method patching; for this purpose, we have to + // filter out some of the directly implemented interfaces: + Iterable directlyImplementedInterfacesFiltered = TranspilerUtils + .filterNominalInterfaces(interfaces); + return _ArrLit( + toList(map(directlyImplementedInterfacesFiltered, i -> _IdentRef(i))).toArray(new IdentifierRef[0])); + } + + @SuppressWarnings("unchecked") + private List createInstanceFieldDefaultsProperty(N4InterfaceDeclaration ifcDecl) { + // $fieldInits: { + // fieldName1: undefined, + // fieldName2: 42, + // fieldName3: () => , + // ... + // } + List instanceFields = toList( + filter(ifcDecl.getOwnedFields(), f -> !f.isStatic() && f.getName() != null)); + if (instanceFields.isEmpty()) { + return Collections.emptyList(); + } + List> nameValuePairs = new ArrayList<>(); + for (N4FieldDeclaration fd : instanceFields) { + nameValuePairs.add(Pair.of(fd.getName(), + hasNonTrivialInitExpression(fd) + ? (canSkipFunctionWrapping(fd.getExpression()) ? fd.getExpression() + : _ArrowFunc(false, new FormalParameter[0], + wrapInParenthesesIfNeeded(fd.getExpression()))) + : undefinedRef())); + } + + return List.of( + _PropertyNameValuePair( + steFor_$fieldInits().getName(), + _ObjLit(nameValuePairs.toArray(new Pair[0])))); + } + + private List createInstanceMemberPropertiesExceptFields(N4InterfaceDeclaration ifcDecl) { + // $defaultMembers: { + // get getter() { + // }, + // set setter(value) { + // }, + // method() { + // }, + // ... + // } + + List instanceMembersExceptFields = toList(filter(ifcDecl.getOwnedMembers(), + m -> !m.isStatic() && !m.isAbstract() && m.getName() != null && !(m instanceof N4FieldDeclaration))); + + if (instanceMembersExceptFields.isEmpty()) { + return Collections.emptyList(); + } + return List.of( + _PropertyNameValuePair( + steFor_$defaultMembers().getName(), + _ObjLit( + toList(map(instanceMembersExceptFields, m -> convertMemberToProperty(m))) + .toArray(new PropertyAssignment[0])))); + } + + private List createStaticMemberPropertiesExceptFields(N4InterfaceDeclaration ifcDecl) { + // get staticGetter() { + // }, + // set staticSetter(value) { + // }, + // staticMethod() { + // }, + // ... + + List staticMembersExceptFields = toList(filter(ifcDecl.getOwnedMembers(), + m -> m.isStatic() && !m.isAbstract() && m.getName() != null && !(m instanceof N4FieldDeclaration))); + + if (staticMembersExceptFields.isEmpty()) { + return Collections.emptyList(); + } + return toList(map(staticMembersExceptFields, m -> convertMemberToProperty(m))); + } + + /** + * Converts getter, setter, and method declarations to the corresponding declarations in object literals. Does not + * support conversion of field declarations! + */ + private PropertyAssignment convertMemberToProperty(N4MemberDeclaration memberDecl) { + // fields: + if (memberDecl instanceof N4FieldDeclaration) { + N4FieldDeclaration fieldDecl = (N4FieldDeclaration) memberDecl; + return _PropertyNameValuePair( + // reuse existing name + fieldDecl.getDeclaredName(), + // reuse existing initializer expression (if any) + fieldDecl.getExpression() != null ? fieldDecl.getExpression() : undefinedRef()); + } + // getters, setters, and methods: + AnnotablePropertyAssignment result = null; + if (memberDecl instanceof GetterDeclaration) { + PropertyGetterDeclaration gd = N4JSFactory.eINSTANCE.createPropertyGetterDeclaration(); + gd.setBody(((GetterDeclaration) memberDecl).getBody()); // reuse existing body + result = gd; + } else if (memberDecl instanceof SetterDeclaration) { + PropertySetterDeclaration sd = N4JSFactory.eINSTANCE.createPropertySetterDeclaration(); + sd.setFpar(((SetterDeclaration) memberDecl).getFpar()); // reuse existing fpar + sd.setBody(((SetterDeclaration) memberDecl).getBody()); // reuse existing body + result = sd; + } else if (memberDecl instanceof FunctionDefinition) { + FunctionDefinition fd = (FunctionDefinition) memberDecl; + PropertyMethodDeclaration pmd = N4JSFactory.eINSTANCE.createPropertyMethodDeclaration(); + pmd.getFpars().addAll(fd.getFpars()); // reuse existing fpar + pmd.setBody(fd.getBody()); // reuse existing body + pmd.setGenerator(fd.isGenerator()); + pmd.setDeclaredAsync(fd.isAsync()); + result = pmd; + } else { + throw new IllegalArgumentException("not a getter, setter, or method declaration"); + } + + result.setDeclaredName(((PropertyNameOwner) memberDecl).getDeclaredName()); // reuse existing name + if (!memberDecl.getAnnotations().isEmpty()) { + // reuse existing annotations + result.setAnnotationList( + _PropertyAssignmentAnnotationList(memberDecl.getAnnotations().toArray(new Annotation[0]))); + } + return result; + } + + private Expression wrapInParenthesesIfNeeded(Expression expr) { + if (expr instanceof CommaExpression + || expr instanceof ObjectLiteral + || expr instanceof AwaitExpression + || expr instanceof YieldExpression + || expr instanceof PromisifyExpression) { + return _Parenthesis(expr); + } + return expr; + } + + private boolean canSkipFunctionWrapping(Expression initExpression) { + if (initExpression instanceof BooleanLiteral + || initExpression instanceof NullLiteral + || initExpression instanceof NumericLiteral + || initExpression instanceof StringLiteral) { + return true; + } + if (initExpression instanceof UnaryExpression) { + UnaryExpression unaryExpr = (UnaryExpression) initExpression; + // WARNING: some unary operators have a side effect, so they have to be wrapped in a function! + if (canSkipFunctionWrapping(unaryExpr.getExpression())) { + switch (unaryExpr.getOp()) { + case INC: + case DEC: + case DELETE: + return false; + case POS: + case NEG: + case INV: + case NOT: + case TYPEOF: + case VOID: + return true; + } + } + } + if (initExpression instanceof AdditiveExpression) { + AdditiveExpression addExpr = (AdditiveExpression) initExpression; + if (canSkipFunctionWrapping(addExpr.getLhs()) + && canSkipFunctionWrapping(addExpr.getRhs())) { + + switch (addExpr.getOp()) { + case ADD: + case SUB: + return true; + } + } + } + if (initExpression instanceof MultiplicativeExpression) { + MultiplicativeExpression mulExpr = (MultiplicativeExpression) initExpression; + if (canSkipFunctionWrapping(mulExpr.getLhs()) + && canSkipFunctionWrapping(mulExpr.getRhs())) { + + switch (mulExpr.getOp()) { + case TIMES: + case DIV: + case MOD: + return true; + } + } + } + if (initExpression instanceof BinaryBitwiseExpression) { + BinaryBitwiseExpression bbExpr = (BinaryBitwiseExpression) initExpression; + if (canSkipFunctionWrapping(bbExpr.getLhs()) + && canSkipFunctionWrapping(bbExpr.getRhs())) { + + switch (bbExpr.getOp()) { + case OR: + case XOR: + case AND: + return true; + } + } + } + if (initExpression instanceof BinaryLogicalExpression) { + BinaryLogicalExpression blExpr = (BinaryLogicalExpression) initExpression; + if (canSkipFunctionWrapping(blExpr.getLhs()) + && canSkipFunctionWrapping(blExpr.getRhs())) { + + switch (blExpr.getOp()) { + case OR: + case AND: + return true; + } + } + } + if (initExpression instanceof CastExpression) { + return canSkipFunctionWrapping(((CastExpression) initExpression).getExpression()); + } + if (initExpression instanceof ParenExpression) { + return canSkipFunctionWrapping(((ParenExpression) initExpression).getExpression()); + } + if (initExpression instanceof IdentifierRef) { + IdentifierRef idRef = (IdentifierRef) initExpression; + return idRef.getId() == getGlobalObjectScope(getState().G).getFieldUndefined(); + } + + return false; + } + + private List createStaticFieldInitializations(N4InterfaceDeclaration ifcDecl, + SymbolTableEntry ifcSTE) { + return classifierAssistant.createStaticFieldInitializations(ifcDecl, ifcSTE, Collections.emptySet()); + } + + private N4MethodDeclaration createHasInstanceMethod(N4InterfaceDeclaration ifcDecl) { + TClass symbolObjectType = symbolObjectType(getState().G); + SymbolTableEntryOriginal symbolSTE = getSymbolTableEntryOriginal(symbolObjectType, true); + SymbolTableEntryOriginal hasInstanceSTE = getSymbolTableEntryForMember(symbolObjectType, "hasInstance", false, + true, true); + ParameterizedPropertyAccessExpression_IM hasInstanceExpr = _PropertyAccessExpr(symbolSTE, hasInstanceSTE); + LiteralOrComputedPropertyName declaredName = _LiteralOrComputedPropertyName(hasInstanceExpr, + N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX + "hasInstance"); + + TInterface ifcType = getState().info.getOriginalDefinedType(ifcDecl); + String fqn = resourceNameComputer.getFullyQualifiedTypeName(ifcType); + + N4MethodDeclaration result = _N4MethodDecl(true, declaredName, new FormalParameter[] { _Fpar("instance") }, + _Block( + _ReturnStmnt( + _Snippet("instance && instance.constructor && instance.constructor.n4type " + // required because we cannot be sure "instance.constructor.n4type" is of type + // N4Classifier + + "&& instance.constructor.n4type.allImplementedInterfaces " + + "&& instance.constructor.n4type.allImplementedInterfaces.indexOf('" + fqn + + "') !== -1")))); + + getState().info.markAsHiddenFromReflection(result); + + return result; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/InterfaceDeclarationTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/InterfaceDeclarationTransformation.xtend deleted file mode 100644 index d1321ca496..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/InterfaceDeclarationTransformation.xtend +++ /dev/null @@ -1,368 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import java.util.Collections -import java.util.List -import org.eclipse.n4js.n4JS.AdditiveExpression -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.AwaitExpression -import org.eclipse.n4js.n4JS.BinaryBitwiseExpression -import org.eclipse.n4js.n4JS.BinaryLogicalExpression -import org.eclipse.n4js.n4JS.BooleanLiteral -import org.eclipse.n4js.n4JS.CastExpression -import org.eclipse.n4js.n4JS.CommaExpression -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.n4JS.GetterDeclaration -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.MultiplicativeExpression -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.n4JS.N4JSFactory -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.n4JS.NullLiteral -import org.eclipse.n4js.n4JS.NumericLiteral -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.ParenExpression -import org.eclipse.n4js.n4JS.PromisifyExpression -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.PropertyNameOwner -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.SetterDeclaration -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.n4JS.StringLiteral -import org.eclipse.n4js.n4JS.UnaryExpression -import org.eclipse.n4js.n4JS.YieldExpression -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.assistants.TypeAssistant -import org.eclipse.n4js.transpiler.es.assistants.ClassifierAssistant -import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant -import org.eclipse.n4js.transpiler.es.assistants.ReflectionAssistant -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.transpiler.utils.TranspilerUtils -import org.eclipse.n4js.ts.types.TypingStrategy -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.ResourceNameComputer - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.transpiler.utils.TranspilerUtils.* -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - */ -class InterfaceDeclarationTransformation extends Transformation { - - @Inject private ClassifierAssistant classifierAssistant; - @Inject private ReflectionAssistant reflectionAssistant; - @Inject private DelegationAssistant delegationAssistant; - @Inject private TypeAssistant typeAssistant; - @Inject private ResourceNameComputer resourceNameComputer; - - - override assertPreConditions() { - typeAssistant.assertClassifierPreConditions(); - } - - override assertPostConditions() { - // none - } - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, N4InterfaceDeclaration, false).forEach[transformInterfaceDecl]; - } - - def private void transformInterfaceDecl(N4InterfaceDeclaration ifcDecl) { - if (ifcDecl.typingStrategy === TypingStrategy.STRUCTURAL) { - // structural interfaces are shapes, i.e. do only exist at compile time - val ifcOrParent = ifcDecl.eContainer instanceof ExportDeclaration ? ifcDecl.eContainer : ifcDecl; - remove(ifcOrParent); - return; - } - val ifcSTE = findSymbolTableEntryForElement(ifcDecl, true); - - // add 'Symbol.hasInstance' function for supporting the 'instanceof' operator - ifcDecl.ownedMembersRaw += createHasInstanceMethod(ifcDecl, ifcSTE); - - reflectionAssistant.addN4TypeGetter(ifcDecl, ifcDecl); - - val belowIfcDecl = newArrayList; - belowIfcDecl += createStaticFieldInitializations(ifcDecl, ifcSTE); - insertAfter(ifcDecl.orContainingExportDeclaration, belowIfcDecl); - - ifcDecl.ownedMembersRaw.removeIf[static && it instanceof N4FieldDeclaration]; - delegationAssistant.replaceDelegatingMembersByOrdinaryMembers(ifcDecl); - - val ifcObjLit = createInterfaceObject(ifcDecl, ifcSTE); - val varDecl = _VariableDeclaration(ifcDecl.name, ifcObjLit); - state.tracer.copyTrace(ifcDecl, varDecl); - - replace(ifcDecl, varDecl); - } - - /** Creates an object literal for the object representing the interface of the given ifcDecl. */ - def private ObjectLiteral createInterfaceObject(N4InterfaceDeclaration ifcDecl, SymbolTableEntry ifcSTE) { - val props = newArrayList; - val extendedInterfaces = createDirectlyImplementedOrExtendedInterfacesArgument(ifcDecl); - if (!extendedInterfaces.elements.empty) { - val $extends = steFor_$extendsInterfaces.name; - props += _PropertyGetterDecl( - $extends, - _ReturnStmnt(extendedInterfaces) - ); - } - props += createInstanceFieldDefaultsProperty(ifcDecl, ifcSTE); - props += createInstanceMemberPropertiesExceptFields(ifcDecl, ifcSTE); - props += createStaticMemberPropertiesExceptFields(ifcDecl, ifcSTE); - - return _ObjLit(props); - } - - def private ArrayLiteral createDirectlyImplementedOrExtendedInterfacesArgument(N4ClassifierDeclaration typeDecl) { - val interfaces = typeAssistant.getSuperInterfacesSTEs(typeDecl); - - // the return value of this method is intended for default method patching; for this purpose, we have to - // filter out some of the directly implemented interfaces: - val directlyImplementedInterfacesFiltered = TranspilerUtils.filterNominalInterfaces(interfaces); - return _ArrLit( directlyImplementedInterfacesFiltered.map[ _IdentRef(it) ] ); - } - - def private PropertyNameValuePair[] createInstanceFieldDefaultsProperty(N4InterfaceDeclaration ifcDecl, SymbolTableEntry ifcSTE) { - // $fieldInits: { - // fieldName1: undefined, - // fieldName2: 42, - // fieldName3: () => , - // ... - // } - val instanceFields = ifcDecl.ownedFields - .filter[!static && name!==null] - .toList; - if (instanceFields.empty) { - return #[]; - } - return #[ - _PropertyNameValuePair( - steFor_$fieldInits.name, - _ObjLit( - instanceFields.map[field| - field.name -> if (field.hasNonTrivialInitExpression) { - if (canSkipFunctionWrapping(field.expression)) { - field.expression - } else { - _ArrowFunc(false, #[], field.expression.wrapInParenthesesIfNeeded) - } - } else { - undefinedRef() - } - ] - ) - ) - ]; - } - - def private PropertyNameValuePair[] createInstanceMemberPropertiesExceptFields(N4InterfaceDeclaration ifcDecl, SymbolTableEntry ifcSTE) { - // $defaultMembers: { - // get getter() { - // }, - // set setter(value) { - // }, - // method() { - // }, - // ... - // } - - val instanceMembersExceptFields = ifcDecl.ownedMembers - .filter[!static && !abstract && name!==null] - .filter[!(it instanceof N4FieldDeclaration)] - .toList; - if (instanceMembersExceptFields.empty) { - return #[]; - } - return #[ - _PropertyNameValuePair( - steFor_$defaultMembers.name, - _ObjLit( - instanceMembersExceptFields.map[convertMemberToProperty] - ) - ) - ]; - } - - def private PropertyAssignment[] createStaticMemberPropertiesExceptFields(N4InterfaceDeclaration ifcDecl, SymbolTableEntry ifcSTE) { - // get staticGetter() { - // }, - // set staticSetter(value) { - // }, - // staticMethod() { - // }, - // ... - - val staticMembersExceptFields = ifcDecl.ownedMembers - .filter[static && !abstract && name!==null] - .filter[!(it instanceof N4FieldDeclaration)] - .toList; - if (staticMembersExceptFields.empty) { - return #[]; - } - return staticMembersExceptFields.map[convertMemberToProperty]; - } - - /** - * Converts getter, setter, and method declarations to the corresponding declarations in object literals. - * Does not support conversion of field declarations! - */ - def private PropertyAssignment convertMemberToProperty(N4MemberDeclaration memberDecl) { - // fields: - if (memberDecl instanceof N4FieldDeclaration) { - return _PropertyNameValuePair( - memberDecl.declaredName, // reuse existing name - memberDecl.expression ?: undefinedRef); // reuse existing initializer expression (if any) - } - // getters, setters, and methods: - val result = switch(memberDecl) { - GetterDeclaration: N4JSFactory.eINSTANCE.createPropertyGetterDeclaration - SetterDeclaration: N4JSFactory.eINSTANCE.createPropertySetterDeclaration => [ - it.fpar = memberDecl.fpar; // reuse existing fpar - ] - FunctionDefinition: N4JSFactory.eINSTANCE.createPropertyMethodDeclaration => [ - it.fpars += memberDecl.fpars; // reuse existing fpars - it.generator = memberDecl.generator; - it.declaredAsync = memberDecl.async; - ] - default: - throw new IllegalArgumentException("not a getter, setter, or method declaration") - }; - result.declaredName = (memberDecl as PropertyNameOwner).declaredName; // reuse existing name - result.body = (memberDecl as FunctionOrFieldAccessor).body; // reuse existing body - if (!memberDecl.annotations.isEmpty) { - result.annotationList = _PropertyAssignmentAnnotationList( memberDecl.annotations ) // reuse existing annotations - } - return result; - } - - def private Expression wrapInParenthesesIfNeeded(Expression expr) { - if (expr instanceof CommaExpression - || expr instanceof ObjectLiteral - || expr instanceof AwaitExpression - || expr instanceof YieldExpression - || expr instanceof PromisifyExpression) { - return _Parenthesis(expr); - } - return expr; - } - - def private boolean canSkipFunctionWrapping(Expression initExpression) { - return switch(initExpression) { - BooleanLiteral: true - NullLiteral: true - NumericLiteral: true - StringLiteral: true - UnaryExpression: { - // WARNING: some unary operators have a side effect, so they have to be wrapped in a function! - val isFreeOfSideEffect = switch(initExpression.op) { - case INC: false - case DEC: false - case DELETE: false - case POS: true - case NEG: true - case INV: true - case NOT: true - case TYPEOF: true - case VOID: true - } - return isFreeOfSideEffect - && canSkipFunctionWrapping(initExpression.expression); - } - AdditiveExpression: { - val isFreeOfSideEffect = switch(initExpression.op) { - case ADD: true - case SUB: true - } - return isFreeOfSideEffect - && canSkipFunctionWrapping(initExpression.lhs) - && canSkipFunctionWrapping(initExpression.rhs); - } - MultiplicativeExpression: { - val isFreeOfSideEffect = switch(initExpression.op) { - case TIMES: true - case DIV: true - case MOD: true - } - return isFreeOfSideEffect - && canSkipFunctionWrapping(initExpression.lhs) - && canSkipFunctionWrapping(initExpression.rhs); - } - BinaryBitwiseExpression: { - val isFreeOfSideEffect = switch(initExpression.op) { - case OR: true - case XOR: true - case AND: true - } - return isFreeOfSideEffect - && canSkipFunctionWrapping(initExpression.lhs) - && canSkipFunctionWrapping(initExpression.rhs); - } - BinaryLogicalExpression: { - val isFreeOfSideEffect = switch(initExpression.op) { - case OR: true - case AND: true - } - return isFreeOfSideEffect - && canSkipFunctionWrapping(initExpression.lhs) - && canSkipFunctionWrapping(initExpression.rhs); - } - CastExpression: canSkipFunctionWrapping(initExpression.expression) - ParenExpression: canSkipFunctionWrapping(initExpression.expression) - IdentifierRef: { - return initExpression.id === state.G.globalObjectScope.fieldUndefined; - } - default: false - }; - } - - def protected List createStaticFieldInitializations(N4InterfaceDeclaration ifcDecl, SymbolTableEntry ifcSTE) { - return classifierAssistant.createStaticFieldInitializations(ifcDecl, ifcSTE, Collections.emptySet); - } - - def private N4MethodDeclaration createHasInstanceMethod(N4InterfaceDeclaration ifcDecl, SymbolTableEntry ifcSTE) { - val symbolObjectType = state.G.symbolObjectType; - val symbolSTE = getSymbolTableEntryOriginal(symbolObjectType, true); - val hasInstanceSTE = getSymbolTableEntryForMember(symbolObjectType, "hasInstance", false, true, true); - val hasInstanceExpr = _PropertyAccessExpr(symbolSTE, hasInstanceSTE); - val declaredName = _LiteralOrComputedPropertyName(hasInstanceExpr, N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX + "hasInstance"); - - val ifcType = state.info.getOriginalDefinedType(ifcDecl); - val fqn = resourceNameComputer.getFullyQualifiedTypeName(ifcType); - - val result = _N4MethodDecl(true, declaredName, #[ _Fpar("instance") ], _Block( - _ReturnStmnt( - _Snippet("instance && instance.constructor && instance.constructor.n4type " - + "&& instance.constructor.n4type.allImplementedInterfaces " // required because we cannot be sure "instance.constructor.n4type" is of type N4Classifier - + "&& instance.constructor.n4type.allImplementedInterfaces.indexOf('" + fqn + "') !== -1" - ) - ) - )); - - state.info.markAsHiddenFromReflection(result); - - return result; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/JSXTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/JSXTransformation.java new file mode 100644 index 0000000000..7ef14e5728 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/JSXTransformation.java @@ -0,0 +1,314 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NULL; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ObjLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyNameValuePair; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._TRUE; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.JSXAbstractElement; +import org.eclipse.n4js.n4JS.JSXAttribute; +import org.eclipse.n4js.n4JS.JSXChild; +import org.eclipse.n4js.n4JS.JSXElement; +import org.eclipse.n4js.n4JS.JSXExpression; +import org.eclipse.n4js.n4JS.JSXFragment; +import org.eclipse.n4js.n4JS.JSXPropertyAttribute; +import org.eclipse.n4js.n4JS.JSXSpreadAttribute; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.tooling.react.ReactHelper; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.im.IdentifierRef_IM; +import org.eclipse.n4js.transpiler.im.Script_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.utils.ResourceType; +import org.eclipse.xtext.EcoreUtil2; + +import com.google.inject.Inject; + +/** + * Transforms JSX tags to output code according to JSX/React conventions. + *

+ * For example: + * + *

+ * <div attr="value"></div>
+ * 
+ * + * will be transformed to + * + *
+ * React.createElement('div', Object.assign({attr: "value"}));
+ * 
+ */ +public class JSXTransformation extends Transformation { + + private SymbolTableEntryOriginal steForJsxBackendNamespace; + private SymbolTableEntryOriginal steForJsxBackendElementFactoryFunction; + private SymbolTableEntryOriginal steForJsxBackendFragmentComponent; + + @Inject + private ReactHelper reactHelper; + + @Override + public void assertPreConditions() { + // empty + } + + @Override + public void assertPostConditions() { + // empty + } + + @Override + public void analyze() { + // ignore + } + + /** + * IMPORTANT: our strategy for handling nested JSXElements is as follows: 1) direct child JSXElements will be + * handled together with their parent JSXElement 2) indirect children that are contained in nested expressions will + * be handled via a separate invocation to method #transformJSXElement(JSXElement) + * + * Example for case 1: + * + *
+	 * let elem1 = <div><a></a></div>; // <a> is a direct child
+	 * 
+ * + * Example for case 2: + * + *
+	 * let elem2 = <div>{function() {return <a></a>;}}</div>; // <a> is the child of a nested expression!
+	 * 
+ * + * More examples for case 2: + * + *
+	 * let elem3 = <div>{<a></a>}</div>;
+	 * let elem4 = <div prop={<a></a>}></div>;
+	 * 
+ */ + @Override + public void transform() { + ResourceType resourceType = ResourceType.getResourceType(getState().resource); + boolean inJSX = resourceType == ResourceType.JSX || resourceType == ResourceType.N4JSX; + if (!inJSX) { + return; // this transformation is not applicable + } + + // Transform JSXFragments and JSXElements + List jsxAbstractElements = collectNodes(getState().im, JSXAbstractElement.class, true); + if (jsxAbstractElements.isEmpty()) { + // Nothing to transform + return; + } + + steForJsxBackendNamespace = prepareImportOfJsxBackend(); + steForJsxBackendElementFactoryFunction = prepareElementFactoryFunction(); + steForJsxBackendFragmentComponent = prepareFragmentComponent(); + + // note: we are passing 'true' to #collectNodes(), i.e. we are searching for nested elements + for (JSXAbstractElement jsxElem : jsxAbstractElements) { + transformJSXAbstractElement(jsxElem); + } + } + + private SymbolTableEntryOriginal prepareImportOfJsxBackend() { + TModule jsxBackendModule = reactHelper.getJsxBackendModule(getState().resource); + if (jsxBackendModule == null) { + throw new RuntimeException("cannot locate JSX backend for N4JSX resource " + getState().resource.getURI()); + } + NamespaceImportSpecifier existingNamespaceImportOfReactIM = null; + for (ImportDeclaration id : filter(getState().im.getScriptElements(), ImportDeclaration.class)) { + if (getState().info.getImportedModule(id) == jsxBackendModule) { + List niss = toList( + filter(id.getImportSpecifiers(), NamespaceImportSpecifier.class)); + if (!niss.isEmpty()) { + existingNamespaceImportOfReactIM = niss.get(0); + break; + } + } + } + + if (existingNamespaceImportOfReactIM != null) { + // we already have a namespace import of the JSX backend, no need to create a new one: + existingNamespaceImportOfReactIM.setFlaggedUsedInCode(true); + return findSymbolTableEntryForNamespaceImport(existingNamespaceImportOfReactIM); + } + // create namespace import for the JSX backend + // (note: we do not have to care for name clashes regarding name of the namespace, because validations ensure + // that "React" is never used as a name in N4JSX files, except as the namespace name of a react import) + return addNamespaceImport(jsxBackendModule, reactHelper.getJsxBackendNamespaceName()); + } + + private SymbolTableEntryOriginal prepareElementFactoryFunction() { + TFunction elementFactoryFunction = reactHelper.getJsxBackendElementFactoryFunction(getState().resource); + if (elementFactoryFunction == null) { + throw new RuntimeException("cannot locate element factory function of JSX backend for N4JSX resource " + + getState().resource.getURI()); + } + return getSymbolTableEntryOriginal(elementFactoryFunction, true); + } + + private SymbolTableEntryOriginal prepareFragmentComponent() { + IdentifiableElement fragmentComponent = reactHelper.getJsxBackendFragmentComponent(getState().resource); + if (fragmentComponent == null) { + throw new RuntimeException("cannot locate fragment component of JSX backend for N4JSX resource " + + getState().resource.getURI()); + } + return getSymbolTableEntryOriginal(fragmentComponent, true); + } + + private void transformJSXAbstractElement(JSXAbstractElement elem) { + // IMPORTANT: 'elem' might be a direct or indirect child, but if it is a direct child, it was already + // transformed when this method was invoked with its ancestor JSXElement as argument + if (EcoreUtil2.getContainerOfType(elem, Script_IM.class) == null) { + // 'elem' was already processed -> simply ignore it + return; + } + replace(elem, convertJSXAbstractElement(elem)); + } + + private ParameterizedCallExpression convertJSXAbstractElement(JSXAbstractElement elem) { + List args = new ArrayList<>(); + if (elem instanceof JSXElement) { + JSXElement jsxElem = (JSXElement) elem; + args.add(getTagNameFromElement(jsxElem)); + args.add(convertJSXAttributes(jsxElem.getJsxAttributes())); + } else { + args.add(_PropertyAccessExpr(steForJsxBackendNamespace, steForJsxBackendFragmentComponent)); + args.add(_NULL()); + } + args.addAll(toList(map(elem.getJsxChildren(), child -> convertJSXChild(child)))); + + return _CallExpr( + _PropertyAccessExpr(steForJsxBackendNamespace, steForJsxBackendElementFactoryFunction), + args.toArray(new Expression[0])); + } + + private Expression convertJSXChild(JSXChild child) { + if (child instanceof JSXElement) { + return convertJSXAbstractElement((JSXElement) child); + } + if (child instanceof JSXFragment) { + return convertJSXAbstractElement((JSXFragment) child); + } + if (child instanceof JSXExpression) { + return ((JSXExpression) child).getExpression(); + } + return null; + } + + // Generate Object.assign({}, {foo, bar: "Hi"}, spr) + private Expression convertJSXAttributes(List attrs) { + if (attrs.isEmpty()) { + return _NULL(); + } else if (attrs.size() == 1 && attrs.get(0) instanceof JSXSpreadAttribute) { + // Special case: if only a single spread operator is passed, we pass it directly, e.g. spr instead of + // cloning with Object.assign. + return ((JSXSpreadAttribute) attrs.get(0)).getExpression(); + } else { + + List spreadIndices = new ArrayList<>(); + for (int idx = 0; idx < attrs.size(); idx++) { + if (attrs.get(idx) instanceof JSXSpreadAttribute) { + spreadIndices.add(idx); + } + } + // GHOLD-413: We have to make sure that the only properties locating next to each other are combined. + // Moreover, the order of properties as well as spread operators must be preserved! + List props = new ArrayList<>(); + if (attrs.get(0) instanceof JSXSpreadAttribute) { + // The first attribute is a spread object, the target must be {}. + } else { + // Otherwise, the target is of the form {foo: true, bar: "Hi"} + int firstSpreadIndex = (!spreadIndices.isEmpty()) ? spreadIndices.get(0) : attrs.size(); + for (int i = 0; i < firstSpreadIndex; i++) { + props.add(convertJSXAttribute((JSXPropertyAttribute) attrs.get(i))); + } + } + ObjectLiteral target = _ObjLit(props.toArray(new PropertyNameValuePair[0])); + + List parameters = new ArrayList<>(); + parameters.add(target); + + for (int i = 0; i < spreadIndices.size(); i++) { + int curSpreadIdx = spreadIndices.get(i); + // Spread expression passed is used directly + parameters.add(((JSXSpreadAttribute) attrs.get(curSpreadIdx)).getExpression()); + // Combine properties between spread intervals + int nextSpreadIdx = (i < spreadIndices.size() - 1) ? spreadIndices.get(i + 1) + : attrs.size(); + List propsBetweenTwoSpreads = attrs.subList(curSpreadIdx + 1, nextSpreadIdx); + if (!propsBetweenTwoSpreads.isEmpty()) { + List props2 = new ArrayList<>(); + for (JSXAttribute attr : propsBetweenTwoSpreads) { + props2.add(convertJSXAttribute((JSXPropertyAttribute) attr)); + } + parameters.add(_ObjLit(props2.toArray(new PropertyAssignment[0]))); + } + } + + return _CallExpr(_PropertyAccessExpr(steFor_Object(), steFor_Object_assign()), + parameters.toArray(new Expression[0])); + } + } + + private PropertyNameValuePair convertJSXAttribute(JSXPropertyAttribute attr) { + return _PropertyNameValuePair( + getNameFromPropertyAttribute(attr), + getValueExpressionFromPropertyAttribute(attr)); + } + + private Expression getTagNameFromElement(JSXElement elem) { + Expression nameExpr = elem.getJsxElementName().getExpression(); + if (nameExpr instanceof IdentifierRef_IM) { + IdentifierRef_IM idRef = (IdentifierRef_IM) nameExpr; + SymbolTableEntry id = idRef.getId_IM(); + if (id == null) { + return _StringLiteral(idRef.getIdAsText()); + } + } + return nameExpr; + } + + private String getNameFromPropertyAttribute(JSXPropertyAttribute attr) { + IdentifiableElement prop = attr.getProperty(); + if (prop != null && !prop.eIsProxy()) { + return prop.getName(); + } + return attr.getPropertyAsText(); + } + + private Expression getValueExpressionFromPropertyAttribute(JSXPropertyAttribute attr) { + return attr.getJsxAttributeValue() != null ? attr.getJsxAttributeValue() : _TRUE(); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/JSXTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/JSXTransformation.xtend deleted file mode 100644 index b5b758832f..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/JSXTransformation.xtend +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import java.util.ArrayList -import java.util.List -import java.util.stream.IntStream -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.JSXAbstractElement -import org.eclipse.n4js.n4JS.JSXAttribute -import org.eclipse.n4js.n4JS.JSXChild -import org.eclipse.n4js.n4JS.JSXElement -import org.eclipse.n4js.n4JS.JSXExpression -import org.eclipse.n4js.n4JS.JSXFragment -import org.eclipse.n4js.n4JS.JSXPropertyAttribute -import org.eclipse.n4js.n4JS.JSXSpreadAttribute -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.tooling.react.ReactHelper -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.im.IdentifierRef_IM -import org.eclipse.n4js.transpiler.im.Script_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.utils.ResourceType -import org.eclipse.xtext.EcoreUtil2 - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Transforms JSX tags to output code according to JSX/React conventions. - *

- * For example: - *

- * <div attr="value"></div>
- * 
- * will be transformed to - *
- * React.createElement('div', Object.assign({attr: "value"}));
- * 
- */ -class JSXTransformation extends Transformation { - - private SymbolTableEntryOriginal steForJsxBackendNamespace; - private SymbolTableEntryOriginal steForJsxBackendElementFactoryFunction; - private SymbolTableEntryOriginal steForJsxBackendFragmentComponent; - - @Inject - private ReactHelper reactHelper; - - override assertPreConditions() { - } - - override assertPostConditions() { - } - - override analyze() { - // ignore - } - - /** - * IMPORTANT: our strategy for handling nested JSXElements is as follows: - * 1) direct child JSXElements will be handled together with their parent JSXElement - * 2) indirect children that are contained in nested expressions will be handled via a separate invocation to - * method #transformJSXElement(JSXElement) - * - * Example for case 1: - *
-	 * let elem1 = <div><a></a></div>; // <a> is a direct child
-	 * 
- * Example for case 2: - *
-	 * let elem2 = <div>{function() {return <a></a>;}}</div>; // <a> is the child of a nested expression!
-	 * 
- * More examples for case 2: - *
-	 * let elem3 = <div>{<a></a>}</div>;
-	 * let elem4 = <div prop={<a></a>}></div>;
-	 * 
- */ - override void transform() { - val resourceType = ResourceType.getResourceType(state.resource); - val inJSX = resourceType === ResourceType.JSX || resourceType === ResourceType.N4JSX; - if(!inJSX) { - return; // this transformation is not applicable - } - - // Transform JSXFragments and JSXElements - val jsxAbstractElements = collectNodes(state.im, JSXAbstractElement, true); - if (jsxAbstractElements.isEmpty) { - // Nothing to transform - return; - } - - steForJsxBackendNamespace = prepareImportOfJsxBackend(); - steForJsxBackendElementFactoryFunction = prepareElementFactoryFunction(); - steForJsxBackendFragmentComponent = prepareFragmentComponent(); - - // note: we are passing 'true' to #collectNodes(), i.e. we are searching for nested elements - jsxAbstractElements.forEach[transformJSXAbstractElement]; - } - - def private SymbolTableEntryOriginal prepareImportOfJsxBackend() { - val jsxBackendModule = reactHelper.getJsxBackendModule(state.resource); - if(jsxBackendModule===null) { - throw new RuntimeException("cannot locate JSX backend for N4JSX resource " + state.resource.URI); - } - val existingNamespaceImportOfReactIM = state.im.scriptElements.filter(ImportDeclaration) - .filter[impDeclIM | state.info.getImportedModule(impDeclIM)===jsxBackendModule] - .map[importSpecifiers].flatten - .filter(NamespaceImportSpecifier) - .head; - if(existingNamespaceImportOfReactIM!==null) { - // we already have a namespace import of the JSX backend, no need to create a new one: - existingNamespaceImportOfReactIM.flaggedUsedInCode = true; - return findSymbolTableEntryForNamespaceImport(existingNamespaceImportOfReactIM); - } - // create namespace import for the JSX backend - // (note: we do not have to care for name clashes regarding name of the namespace, because validations ensure - // that "React" is never used as a name in N4JSX files, except as the namespace name of a react import) - return addNamespaceImport(jsxBackendModule, reactHelper.getJsxBackendNamespaceName()); - } - - def private SymbolTableEntryOriginal prepareElementFactoryFunction() { - val elementFactoryFunction = reactHelper.getJsxBackendElementFactoryFunction(state.resource); - if(elementFactoryFunction===null) { - throw new RuntimeException("cannot locate element factory function of JSX backend for N4JSX resource " + state.resource.URI); - } - return getSymbolTableEntryOriginal(elementFactoryFunction, true); - } - - def private SymbolTableEntryOriginal prepareFragmentComponent() { - val fragmentComponent = reactHelper.getJsxBackendFragmentComponent(state.resource); - if(fragmentComponent===null) { - throw new RuntimeException("cannot locate fragment component of JSX backend for N4JSX resource " + state.resource.URI); - } - return getSymbolTableEntryOriginal(fragmentComponent, true); - } - - def private void transformJSXAbstractElement(JSXAbstractElement elem) { - // IMPORTANT: 'elem' might be a direct or indirect child, but if it is a direct child, it was already - // transformed when this method was invoked with its ancestor JSXElement as argument - if(EcoreUtil2.getContainerOfType(elem, Script_IM)===null) { - // 'elem' was already processed -> simply ignore it - return; - } - replace(elem, convertJSXAbstractElement(elem)); - } - - def private ParameterizedCallExpression convertJSXAbstractElement(JSXAbstractElement elem) { - val firstParams = if (elem instanceof JSXElement) { - #[ - elem.tagNameFromElement, - convertJSXAttributes(elem.jsxAttributes) - ] - } else { - #[ - _PropertyAccessExpr(steForJsxBackendNamespace, steForJsxBackendFragmentComponent), - _NULL - ] - }; - return _CallExpr( - _PropertyAccessExpr(steForJsxBackendNamespace, steForJsxBackendElementFactoryFunction), - ( - firstParams - + elem.jsxChildren.map[convertJSXChild] - ) - ); - } - - def private Expression convertJSXChild(JSXChild child) { - switch(child) { - JSXElement: - convertJSXAbstractElement(child) - JSXFragment: - convertJSXAbstractElement(child) - JSXExpression: - child.expression - } - } - - // Generate Object.assign({}, {foo, bar: "Hi"}, spr) - def private Expression convertJSXAttributes(List attrs) { - if(attrs.isEmpty) { - return _NULL; - } else if (attrs.size == 1 && attrs.get(0) instanceof JSXSpreadAttribute) { - // Special case: if only a single spread operator is passed, we pass it directly, e.g. spr instead of cloning with Object.assign. - return (attrs.get(0) as JSXSpreadAttribute).expression; - } else { - val spreadIndices = IntStream.range(0, attrs.size) - .filter[i | attrs.get(i) instanceof JSXSpreadAttribute].toArray; - // GHOLD-413: We have to make sure that the only properties locating next to each other are combined. - // Moreover, the order of properties as well as spread operators must be preserved! - val target = if (attrs.get(0) instanceof JSXSpreadAttribute) { - // The first attribute is a spread object, the target must be {}. - _ObjLit - } else { - // Otherwise, the target is of the form {foo: true, bar: "Hi"} - val firstSpreadIndex = if (!spreadIndices.empty) { - spreadIndices.get(0) - } else { - attrs.size - } - var firstProps = attrs.subList(0, firstSpreadIndex).map[it as JSXPropertyAttribute]; - _ObjLit(firstProps.map[convertJSXAttribute]) - } - - var parameters = new ArrayList(); - parameters.add(target); - - for (var i = 0; i < spreadIndices.length; i++) { - val curSpreadIdx = spreadIndices.get(i) - // Spread expression passed is used directly - parameters.add((attrs.get(curSpreadIdx) as JSXSpreadAttribute).expression); - // Combine properties between spread intervals - val nextSpreadIdx = if (i < spreadIndices.length-1) { - spreadIndices.get(i + 1); - } else { - attrs.length - } - val propsBetweenTwoSpreads = attrs.subList(curSpreadIdx + 1, nextSpreadIdx) - if (!propsBetweenTwoSpreads.empty) { - parameters.add(_ObjLit(propsBetweenTwoSpreads.map[(it as JSXPropertyAttribute).convertJSXAttribute])); - } - } - - return _CallExpr(_PropertyAccessExpr(steFor_Object, steFor_Object_assign), parameters); - } - } - - def private PropertyNameValuePair convertJSXAttribute(JSXPropertyAttribute attr) { - _PropertyNameValuePair( - attr.nameFromPropertyAttribute, - attr.valueExpressionFromPropertyAttribute) - } - - def private Expression getTagNameFromElement(JSXElement elem) { - val nameExpr = elem.jsxElementName.expression; - if(nameExpr instanceof IdentifierRef_IM) { - val id = nameExpr.id_IM; - if(id===null) { - return _StringLiteral(nameExpr.idAsText); - } - } - return nameExpr; - } - - def private String getNameFromPropertyAttribute(JSXPropertyAttribute attr) { - val prop = attr.property; - if(prop!==null && !prop.eIsProxy) { - return prop.name; - } - return attr.propertyAsText; - } - def private Expression getValueExpressionFromPropertyAttribute(JSXPropertyAttribute attr) { - return attr.jsxAttributeValue ?: _TRUE; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/MemberPatchingTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/MemberPatchingTransformation.java new file mode 100644 index 0000000000..bf5bc141b0 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/MemberPatchingTransformation.java @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._N4MemberDecl; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.List; + +import org.apache.log4j.Logger; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant; +import org.eclipse.n4js.transpiler.im.DelegatingMember; +import org.eclipse.n4js.transpiler.utils.ConcreteMembersOrderedForTranspiler; +import org.eclipse.n4js.ts.types.ContainerType; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.ts.types.TypingStrategy; +import org.eclipse.n4js.ts.types.util.AccessorTuple; + +import com.google.inject.Inject; + +/** + * Handles some special cases where code has to be emitted for non-owned members, e.g. for members consumed by an + * interface, EXCEPT for {@link StaticPolyfillTransformation static polyfills} and + * {@link ApiImplStubGenerationTransformation API/Impl stubs} (those cases are handled in other transformations). + *

+ * This transformation will add new "full" members or {@link DelegatingMember}s to the ownedMembersRaw + * property of {@link ContainerType}s in the intermediate model. + */ +public class MemberPatchingTransformation extends Transformation { + private final static Logger LOGGER = Logger.getLogger(MemberPatchingTransformation.class); + + @Inject + private DelegationAssistant delegationAssistant; + @Inject + private TypeAssistant typeAssistant; + + @Override + public void assertPreConditions() { + typeAssistant.assertClassifierPreConditions(); + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + for (N4ClassifierDeclaration cd : collectNodes(getState().im, N4ClassifierDeclaration.class, false)) { + if (cd instanceof N4InterfaceDeclaration) { + transformClassifierDecl((N4InterfaceDeclaration) cd); + + } else if (cd instanceof N4ClassDeclaration) { + transformClassifierDecl((N4ClassDeclaration) cd); + } + } + } + + private void transformClassifierDecl(N4InterfaceDeclaration ifcDecl) { + TInterface tIfc = getState().info.getOriginalDefinedType(ifcDecl); + ConcreteMembersOrderedForTranspiler cmoft = typeAssistant.getOrCreateCMOFT(tIfc); + + // for interfaces we ALWAYS add delegates to ALL inherited members (except fields) + for (TMember m : cmoft.ownedAndMixedInConcreteMembers) { + if (!(m instanceof TField)) { + boolean isInherited = m.getContainingType() != tIfc; + if (isInherited) { + DelegatingMember delegator = delegationAssistant.createDelegatingMember(tIfc, m); + getState().info.markAsConsumedFromInterface(delegator); + ifcDecl.getOwnedMembersRaw().add(delegator); + } + } else { + // note: it is slightly inconsistent that we ignore fields here (also compare with method for classes); + // but not required, because this is handled by the field initializer function created here: + // InterfaceDeclarationTransformation#createInstanceFieldInitializationFunction(N4InterfaceDeclaration, + // SymbolTableEntry) + } + } + + for (TField field : cmoft.fieldsPurelyMixedInNotOverridingAccessor) { + if (cmoft.inlinedMembersFromShapes.contains(field)) { + N4MemberDeclaration member = _N4MemberDecl(field); + ifcDecl.getOwnedMembersRaw().add(member); + getState().info.setOriginalDefinedMember(member, field); + } + } + } + + private void transformClassifierDecl(N4ClassDeclaration classDecl) { + TClass tClass = getState().info.getOriginalDefinedType(classDecl); + ConcreteMembersOrderedForTranspiler cmoft = typeAssistant.getOrCreateCMOFT(tClass); + + // add delegates to methods consumed from a nominal interface + List consumedMethods = toList( + filter(filter(cmoft.ownedAndMixedInConcreteMembers, TMethod.class), m -> m.eContainer() != tClass)); + for (TMethod m : consumedMethods) { + DelegatingMember member = delegationAssistant.createDelegatingMember(tClass, m); + if (!isInStructuralInterface(m)) { + getState().info.markAsConsumedFromInterface(member); + } + classDecl.getOwnedMembersRaw().add(member); + } + + // add delegates to getters/setters consumed from a nominal interface + for (AccessorTuple accTuple : cmoft.concreteAccessorTuples) { + if (accTuple.getGetter() != null && accTuple.getGetter().getContainingType() != tClass + && accTuple.getInheritedGetter() == null) { + TGetter g = accTuple.getGetter(); + DelegatingMember member = delegationAssistant.createDelegatingMember(tClass, g); + if (!isInStructuralInterface(accTuple.getGetter())) { + getState().info.markAsConsumedFromInterface(member); + } + classDecl.getOwnedMembersRaw().add(member); + } + if (accTuple.getSetter() != null && accTuple.getSetter().getContainingType() != tClass + && accTuple.getInheritedSetter() == null) { + TSetter s = accTuple.getSetter(); + DelegatingMember member = delegationAssistant.createDelegatingMember(tClass, s); + if (!isInStructuralInterface(accTuple.getSetter())) { + getState().info.markAsConsumedFromInterface(member); + } + classDecl.getOwnedMembersRaw().add(member); + } + } + + // add fields consumed from a nominal interface + for (TField field : cmoft.fieldsPurelyMixedInNotOverridingAccessor) { + N4MemberDeclaration member = _N4MemberDecl(field); + classDecl.getOwnedMembersRaw().add(member); + getState().info.setOriginalDefinedMember(member, field); + if (!cmoft.inlinedMembersFromShapes.contains(field)) { + getState().info.markAsConsumedFromInterface(member); + } + } + + // add delegates to inherited fields/getters/setters shadowed by an owned setter XOR getter + // NOTE: Partial shadowing in general is disallowed by validation. However, in incomplete + // API-impl situation we still support this feature here to propagate generated stubs for + // test reporting-purposes. + // MOVED: the actual implementation moved to the {@link + // org.eclipse.n4js.transpiler.es.transform.ApiImplStubGenerationTransformation} class + // the following code will issue errors if such a 'forbidden' case is still encountered: + for (AccessorTuple accTuple : cmoft.concreteAccessorTuples) { + if (accTuple.getInheritedGetter() != null && accTuple.getGetter() == null && accTuple.getSetter() != null) { + // an owned setter is shadowing an inherited getter -> delegate to the inherited getter + LOGGER.error("Encountered an invalid getter shadowing. Setter " + accTuple.getSetter().getName() + + " of classifier " + accTuple.getSetter().getContainingType() + "", + new IllegalStateException( + "Invalid shadowing of inherited getter. Getter should be implemented explicitly.")); + } + if (accTuple.getInheritedSetter() != null && accTuple.getGetter() != null && accTuple.getSetter() == null) { + // an owned getter is shadowing an inherited setter -> delegate to the inherited setter + LOGGER.error( + "Encountered an invalid inherited setter shadowing. Getter " + accTuple.getGetter().getName() + + " of classifier " + accTuple.getGetter().getContainingType() + "", + new IllegalStateException( + "Invalid shadowing of inherited setter. Setter should be implemented explicitly.")); + } + } + } + + // Note: Structural interfaces do not appear in the output code. Hence they must be consumed by the + // classes/interfaces that implement them. + private boolean isInStructuralInterface(TMember member) { + return member.eContainer() instanceof TInterface + && ((TInterface) member.eContainer()).getTypingStrategy() == TypingStrategy.STRUCTURAL; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/MemberPatchingTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/MemberPatchingTransformation.xtend deleted file mode 100644 index a410918932..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/MemberPatchingTransformation.xtend +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.assistants.TypeAssistant -import org.eclipse.n4js.transpiler.es.assistants.DelegationAssistant -import org.eclipse.n4js.transpiler.im.DelegatingMember -import org.eclipse.n4js.ts.types.ContainerType -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TypingStrategy -import org.eclipse.n4js.utils.Log - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Handles some special cases where code has to be emitted for non-owned members, e.g. for members consumed by an - * interface, EXCEPT for {@link StaticPolyfillTransformation static polyfills} and - * {@link ApiImplStubGenerationTransformation API/Impl stubs} (those cases are handled in other transformations). - *

- * This transformation will add new "full" members or {@link DelegatingMember}s to the ownedMembersRaw - * property of {@link ContainerType}s in the intermediate model. - */ -@Log -class MemberPatchingTransformation extends Transformation { - @Inject private DelegationAssistant delegationAssistant; - @Inject private TypeAssistant typeAssistant; - - - override assertPreConditions() { - typeAssistant.assertClassifierPreConditions(); - } - override assertPostConditions() { - // true - } - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, N4ClassifierDeclaration, false).forEach[transformClassifierDecl]; - } - - def private dispatch void transformClassifierDecl(N4InterfaceDeclaration ifcDecl) { - val tIfc = state.info.getOriginalDefinedType(ifcDecl); - val cmoft = typeAssistant.getOrCreateCMOFT(tIfc); - - // for interfaces we ALWAYS add delegates to ALL inherited members (except fields) - for(TMember m : cmoft.ownedAndMixedInConcreteMembers) { - if(!(m instanceof TField)) { - val isInherited = m.containingType!==tIfc; - if(isInherited){ - val delegator = delegationAssistant.createDelegatingMember(tIfc, m); - state.info.markAsConsumedFromInterface(delegator); - ifcDecl.ownedMembersRaw += delegator; - } - } else { - // note: it is slightly inconsistent that we ignore fields here (also compare with method for classes); - // but not required, because this is handled by the field initializer function created here: - // InterfaceDeclarationTransformation#createInstanceFieldInitializationFunction(N4InterfaceDeclaration, SymbolTableEntry) - } - } - - for(field : cmoft.fieldsPurelyMixedInNotOverridingAccessor) { - if (cmoft.inlinedMembersFromShapes.contains(field)) { - val member = _N4MemberDecl(field); - ifcDecl.ownedMembersRaw += member; - state.info.setOriginalDefinedMember(member, field); - } - } - } - - def private dispatch void transformClassifierDecl(N4ClassDeclaration classDecl) { - val tClass = state.info.getOriginalDefinedType(classDecl); - val cmoft = typeAssistant.getOrCreateCMOFT(tClass); - - // add delegates to methods consumed from a nominal interface - val consumedMethods = cmoft.ownedAndMixedInConcreteMembers.filter(TMethod).filter[m|m.eContainer!==tClass].toList; - for(m : consumedMethods) { - val member = delegationAssistant.createDelegatingMember(tClass, m); - if (!isInStructuralInterface(m)) { - state.info.markAsConsumedFromInterface(member); - } - classDecl.ownedMembersRaw += member; - } - - // add delegates to getters/setters consumed from a nominal interface - for(accTuple : cmoft.concreteAccessorTuples) { - if(accTuple.getter!==null && accTuple.getter.containingType!==tClass && accTuple.inheritedGetter===null) { - val g = accTuple.getter; - val member = delegationAssistant.createDelegatingMember(tClass, g); - if (!isInStructuralInterface(accTuple.getter)) { - state.info.markAsConsumedFromInterface(member); - } - classDecl.ownedMembersRaw += member; - } - if(accTuple.setter!==null && accTuple.setter.containingType!==tClass && accTuple.inheritedSetter===null) { - val s = accTuple.setter; - val member = delegationAssistant.createDelegatingMember(tClass, s); - if (!isInStructuralInterface(accTuple.setter)) { - state.info.markAsConsumedFromInterface(member); - } - classDecl.ownedMembersRaw += member; - } - } - - // add fields consumed from a nominal interface - for(field : cmoft.fieldsPurelyMixedInNotOverridingAccessor) { - val member = _N4MemberDecl(field); - classDecl.ownedMembersRaw += member; - state.info.setOriginalDefinedMember(member, field); - if (!cmoft.inlinedMembersFromShapes.contains(field)) { - state.info.markAsConsumedFromInterface(member); - } - } - - - // add delegates to inherited fields/getters/setters shadowed by an owned setter XOR getter - // NOTE: Partial shadowing in general is disallowed by validation. However, in incomplete - // API-impl situation we still support this feature here to propagate generated stubs for - // test reporting-purposes. - // MOVED: the actual implementation moved to the {@link org.eclipse.n4js.transpiler.es.transform.ApiImplStubGenerationTransformation} class - // the following code will issue errors if such a 'forbidden' case is still encountered: - for(accTuple : cmoft.concreteAccessorTuples) { - if(accTuple.inheritedGetter!==null && accTuple.getter===null && accTuple.setter!==null) { - // an owned setter is shadowing an inherited getter -> delegate to the inherited getter - logger.error("Encountered an invalid getter shadowing. Setter "+accTuple.setter.name+ - " of classifier "+accTuple.setter.containingType+"", new IllegalStateException("Invalid shadowing of inherited getter. Getter should be implemented explicitly.")) - } - if(accTuple.inheritedSetter!==null && accTuple.getter!==null && accTuple.setter===null) { - // an owned getter is shadowing an inherited setter -> delegate to the inherited setter - logger.error("Encountered an invalid inherited setter shadowing. Getter "+accTuple.getter.name+ - " of classifier "+accTuple.getter.containingType+"", new IllegalStateException("Invalid shadowing of inherited setter. Setter should be implemented explicitly.")) - } - } - } - - // Note: Structural interfaces do not appear in the output code. Hence they must be consumed by the classes/interfaces that implement them. - def private boolean isInStructuralInterface(TMember member) { - return member.eContainer instanceof TInterface - && (member.eContainer as TInterface).typingStrategy === TypingStrategy.STRUCTURAL - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.java new file mode 100644 index 0000000000..ca8ee2a596 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.java @@ -0,0 +1,243 @@ +/** + * Copyright (c) 2021 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ModuleSpecifierForm; +import org.eclipse.n4js.packagejson.projectDescription.ProjectType; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.utils.DeclMergingUtils; +import org.eclipse.n4js.utils.N4JSLanguageHelper; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.ResourceNameComputer; +import org.eclipse.n4js.utils.URIUtils; +import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot; +import org.eclipse.n4js.workspace.WorkspaceAccess; +import org.eclipse.n4js.workspace.utils.N4JSPackageName; + +import com.google.common.base.Strings; +import com.google.inject.Inject; + +/** + * Converts the module specifiers of import statements from N4JS to ES6. + *

+ * For details, see {@link #computeModuleSpecifierForOutputCode(ImportDeclaration)}. + */ +public class ModuleSpecifierTransformation extends Transformation { + + @Inject + private WorkspaceAccess workspaceAccess; + + @Inject + private ResourceNameComputer resourceNameComputer; + + @Inject + private N4JSLanguageHelper n4jsLanguageHelper; + + private String[] localModulePath = null; // will be set in #analyze() + private Map definedModuleSpecifierRewrites = null; // will be set in #analyze() + + @Override + public void assertPreConditions() { + // true + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + TModule localModule = getState().resource.getModule(); + String localModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(localModule); + String[] localModuleSpecifierSegments = localModuleSpecifier.split("/", -1); + localModulePath = Arrays.copyOf(localModuleSpecifierSegments, localModuleSpecifierSegments.length - 1); + definedModuleSpecifierRewrites = getState().project.getProjectDescription() + .getGeneratorRewriteModuleSpecifiers(); + } + + @Override + public void transform() { + // adjust module specifiers in imports + for (ImportDeclaration id : collectNodes(getState().im, ImportDeclaration.class, false)) { + transformImportDecl(id); + } + + } + + /** Returns file extension of output module */ + protected String getActualFileExtension(TModule targetModule) { + return n4jsLanguageHelper.getOutputFileExtension(getState().index, targetModule); + } + + private void transformImportDecl(ImportDeclaration importDeclIM) { + String definedRewrite = definedModuleSpecifierRewrites.get(importDeclIM.getModuleSpecifierAsText()); + if (definedRewrite != null) { + // special case: a rewrite for this module specifier was defined in the package.json + importDeclIM.setModuleSpecifierAsText(definedRewrite); + return; + } + + String moduleSpecifier = computeModuleSpecifierForOutputCode(importDeclIM); + String moduleSpecifierNormalized = moduleSpecifier.replace("/./", "/"); + importDeclIM.setModuleSpecifierAsText(moduleSpecifierNormalized); + } + + /** + * For the following reasons, we cannot simply reuse the module specifier from the N4JS source code in the generated + * output code: + *

    + *
  1. in N4JS, module specifiers are always absolute whereas in plain Javascript module specifiers must be relative + * (i.e. start with a segment '.' or '..') when importing from a module within the same npm package. N4JS does not + * even support relative module specifiers. + *
  2. in N4JS, the project name as the first segment of an absolute module specifier is optional (see + * {@link ModuleSpecifierForm#PLAIN} vs. {@link ModuleSpecifierForm#COMPLETE}); this is not supported by plain + * Javascript. + *
  3. in N4JS, module specifiers do not contain the path to the output folder, whereas in plain Javascript absolute + * module specifiers must always contain the full path from a project's root folder to the module. + *
  4. in N4JS, module specifiers do not include file extensions; in Javascript executed with node's native support + * for ES6 modules, file extensions are mandatory (note: this was not the case when using "esm" for handling ES6 + * modules). + *
+ * Importing from a runtime library is an exception to the above: in this case we must never include the runtime + * library's project name nor its path to the output folder in the module specifier. + */ + private String computeModuleSpecifierForOutputCode(ImportDeclaration importDeclIM) { + TModule targetModule = getState().info.getImportedModule(importDeclIM); + + if (URIUtils.isVirtualResourceURI(targetModule.eResource().getURI()) + && !DeclMergingUtils.isModuleAugmentation(targetModule)) { + // SPECIAL CASE #1a + // pointing to a module explicitly declared in a .d.ts file, such as a node built-in library: + // import * as path_lib from "path" + // --> always use plain module specifier + return targetModule.getModuleSpecifier(); // no file extension to add! + } + + N4JSProjectConfigSnapshot targetProject = workspaceAccess.findProjectContaining(targetModule); + if (targetProject.getType() == ProjectType.RUNTIME_LIBRARY) { + // SPECIAL CASE #1b + // pointing to a module in a runtime library, such as importing a node built-in library: + // import * as path_lib from "path" + // --> always use plain module specifier + return targetModule.getModuleSpecifier(); // no file extension to add! + } + + boolean importingFromModuleInSameProject = Objects.equals(targetProject.getPathAsFileURI(), getState().project + .getPathAsFileURI()); + if (importingFromModuleInSameProject) { + // SPECIAL CASE #2 + // module specifiers are always absolute in N4JS, but Javascript requires relative module + // specifiers when importing from a module within the same npm package + // --> need to create a relative module specifier here: + return createRelativeModuleSpecifier(targetModule); + } + + ModuleSpecifierForm moduleSpecifierForm = importDeclIM.getModuleSpecifierForm(); + if (moduleSpecifierForm == ModuleSpecifierForm.PROJECT + || moduleSpecifierForm == ModuleSpecifierForm.PROJECT_NO_MAIN) { + // SPECIAL CASE #3 + // in case of project imports (a.k.a. bare imports) we simply use + // the target project's name as module specifier: + return getActualProjectName(targetProject).getRawName(); // no file extension to add! + } else if (moduleSpecifierForm == ModuleSpecifierForm.PROJECT_EXPORTS) { + // SPECIAL CASE #4 + // in case of project exports imports (defined by package.json property 'exports') we simply use + // the original module specifier: + return importDeclIM.getModuleSpecifierAsText(); + } + + return createAbsoluteModuleSpecifier(targetProject, targetModule); + } + + private String createRelativeModuleSpecifier(TModule targetModule) { + String targetModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(targetModule); + String[] targetModuleSpecifierSegments = targetModuleSpecifier.split("/", -1); + String targetModuleName = targetModuleSpecifierSegments[targetModuleSpecifierSegments.length - 1]; + String[] targetModulePath = Arrays.copyOf(targetModuleSpecifierSegments, + targetModuleSpecifierSegments.length - 1); + int l = Math.min(targetModulePath.length, localModulePath.length); + int i = 0; + while (i < l && Objects.equals(targetModulePath[i], localModulePath[i])) { + i++; + } + String[] differingSegments = Arrays.copyOfRange(targetModulePath, i, targetModulePath.length); + List allSegm = new ArrayList<>(Arrays.asList(differingSegments)); + allSegm.add(targetModuleName); + int goUpCount = localModulePath.length - i; + String ext = getActualFileExtension(targetModule); + String result = ((goUpCount > 0) ? "../".repeat(goUpCount) : "./") + + org.eclipse.n4js.utils.Strings.join("/", allSegm) + + ((ext != null && !ext.isEmpty()) ? "." + ext : ""); + return result; + } + + private String createAbsoluteModuleSpecifier(N4JSProjectConfigSnapshot targetProject, TModule targetModule) { + if (N4JSLanguageUtils.isMainModule(targetProject, targetModule)) { + // 'targetModule' is the main module of 'targetProject', so we can use a project import: + return getActualProjectName(targetProject).toString(); + } + + StringBuilder sb = new StringBuilder(); + + // first segment is the project name + N4JSPackageName targetProjectName = getActualProjectName(targetProject); + if (targetProjectName != null) { + sb.append(targetProjectName); + } + + // followed by the path to the output folder + String outputPath = targetProject.getOutputPath(); + if (!Strings.isNullOrEmpty(outputPath)) { + if (!outputPath.startsWith("/")) { + sb.append("/"); + } + sb.append(outputPath); + if (!outputPath.endsWith("/")) { + sb.append("/"); + } + } else { + if (sb.length() > 0) { + sb.append("/"); + } + } + + // and finally the target module's FQN (i.e. the path-to-module) + String targetModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(targetModule); + sb.append(targetModuleSpecifier); + + String ext = getActualFileExtension(targetModule); + if (ext != null && !ext.isEmpty()) { + sb.append('.'); + sb.append(ext); + } + + return sb.toString(); + } + + private N4JSPackageName getActualProjectName(N4JSProjectConfigSnapshot project) { + if (project.getType() == ProjectType.DEFINITION) { + N4JSPackageName definedProjectName = project.getDefinesPackage(); + if (definedProjectName != null && !definedProjectName.isEmpty()) { + return definedProjectName; + } + } + return new N4JSPackageName(project.getPackageName()); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.xtend deleted file mode 100644 index 345282c496..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.xtend +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Copyright (c) 2021 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.common.base.Joiner -import com.google.inject.Inject -import java.util.Arrays -import java.util.Map -import java.util.Objects -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ModuleSpecifierForm -import org.eclipse.n4js.packagejson.projectDescription.ProjectType -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.utils.DeclMergingUtils -import org.eclipse.n4js.utils.N4JSLanguageHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.ResourceNameComputer -import org.eclipse.n4js.utils.URIUtils -import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot -import org.eclipse.n4js.workspace.WorkspaceAccess -import org.eclipse.n4js.workspace.utils.N4JSPackageName - -/** - * Converts the module specifiers of import statements from N4JS to ES6. - *

- * For details, see {@link #computeModuleSpecifierForOutputCode(ImportDeclaration)}. - */ -class ModuleSpecifierTransformation extends Transformation { - - @Inject - private WorkspaceAccess workspaceAccess; - - @Inject - private ResourceNameComputer resourceNameComputer; - - @Inject - private N4JSLanguageHelper n4jsLanguageHelper; - - private String[] localModulePath = null; // will be set in #analyze() - private Map definedModuleSpecifierRewrites = null; // will be set in #analyze() - - override assertPreConditions() { - // true - } - - override assertPostConditions() { - // true - } - - override analyze() { - val localModule = state.resource.module; - val localModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(localModule); - val localModuleSpecifierSegments = localModuleSpecifier.split("/", -1); - localModulePath = Arrays.copyOf(localModuleSpecifierSegments, localModuleSpecifierSegments.length - 1); - definedModuleSpecifierRewrites = state.project.projectDescription.generatorRewriteModuleSpecifiers; - } - - override transform() { - // adjust module specifiers in imports - collectNodes(state.im, ImportDeclaration, false).forEach[transformImportDecl]; - } - - def private void transformImportDecl(ImportDeclaration importDeclIM) { - val definedRewrite = definedModuleSpecifierRewrites.get(importDeclIM.moduleSpecifierAsText); - if (definedRewrite !== null) { - // special case: a rewrite for this module specifier was defined in the package.json - importDeclIM.moduleSpecifierAsText = definedRewrite; - return; - } - - val moduleSpecifier = computeModuleSpecifierForOutputCode(importDeclIM); - val moduleSpecifierNormalized = moduleSpecifier.replace("/./", "/"); - importDeclIM.moduleSpecifierAsText = moduleSpecifierNormalized; - } - - /** - * For the following reasons, we cannot simply reuse the module specifier from the N4JS source code - * in the generated output code: - *

    - *
  1. in N4JS, module specifiers are always absolute whereas in plain Javascript module specifiers - * must be relative (i.e. start with a segment '.' or '..') when importing from a module within the - * same npm package. N4JS does not even support relative module specifiers. - *
  2. in N4JS, the project name as the first segment of an absolute module specifier is optional - * (see {@link ModuleSpecifierForm#PLAIN} vs. {@link ModuleSpecifierForm#COMPLETE}); this is not - * supported by plain Javascript. - *
  3. in N4JS, module specifiers do not contain the path to the output folder, whereas in plain - * Javascript absolute module specifiers must always contain the full path from a project's root - * folder to the module. - *
  4. in N4JS, module specifiers do not include file extensions; in Javascript executed with node's - * native support for ES6 modules, file extensions are mandatory (note: this was not the case when - * using "esm" for handling ES6 modules). - *
- * Importing from a runtime library is an exception to the above: in this case we must never include - * the runtime library's project name nor its path to the output folder in the module specifier. - */ - def private String computeModuleSpecifierForOutputCode(ImportDeclaration importDeclIM) { - val targetModule = state.info.getImportedModule(importDeclIM); - - if (URIUtils.isVirtualResourceURI(targetModule.eResource.URI) && !DeclMergingUtils.isModuleAugmentation(targetModule)) { - // SPECIAL CASE #1a - // pointing to a module explicitly declared in a .d.ts file, such as a node built-in library: - // import * as path_lib from "path" - // --> always use plain module specifier - return targetModule.moduleSpecifier; // no file extension to add! - } - - val targetProject = workspaceAccess.findProjectContaining(targetModule); - if (targetProject.type === ProjectType.RUNTIME_LIBRARY) { - // SPECIAL CASE #1b - // pointing to a module in a runtime library, such as importing a node built-in library: - // import * as path_lib from "path" - // --> always use plain module specifier - return targetModule.moduleSpecifier; // no file extension to add! - } - - val importingFromModuleInSameProject = targetProject.pathAsFileURI == state.project.pathAsFileURI; - if (importingFromModuleInSameProject) { - // SPECIAL CASE #2 - // module specifiers are always absolute in N4JS, but Javascript requires relative module - // specifiers when importing from a module within the same npm package - // --> need to create a relative module specifier here: - return createRelativeModuleSpecifier(targetModule); - } - - val moduleSpecifierForm = importDeclIM.moduleSpecifierForm; - if (moduleSpecifierForm === ModuleSpecifierForm.PROJECT - || moduleSpecifierForm === ModuleSpecifierForm.PROJECT_NO_MAIN) { - // SPECIAL CASE #3 - // in case of project imports (a.k.a. bare imports) we simply use - // the target project's name as module specifier: - return getActualProjectName(targetProject).rawName; // no file extension to add! - } else if (moduleSpecifierForm === ModuleSpecifierForm.PROJECT_EXPORTS) { - // SPECIAL CASE #4 - // in case of project exports imports (defined by package.json property 'exports') we simply use - // the original module specifier: - return importDeclIM.moduleSpecifierAsText - } - - return createAbsoluteModuleSpecifier(targetProject, targetModule); - } - - def private String createRelativeModuleSpecifier(TModule targetModule) { - val targetModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(targetModule); - val targetModuleSpecifierSegments = targetModuleSpecifier.split("/", -1); - val targetModuleName = targetModuleSpecifierSegments.last(); - val targetModulePath = Arrays.copyOf(targetModuleSpecifierSegments, targetModuleSpecifierSegments.length - 1); - val l = Math.min(targetModulePath.length, localModulePath.length); - var i = 0; - while (i < l && Objects.equals(targetModulePath.get(i), localModulePath.get(i))) { - i++; - } - val differingSegments = Arrays.copyOfRange(targetModulePath, i, targetModulePath.length); - val goUpCount = localModulePath.length - i; - val ext = getActualFileExtension(targetModule); - val result = (if (goUpCount > 0) "../".repeat(goUpCount) else "./") - + Joiner.on("/").join(differingSegments + #[targetModuleName]) - + (if (ext !== null && !ext.empty) "." + ext else ""); - return result; - } - - def protected String createAbsoluteModuleSpecifier(N4JSProjectConfigSnapshot targetProject, TModule targetModule) { - if (N4JSLanguageUtils.isMainModule(targetProject, targetModule)) { - // 'targetModule' is the main module of 'targetProject', so we can use a project import: - return getActualProjectName(targetProject).toString(); - } - - val sb = new StringBuilder(); - - // first segment is the project name - val targetProjectName = getActualProjectName(targetProject); - if (targetProjectName !== null) { - sb.append(targetProjectName); - } - - // followed by the path to the output folder - var outputPath = targetProject.outputPath; - if (!outputPath.isNullOrEmpty) { - if (!outputPath.startsWith('/')) { - sb.append('/'); - } - sb.append(outputPath); - if (!outputPath.endsWith('/')) { - sb.append('/'); - } - } else { - if (sb.length > 0) { - sb.append('/'); - } - } - - // and finally the target module's FQN (i.e. the path-to-module) - val targetModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(targetModule); - sb.append(targetModuleSpecifier); - - val ext = getActualFileExtension(targetModule); - if (ext !== null && !ext.empty) { - sb.append('.'); - sb.append(ext); - } - - return sb.toString(); - } - - def protected String getActualFileExtension(TModule targetModule) { - return n4jsLanguageHelper.getOutputFileExtension(state.index, targetModule); - } - - def protected N4JSPackageName getActualProjectName(N4JSProjectConfigSnapshot project) { - if (project.type === ProjectType.DEFINITION) { - val definedProjectName = project.definesPackage; - if (definedProjectName !== null && !definedProjectName.isEmpty) { - return definedProjectName; - } - } - return new N4JSPackageName(project.packageName); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleWrappingTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleWrappingTransformation.java new file mode 100644 index 0000000000..34766b2042 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleWrappingTransformation.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2019 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.isEmpty; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.ExportableElement; +import org.eclipse.n4js.n4JS.ModifiableElement; +import org.eclipse.n4js.n4JS.VariableBinding; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; + +/** + * This transformation will prepare the output code for module loading. Since dropping support for commonjs and SystemJS + * and instead using ECMAScript 2015 imports/exports in the output code, this transformation is no longer doing much. + */ +public class ModuleWrappingTransformation extends Transformation { + + @Override + public void assertPreConditions() { + // true + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + // nothing + } + + @Override + public void transform() { + // strip modifiers off all exported elements + // (e.g. remove "public" from something like "export public let a = 'hello';") + + for (ModifiableElement me : filter(map(collectNodes(getState().im, ExportDeclaration.class, false), + ed -> ed.getExportedElement()), ModifiableElement.class)) { + + me.getDeclaredModifiers().clear(); + } + + // the following is only required because earlier transformations are producing + // invalid "export default var|let|const ..." + // TODO instead of the next line, change the earlier transformations to not produce these invalid constructs + for (ExportDeclaration ed : collectNodes(getState().im, ExportDeclaration.class, false)) { + splitDefaultExportFromVarDecl(ed); + } + + // add implicit import of "n4js-runtime" + addEmptyImport(N4JSGlobals.N4JS_RUNTIME.getRawName()); + } + + /** + * Turns + * + *
+	 * export default var|let|const C = ...
+	 * 
+ * + * into + * + *
+	 * var|let|const C = ...
+	 * export default C;
+	 * 
+ */ + private void splitDefaultExportFromVarDecl(ExportDeclaration exportDecl) { + if (exportDecl.isDefaultExport()) { + ExportableElement exportedElement = exportDecl.getExportedElement(); + if (exportedElement instanceof VariableStatement) { + VariableStatement variableStatement = (VariableStatement) exportedElement; + if (!isEmpty(filter(variableStatement.getVarDeclsOrBindings(), VariableBinding.class))) { + throw new UnsupportedOperationException("unsupported: default-exported variable binding"); + } + if (variableStatement.getVarDeclsOrBindings().size() > 1) { + throw new UnsupportedOperationException( + "unsupported: several default-exported variable declarations in a single export declaration"); + } + VariableDeclaration varDecl = (VariableDeclaration) variableStatement.getVarDeclsOrBindings().get(0); + SymbolTableEntry varDeclSTE = findSymbolTableEntryForElement(varDecl, true); + insertBefore(exportDecl, variableStatement); // will remove exportedElement from exportDecl + exportDecl.setDefaultExportedExpression(_IdentRef(varDeclSTE)); + } + } + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleWrappingTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleWrappingTransformation.xtend deleted file mode 100644 index 817db39397..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleWrappingTransformation.xtend +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2019 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.ModifiableElement -import org.eclipse.n4js.n4JS.VariableBinding -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.transpiler.Transformation - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * This transformation will prepare the output code for module loading. Since dropping support for commonjs and SystemJS - * and instead using ECMAScript 2015 imports/exports in the output code, this transformation is no longer doing much. - */ -class ModuleWrappingTransformation extends Transformation { - - override assertPreConditions() { - // true - } - - override assertPostConditions() { - // true - } - - override analyze() { - // nothing - } - - override transform() { - // strip modifiers off all exported elements - // (e.g. remove "public" from something like "export public let a = 'hello';") - collectNodes(state.im, ExportDeclaration, false).map[exportedElement].filter(ModifiableElement).forEach [ - it.declaredModifiers.clear - ]; - - // the following is only required because earlier transformations are producing - // invalid "export default var|let|const ..." - // TODO instead of the next line, change the earlier transformations to not produce these invalid constructs - collectNodes(state.im, ExportDeclaration, false).forEach[splitDefaultExportFromVarDecl]; - - // add implicit import of "n4js-runtime" - addEmptyImport(N4JSGlobals.N4JS_RUNTIME.rawName); - } - - /** - * Turns - *
-	 * export default var|let|const C = ...
-	 * 
- * into - *
-	 * var|let|const C = ...
-	 * export default C;
-	 * 
- */ - def private void splitDefaultExportFromVarDecl(ExportDeclaration exportDecl) { - if (exportDecl.isDefaultExport) { - val exportedElement = exportDecl.exportedElement; - if (exportedElement instanceof VariableStatement) { - if (!exportedElement.varDeclsOrBindings.filter(VariableBinding).isEmpty) { - throw new UnsupportedOperationException("unsupported: default-exported variable binding"); - } - if (exportedElement.varDeclsOrBindings.size > 1) { - throw new UnsupportedOperationException( - "unsupported: several default-exported variable declarations in a single export declaration"); - } - val varDecl = exportedElement.varDeclsOrBindings.head as VariableDeclaration; - val varDeclSTE = findSymbolTableEntryForElement(varDecl, true); - insertBefore(exportDecl, exportedElement); // will remove exportedElement from exportDecl - exportDecl.defaultExportedExpression = _IdentRef(varDeclSTE); - } - } - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/RestParameterTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/RestParameterTransformation.java new file mode 100644 index 0000000000..1845b87180 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/RestParameterTransformation.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Block; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IdentRef; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._IntLiteral; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableStatement; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.List; + +import org.eclipse.n4js.generator.GeneratorOption; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.transpiler.AbstractTranspiler; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.Optional; +import org.eclipse.n4js.transpiler.TransformationDependency.RequiresBefore; +import org.eclipse.xtext.EcoreUtil2; + +/** + * Transforms ES2015 rest parameters to an ES5 equivalent. + * + * Dependencies: + *
    + *
  • requiresBefore {@link ArrowFunction_Part1_Transformation}:
    + * the transpiled output code for varargs requires the implicit local arguments variable, which is not available in + * arrow functions; therefore, this transformation relies on arrow functions already having been transformed into + * ordinary function expressions. + *
  • requiresBefore {@link BlockTransformation}:
    + * the block transformation sometimes wraps the entire body into a newly created function (for async functions); the + * handling of varargs added below must NOT be wrapped in such an inner function; the easiest way to ensure this is to + * execute BlockTransformation before this transformation. + *
+ */ +@Optional(GeneratorOption.RestParameters) +@RequiresBefore({ ArrowFunction_Part1_Transformation.class, BlockTransformation.class }) +public class RestParameterTransformation extends Transformation { + + @Override + public void assertPreConditions() { + // + } + + @Override + public void assertPostConditions() { + if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { + // as a result of this transformation no rest parameter should be found in the entire model. + List allFormalParameter = EcoreUtil2.eAllOfType(getState().im, FormalParameter.class); + List stillVariadic = toList(filter(allFormalParameter, fp -> fp.isVariadic())); + assertTrue("no rest parameter should be in the model.", stillVariadic.size() == 0); + } + } + + @Override + public void analyze() { + // + } + + @Override + public void transform() { + for (FunctionDefinition fd : collectNodes(getState().im, FunctionDefinition.class, true)) { + transform(fd); + } + } + + private void transform(FunctionDefinition fdef) { + if (fdef.getFpars().isEmpty()) { + return; + } + + int lastFparIdx = fdef.getFpars().size() - 1; + FormalParameter lastFpar = fdef.getFpars().get(lastFparIdx); + + if (!lastFpar.isVariadic()) { + return; // nothing to be done + } + + // rewrite fpar-list. + VariableStatement stmt = _VariableStatement(); + VariableDeclaration varDecl = _VariableDeclaration(lastFpar.getName()); + ParameterizedCallExpression callExpr = _CallExpr( + // Array.prototype.slice.call(arguments, 2); + _PropertyAccessExpr(steFor_Array(), steFor_prototype(), steFor_Array_slice(), steFor_Function_call()), + _IdentRef(steFor_arguments()), + _IntLiteral(lastFparIdx)); + varDecl.setExpression(callExpr); + stmt.getVarDeclsOrBindings().add(varDecl); + + if (fdef.getBody() == null) { + fdef.setBody(_Block()); + } + replaceAndRelocate(lastFpar, stmt, stmt.getVarDecl().get(0), fdef.getBody()); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/RestParameterTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/RestParameterTransformation.xtend deleted file mode 100644 index b105997066..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/RestParameterTransformation.xtend +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.transpiler.AbstractTranspiler -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.Optional -import org.eclipse.n4js.transpiler.TransformationDependency.RequiresBefore -import org.eclipse.xtext.EcoreUtil2 - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Transforms ES2015 rest parameters to an ES5 equivalent. - * - * Dependencies: - *
    - *
  • requiresBefore {@link ArrowFunction_Part1_Transformation}:
    - * the transpiled output code for varargs requires the implicit local arguments variable, which is not available in - * arrow functions; therefore, this transformation relies on arrow functions already having been transformed into - * ordinary function expressions. - *
  • requiresBefore {@link BlockTransformation}:
    - * the block transformation sometimes wraps the entire body into a newly created function (for async functions); - * the handling of varargs added below must NOT be wrapped in such an inner function; the easiest way to ensure this - * is to execute BlockTransformation before this transformation. - *
- */ -@Optional(RestParameters) -@RequiresBefore(ArrowFunction_Part1_Transformation, BlockTransformation) -class RestParameterTransformation extends Transformation { - - - override assertPreConditions() { - // - } - - override assertPostConditions() { - if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { - // as a result of this transformation no rest parameter should be found in the entire model. - val allFormalParameter = EcoreUtil2.eAllOfType(state.im, FormalParameter); - val stillVariadic = allFormalParameter.filter[it.isVariadic].toList; - assertTrue("no rest parameter should be in the model.", stillVariadic.size === 0); - } - } - - - override analyze() { - // - } - - override transform() { - collectNodes(state.im, FunctionDefinition, true).forEach[it.transform]; - } - - def private void transform(FunctionDefinition fdef) { - if (fdef.fpars.isEmpty) { - return; - } - - val lastFparIdx = fdef.fpars.size - 1; - val lastFpar = fdef.fpars.get(lastFparIdx); - - if (!lastFpar.isVariadic) { - return; // nothing to be done - } - - // rewrite fpar-list. - val stmt = _VariableStatement() => [ - varDeclsOrBindings += _VariableDeclaration(lastFpar.name) => [ - it.expression = _CallExpr( - // Array.prototype.slice.call(arguments, 2); - _PropertyAccessExpr(steFor_Array, steFor_prototype, steFor_Array_slice, steFor_Function_call), - _IdentRef(steFor_arguments), - _IntLiteral(lastFparIdx) - ); - ]; - ]; - - if (fdef.body === null) { - fdef.body = _Block(); - } - replaceAndRelocate(lastFpar, stmt, stmt.varDecl.get(0), fdef.body) - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SanitizeImportsTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SanitizeImportsTransformation.java new file mode 100644 index 0000000000..eebfe197e2 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SanitizeImportsTransformation.java @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.map; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toSet; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver; +import org.eclipse.n4js.transpiler.AbstractTranspiler; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.im.IdentifierRef_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.utils.TranspilerUtils; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.Strings; +import org.eclipse.xtext.EcoreUtil2; + +/** + * Transformation to clean up imports: + *
    + *
  • add missing imports, + *
  • remove unused import statements. + *
+ */ +public class SanitizeImportsTransformation extends Transformation { + + @Override + public void analyze() { + // empty + } + + @Override + public void assertPreConditions() { + if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { + assertTrue("requires a fully resolved script", getState().im.isFlaggedUsageMarkingFinished()); + } + } + + @Override + public void assertPostConditions() { + if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { + // no import with flag used in code = false contained. + Iterable unusedImports = filter( + EcoreUtil2.getAllContentsOfType(getState().im, ImportSpecifier.class), is -> !isUsed(is)); + assertTrue("There should not be any unused import." + + " Unused=" + Strings.join(",", unusedImports), !unusedImports.iterator().hasNext()); + } + } + + @Override + public void transform() { + addMissingImplicitImports(); + removeUnusedImports(); + } + + /** + * Compute imports for globally defined elements in other resources on the projects dependencies and add them iff + * missing. + */ + private void addMissingImplicitImports() { + + // Current use-case is use of Variable in global-scope. + // The variable needs the defining module to be executed, hence a + + // collect all elements that are referenced from within the current module + Set referencedSTEs = toSet(filter(map(filter( + getState().im.eAllContents(), IdentifierRef_IM.class), idRef -> idRef.getId_IM()), + SymbolTableEntryOriginal.class)); + + // explanation for filters in previous lines: + // (1) reason why it is legal to filter out all ReferencingElement_IMs that aren't IdentifierRef_IM: + // * ParameterizedTypeRef_IM => type references won't show up in target code anyway -> no import required + // * ParameterizedPropertyAccessExpression_IM => here, the referenced element is a member -> members need not be + // imported + // (2) reason why it is legal to filter out all STEs that aren't SymbolTableEntryOriginal: + // * SymbolTableEntryInternal => low-level stuff, no imports required (at least not imports that work like N4JS + // imports) + // * SymbolTableEntryIMOnly => stuff programmatically created in IM -> need not be imported (defined locally) + + // filter out those that do not require an import (e.g. built-in, provided by runtime, etc.) + TModule baseModule = getState().resource.getModule(); + Iterable requiresImport = filter(referencedSTEs, + ste -> ScriptDependencyResolver.shouldBeImported(baseModule, ste.getOriginalTarget())); + + // look for already given imports: + Iterable thingsToImportSTE = requiresImport; + { + // The Question is, if the variable was already imported, then + // a) do we still see it here and b) if so, was the import flagged by usedInCode ? + + // It turns out, that current scoping as of Dec'2015 doesn't bind to imports to globally available + // definitions. + // Assuming a) no + b) irrelevant for a==no + for (SymbolTableEntryOriginal ste : thingsToImportSTE) { + if (ste.getImportSpecifier() == null) { + IdentifiableElement orig = ste.getOriginalTarget(); + // for an element to be globally available, there are two preconditions: + // (1) containing module must be annotated with @@Global (2) element must be directly exported + if (N4JSLanguageUtils.isDirectlyExported(orig)) { + TModule module = orig.getContainingModule(); + if (AnnotationDefinition.GLOBAL.hasAnnotation(module)) { + addNamedImport(ste, null); + } + } + } + } + } + } + + private void removeUnusedImports() { + List unusedImports = toList( + filter(EcoreUtil2.getAllContentsOfType(getState().im, ImportSpecifier.class), is -> !isUsed(is))); + + for (Iterator iter = unusedImports.iterator(); iter.hasNext();) { + ImportSpecifier is = iter.next(); + + ImportDeclaration container = (ImportDeclaration) is.eContainer(); + EObject toBeRemoved = (container.getImportSpecifiers().size() == 1 + && container.getImportSpecifiers().contains(is)) + ? + // remove container if import-specifier is last child. + container + : + // remove import-specifier + is; + + remove(toBeRemoved); + } + } + + private boolean isUsed(ImportSpecifier importSpec) { + if (importSpec.isFlaggedUsedInCode() == false) { + // was already unused in original N4JS code (and we expect transformations to update this flag if they + // add new usages of an existing import) + // -> therefore simply return false (i.e. unused) + return false; + } else { + // for performance reasons, we do not require the flaggedUsedInCode to be set to false if usages are removed + // -> therefore have to check for references now + SymbolTableEntryOriginal ste = null; + + if (importSpec instanceof NamedImportSpecifier) { + ste = findSymbolTableEntryForNamedImport((NamedImportSpecifier) importSpec); + } else if (importSpec instanceof NamespaceImportSpecifier) { + ste = findSymbolTableEntryForNamespaceImport((NamespaceImportSpecifier) importSpec); + } + + if (ste == null) { + // this may happen in case of several NamedImportSpecifiers importing the same IdentifiableElement + // (which is a validation error in most cases, but the validation misses some corner cases) + return false; + } + + // note: here it is not enough to return !ste.referencingElements.empty, because for performance reasons + // transformations are not required to remove obsolete entries from that list + boolean hasReference = exists(ste.getReferencingElements(), + refElem -> TranspilerUtils.isIntermediateModelElement(refElem)); + if (hasReference) { + // we have an actual reference to the imported element + // -> whether the import is deemed "used" depends on whether that original target actually requires + // an import (will be false in case of elements globally provided by runtime, etc.) + IdentifiableElement target = ste.getOriginalTarget(); + return target != null + && ScriptDependencyResolver.shouldBeImported(getState().resource.getModule(), target); + } + return false; + } + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SanitizeImportsTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SanitizeImportsTransformation.xtend deleted file mode 100644 index cf3f21df18..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SanitizeImportsTransformation.xtend +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.common.base.Joiner -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver -import org.eclipse.n4js.transpiler.AbstractTranspiler -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.im.IdentifierRef_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.transpiler.utils.TranspilerUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.xtext.EcoreUtil2 - -/** - * Transformation to clean up imports: - *
    - *
  • add missing imports, - *
  • remove unused import statements. - *
- */ -class SanitizeImportsTransformation extends Transformation { - - override analyze() { - } - - override assertPreConditions() { - if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { - assertTrue("requires a fully resolved script", state.im.flaggedUsageMarkingFinished); - } - } - - override assertPostConditions() { - if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { - // no import with flag used in code = false contained. - val unusedImports = EcoreUtil2.getAllContentsOfType( state.im, ImportSpecifier ).filter[!isUsed].toList; - assertTrue( "There should not be any unused import." - +" Unused="+ Joiner.on(",").join(unusedImports) - , unusedImports.size === 0 - ); - } - } - - override transform() { - addMissingImplicitImports(); - removeUnusedImports(); - } - - /** - * Compute imports for globally defined elements in other resources on the projects dependencies and - * add them iff missing. - */ - private def addMissingImplicitImports(){ - - // Current use-case is use of Variable in global-scope. - // The variable needs the defining module to be executed, hence a - - // collect all elements that are referenced from within the current module - val referencedSTEs = state.im.eAllContents.filter(IdentifierRef_IM) - .map[id_IM] - .filter(SymbolTableEntryOriginal) - .toSet; - // explanation for filters in previous lines: - // (1) reason why it is legal to filter out all ReferencingElement_IMs that aren't IdentifierRef_IM: - // * ParameterizedTypeRef_IM => type references won't show up in target code anyway -> no import required - // * ParameterizedPropertyAccessExpression_IM => here, the referenced element is a member -> members need not be imported - // (2) reason why it is legal to filter out all STEs that aren't SymbolTableEntryOriginal: - // * SymbolTableEntryInternal => low-level stuff, no imports required (at least not imports that work like N4JS imports) - // * SymbolTableEntryIMOnly => stuff programmatically created in IM -> need not be imported (defined locally) - - // filter out those that do not require an import (e.g. built-in, provided by runtime, etc.) - val baseModule = state.resource.module; - val requiresImport = referencedSTEs.filter[ScriptDependencyResolver.shouldBeImported(baseModule, originalTarget)]; - - - // look for already given imports: - val thingsToImportSTE = requiresImport; - { - // The Question is, if the variable was already imported, then - // a) do we still see it here and b) if so, was the import flagged by usedInCode ? - - // It turns out, that current scoping as of Dec'2015 doesn't bind to imports to globally available definitions. - // Assuming a) no + b) irrelevant for a==no - for (ste : thingsToImportSTE) { - if( ste.importSpecifier === null ) { - val orig = ste.originalTarget; - // for an element to be globally available, there are two preconditions: - // (1) containing module must be annotated with @@Global (2) element must be directly exported - if(N4JSLanguageUtils.isDirectlyExported(orig)) { - val module = orig.containingModule; - if(AnnotationDefinition.GLOBAL.hasAnnotation(module)) { - addNamedImport(ste,null); - } - } - } - } - } - } - - - private def removeUnusedImports(){ - - val unusedImports = EcoreUtil2.getAllContentsOfType( state.im, ImportSpecifier ).filter[!isUsed].toList - - for( val iter = unusedImports.iterator; iter.hasNext; ){ - val is = iter.next; - - val ImportDeclaration container = is.eContainer as ImportDeclaration - val toBeRemoved = if( container.importSpecifiers.size === 1 && container.importSpecifiers.contains( is )) { - // remove container if import-specifier is last child. - container - } else { - // remove import-specifier - is - }; - - remove( toBeRemoved ) - - } - - } - - - def private boolean isUsed(ImportSpecifier importSpec) { - if(importSpec.flaggedUsedInCode===false) { - // was already unused in original N4JS code (and we expect transformations to update this flag if they - // add new usages of an existing import) - // -> therefore simply return false (i.e. unused) - return false; - } else { - // for performance reasons, we do not require the flaggedUsedInCode to be set to false if usages are removed - // -> therefore have to check for references now - val ste = if(importSpec instanceof NamedImportSpecifier) { - findSymbolTableEntryForNamedImport(importSpec) - } else if(importSpec instanceof NamespaceImportSpecifier) { - findSymbolTableEntryForNamespaceImport(importSpec) - }; - - if (ste === null) { - // this may happen in case of several NamedImportSpecifiers importing the same IdentifiableElement - // (which is a validation error in most cases, but the validation misses some corner cases) - return false; - } - - // note: here it is not enough to return !ste.referencingElements.empty, because for performance reasons - // transformations are not required to remove obsolete entries from that list - val hasReference = ste.referencingElements.exists[TranspilerUtils.isIntermediateModelElement(it)]; - if(hasReference) { - // we have an actual reference to the imported element - // -> whether the import is deemed "used" depends on whether that original target actually requires - // an import (will be false in case of elements globally provided by runtime, etc.) - val target = ste.originalTarget; - return target!==null && ScriptDependencyResolver.shouldBeImported(state.resource.module, target); - } - return false; - } - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SimplifyTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SimplifyTransformation.java new file mode 100644 index 0000000000..45c02af56c --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SimplifyTransformation.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2020 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.stringTypeRef; + +import java.util.List; + +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.PropertyNameKind; +import org.eclipse.n4js.n4JS.StringLiteral; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.UnknownTypeRef; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +import com.google.inject.Inject; + +/** + * Performs various simplifications of the output code. This should run rather late to have those simplifications be + * applied to the results of, i.e. code generated by, the main transformations. + */ +public class SimplifyTransformation extends Transformation { + + @Inject + private N4JSTypeSystem ts; + + @Override + public void assertPreConditions() { + // empty + } + + @Override + public void assertPostConditions() { + // empty + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + List nodes = collectNodes(getState().im, LiteralOrComputedPropertyName.class, + false); + for (LiteralOrComputedPropertyName locpn : nodes) { + simplify(locpn); + } + } + + private void simplify(LiteralOrComputedPropertyName name) { + // simplify { ["name"+1]: ... } to { ["name1"]: ... } + if (name.getKind() == PropertyNameKind.COMPUTED + && isOfTypeStringInAST(name.getExpression())) { + + replace(name.getExpression(), _StringLiteral(name.getComputedName())); + } + + // simplify { ["name"]: ... } to { "name": ... } + if (name.getKind() == PropertyNameKind.COMPUTED && name.getExpression() instanceof StringLiteral) { + + name.setKind(PropertyNameKind.STRING); + name.setLiteralName(name.getComputedName()); + name.setComputedName(null); + remove(name.getExpression()); + } + + // simplify { "name": ... } to { name: ... } (iff 'name' is a valid identifier) + if (name.getKind() == PropertyNameKind.STRING + && name.getLiteralName() != null + && N4JSLanguageUtils.isValidIdentifier(name.getLiteralName())) { + + name.setKind(PropertyNameKind.IDENTIFIER); + } + } + + /** + * Returns true iff the given expression has a corresponding expression in the original AST and that expression in + * the AST is of type 'string'. Returns false in all error cases. + *

+ * Does not support expressions created by earlier transformations (i.e. without original AST node); returns false + * in those cases. + */ + private boolean isOfTypeStringInAST(Expression expressionInIM) { + Expression expressionInAST = getState().tracer.getOriginalASTNodeOfSameType(expressionInIM, false); + if (expressionInAST != null) { + TypeRef typeRef = ts.type(getState().G, expressionInAST); + if (!(typeRef instanceof UnknownTypeRef)) { + return ts.subtypeSucceeded(getState().G, typeRef, stringTypeRef(getState().G)); + } + } + return false; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SimplifyTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SimplifyTransformation.xtend deleted file mode 100644 index bcc8744f88..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/SimplifyTransformation.xtend +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2020 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName -import org.eclipse.n4js.n4JS.PropertyNameKind -import org.eclipse.n4js.n4JS.StringLiteral -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.ts.typeRefs.UnknownTypeRef -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.utils.N4JSLanguageUtils - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Performs various simplifications of the output code. This should run rather late to have - * those simplifications be applied to the results of, i.e. code generated by, the main transformations. - */ -class SimplifyTransformation extends Transformation { - - @Inject - private N4JSTypeSystem ts; - - override assertPreConditions() { - } - - override assertPostConditions() { - } - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, LiteralOrComputedPropertyName, false).forEach[simplify(it)]; - } - - def private void simplify(LiteralOrComputedPropertyName name) { - // simplify { ["name"+1]: ... } to { ["name1"]: ... } - if (name.kind === PropertyNameKind.COMPUTED - && isOfTypeStringInAST(name.expression)) { - - replace(name.expression, _StringLiteral(name.computedName)); - } - - // simplify { ["name"]: ... } to { "name": ... } - if (name.kind === PropertyNameKind.COMPUTED - && name.expression instanceof StringLiteral) { - - name.kind = PropertyNameKind.STRING; - name.literalName = name.computedName; - name.computedName = null; - remove(name.expression); - } - - // simplify { "name": ... } to { name: ... } (iff 'name' is a valid identifier) - if (name.kind === PropertyNameKind.STRING - && name.literalName !== null - && N4JSLanguageUtils.isValidIdentifier(name.literalName)) { - - name.kind = PropertyNameKind.IDENTIFIER; - } - } - - /** - * Returns true iff the given expression has a corresponding expression in the original AST and - * that expression in the AST is of type 'string'. Returns false in all error cases. - *

- * Does not support expressions created by earlier transformations (i.e. without original AST node); - * returns false in those cases. - */ - def private boolean isOfTypeStringInAST(Expression expressionInIM) { - val expressionInAST = state.tracer.getOriginalASTNodeOfSameType(expressionInIM, false); - if (expressionInAST !== null) { - val typeRef = ts.type(state.G, expressionInAST); - if (!(typeRef instanceof UnknownTypeRef)) { - return ts.subtypeSucceeded(state.G, typeRef, state.G.stringTypeRef); - } - } - return false; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/StaticPolyfillTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/StaticPolyfillTransformation.java new file mode 100644 index 0000000000..0b7aad908a --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/StaticPolyfillTransformation.java @@ -0,0 +1,278 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._DefaultImportSpecifier; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ImportDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NamedImportSpecifier; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NamespaceImportSpecifier; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._TypeReferenceNode; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isContainedInStaticPolyfillAware; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.flatten; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.n4JS.DefaultImportSpecifier; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassDefinition; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.assistants.TypeAssistant; +import org.eclipse.n4js.transpiler.im.ReferencingElement_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TEnumLiteral; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.utils.StaticPolyfillHelper; +import org.eclipse.n4js.utils.URIUtils; +import org.eclipse.xtext.EcoreUtil2; + +import com.google.inject.Inject; + +/** + */ +public class StaticPolyfillTransformation extends Transformation { + + @Inject + private StaticPolyfillHelper staticPolyfillHelper; + @Inject + private TypeAssistant typeAssistant; + + /** + * While inserting members into classes, we add here the elements referenced in the initializer expressions, fpar + * default expressions and bodies. + */ + private final Set referencedElements = new HashSet<>(); + /** + * Each referenced element for which we add an import gets its own alias. This set is used to keep track of those + * aliases (mainly when creating a new, unique alias). + */ + private final Set referencedElementsAliases = new HashSet<>(); + + @Override + public void assertPreConditions() { + // none + } + + @Override + public void assertPostConditions() { + // none + } + + @Override + public void analyze() { + // nothing + } + + @Override + public void transform() { + boolean isAware = isContainedInStaticPolyfillAware(getState().resource.getScript()); + if (isAware) { + N4JSResource fillingResource = staticPolyfillHelper.getStaticPolyfillResource(getState().resource); + if (fillingResource != null) { + referencedElements.clear(); + for (N4ClassDeclaration cd : collectNodes(getState().im, N4ClassDeclaration.class, false)) { + doStaticPolyfilling(cd); + } + for (SymbolTableEntryOriginal steo : referencedElements) { + addImportIfRequired(steo, fillingResource); + } + } + } + } + + private void doStaticPolyfilling(N4ClassDeclaration classDecl) { + TClass defCls = getState().info.getOriginalDefinedType(classDecl); + N4ClassDeclaration filler = staticPolyfillHelper.getStaticPolyfill(defCls); + if (filler != null) { + doStaticPolyfilling(classDecl, filler); + } + } + + private void doStaticPolyfilling(N4ClassDeclaration classFilled, N4ClassDeclaration classFiller) { + // fill additionally implemented interfaces + Set currentIfcs = new HashSet<>(); + for (TypeReferenceNode trn : classFilled.getImplementedInterfaceRefs()) { + TypeRef origTRef = getState().info.getOriginalProcessedTypeRef(trn); + if (origTRef != null && origTRef.getDeclaredType() instanceof TInterface) { + currentIfcs.add((TInterface) origTRef.getDeclaredType()); + } + } + for (TypeReferenceNode trn : classFiller.getImplementedInterfaceRefs()) { + Type origType = typeAssistant.getOriginalDeclaredType(trn); + if (origType instanceof TInterface) { + insertImplementedInterface(classFilled, (TInterface) origType, currentIfcs); + } + } + // fill members + for (N4MemberDeclaration member : classFiller.getOwnedMembers()) { + insertMember(classFilled, member); + } + } + + private void insertImplementedInterface(N4ClassDefinition classFilled, TInterface ifcToBeInserted, + Set currentIfcs) { + if (!currentIfcs.contains(ifcToBeInserted)) { // avoid duplicates! + classFilled.getImplementedInterfaceRefs() + .add(_TypeReferenceNode(getState(), TypeUtils.createTypeRef(ifcToBeInserted))); + } + } + + /** + * Create and insert a copy of memberToBeInserted into classFilled. + * + * @param classFilled + * the class declaration to be filled, must be contained in the intermediate model. + * @param memberToBeInserted + * this member is expected to be contained in the AST of the filling resource, so this is neither + * contained in the original AST (of the resource to compile) nor the intermediate model! + */ + private void insertMember(N4ClassDefinition classFilled, N4MemberDeclaration memberToBeInserted) { + N4MemberDeclaration existing = findFirst(classFilled.getOwnedMembers(), + member -> member.eClass() == memberToBeInserted.eClass() + && Objects.equals(member.getName(), memberToBeInserted.getName())); + + N4MemberDeclaration copy = copyAlienElement(memberToBeInserted); + // store elements that are referenced from within this member + // (e.g. from within the body, from initializer expressions of fields, from default expressions of fpars, etc.) + for (ReferencingElement_IM refElem : EcoreUtil2.eAllOfType(copy, ReferencingElement_IM.class)) { + if (refElem.getRewiredTarget() instanceof SymbolTableEntryOriginal) { + referencedElements.add((SymbolTableEntryOriginal) refElem.getRewiredTarget()); + } + } + + if (existing != null) { + replace(existing, copy); + } else { + classFilled.getOwnedMembersRaw().add(copy); + } + getState().info.setOriginalDefinedMember(copy, memberToBeInserted.getDefinedTypeElement()); + } + + private void addImportIfRequired(SymbolTableEntryOriginal ste, N4JSResource fillingResource) { + if (ste.getImportSpecifier() == null) { + IdentifiableElement originalTarget = ste.getOriginalTarget(); + boolean isNested = originalTarget instanceof TMember || originalTarget instanceof TEnumLiteral; + if (!isNested + || N4JSGlobals.DTS_FILE_EXTENSION + .equals(URIUtils.fileExtension(originalTarget.eResource().getURI()))) { + boolean isLocal = originalTarget.eResource() == getState().resource; + if (!isLocal + && ScriptDependencyResolver.shouldBeImported(fillingResource.getModule(), originalTarget)) { + addImport(ste, fillingResource); + } + } + } + } + + /** + * Add an import for the element represented by the given SymbolTableEntry in the intermediate model. + */ + private void addImport(SymbolTableEntryOriginal ste, N4JSResource fillingResource) { + IdentifiableElement importedElement = ste.getOriginalTarget(); + boolean isNamespace = importedElement instanceof ModuleNamespaceVirtualType; + + // search original import specification (in original AST of fillingResource) + Iterable impSpecs = flatten(map(filter( + fillingResource.getScript().getScriptElements(), ImportDeclaration.class), + id -> id.getImportSpecifiers())); + ImportSpecifier impSpec_original = (isNamespace) + ? findFirst(filter(impSpecs, NamespaceImportSpecifier.class), + nis -> nis.getDefinedType() == importedElement) + : findFirst(filter(impSpecs, NamedImportSpecifier.class), + nis -> nis.getImportedElement() == importedElement); + EObject impDecl_original = impSpec_original == null ? null : impSpec_original.eContainer(); + + // if all this was successful, go ahead and add the import ... + if (impDecl_original instanceof ImportDeclaration) { + ImportDeclaration impDeclCasted = (ImportDeclaration) impDecl_original; + String aliasName = getAlias(impSpec_original) != null + ? getAlias(impSpec_original) + : ste.getExportedName() != null + ? ste.getExportedName() + : "unnamed"; + String alias = chooseNewUniqueAlias(aliasName); + ImportSpecifier impSpec; + if (isNamespace) { + impSpec = _NamespaceImportSpecifier(alias, true); + } else if (impSpec_original instanceof DefaultImportSpecifier) { + impSpec = _DefaultImportSpecifier(alias, true); + } else { + impSpec = _NamedImportSpecifier(ste.getExportedName(), alias, true); + } + + // obtain module from which we import importedElement + TModule remoteModule = (isNamespace) ? + // warning: in case of namespaces, importedElement resides in the TModule of the fillingResource! + // -> so we cannot just get the containing TModule in this case + ((ModuleNamespaceVirtualType) importedElement).getModule() + : + // standard case: just find the containing TModule of importedElement + impDeclCasted.getModule(); + + ImportDeclaration impDecl = _ImportDecl(impSpec); + impDecl.setModuleSpecifierForm(impDeclCasted.getModuleSpecifierForm()); + getState().im.getScriptElements().add(0, impDecl); + ste.setName(alias); + ste.setImportSpecifier(impSpec); + getState().tracer.setOriginalASTNode(impDecl, impDeclCasted); + getState().tracer.setOriginalASTNode(impSpec, impSpec_original); + // store the imported module in information registry + // (required my ModuleWrappingTransformation) + getState().info.setImportedModule(impDecl, remoteModule); + } + } + + private String chooseNewUniqueAlias(String baseName) { + String alias; + int cnt = 0; + do { + String cntStr = (cnt == 0) ? "" : Integer.toString(cnt); + alias = baseName + cntStr + "$polyfilled"; + cnt++; + } while (referencedElementsAliases.contains(alias)); + referencedElementsAliases.add(alias); + return alias; + } + + private static final String getAlias(ImportSpecifier impSpec) { + if (impSpec instanceof NamedImportSpecifier) { + return ((NamedImportSpecifier) impSpec).getAlias(); + } + + if (impSpec instanceof NamespaceImportSpecifier) { + return ((NamespaceImportSpecifier) impSpec).getAlias(); + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/StaticPolyfillTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/StaticPolyfillTransformation.xtend deleted file mode 100644 index 09e6fe1aee..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/StaticPolyfillTransformation.xtend +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.inject.Inject -import java.util.Set -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.n4JS.DefaultImportSpecifier -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassDefinition -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.assistants.TypeAssistant -import org.eclipse.n4js.transpiler.im.ReferencingElement_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.TEnumLiteral -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.utils.StaticPolyfillHelper -import org.eclipse.n4js.utils.URIUtils - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.* - -/** - */ -class StaticPolyfillTransformation extends Transformation { - - @Inject private StaticPolyfillHelper staticPolyfillHelper; - @Inject private TypeAssistant typeAssistant; - - /** - * While inserting members into classes, we add here the elements referenced in the initializer expressions, fpar - * default expressions and bodies. - */ - private Set referencedElements = newHashSet; - /** - * Each referenced element for which we add an import gets its own alias. This set is used to keep track of those - * aliases (mainly when creating a new, unique alias). - */ - private Set referencedElementsAliases = newHashSet; - - - override assertPreConditions() { - // none - } - - override assertPostConditions() { - // none - } - - override analyze() { - // nothing - } - - override transform() { - val isAware = state.resource.script.isContainedInStaticPolyfillAware; - if(isAware) { - val fillingResource = staticPolyfillHelper.getStaticPolyfillResource(state.resource); - if(fillingResource!==null) { - referencedElements.clear(); - collectNodes(state.im, N4ClassDeclaration, false).forEach[doStaticPolyfilling]; - referencedElements.forEach[addImportIfRequired(fillingResource)]; - } - } - } - - def private void doStaticPolyfilling(N4ClassDeclaration classDecl) { - val defCls = state.info.getOriginalDefinedType(classDecl); - val filler = staticPolyfillHelper.getStaticPolyfill(defCls); - if(filler!==null) { - doStaticPolyfilling(classDecl, filler); - } - } - - def private void doStaticPolyfilling(N4ClassDeclaration classFilled, N4ClassDeclaration classFiller) { - // fill additionally implemented interfaces - val currentIfcs = classFilled.implementedInterfaceRefs.map[state.info.getOriginalProcessedTypeRef(it)?.declaredType].filterNull.filter(TInterface).toSet; - classFiller.implementedInterfaceRefs - .map[typeAssistant.getOriginalDeclaredType(it)] - .filter(TInterface) - .forEach[classFilled.insertImplementedInterface(it, currentIfcs)]; - // fill members - classFiller.ownedMembers.forEach[classFilled.insertMember(it)]; - } - - def private void insertImplementedInterface(N4ClassDefinition classFilled, TInterface ifcToBeInserted, - Set currentIfcs) { - if(!currentIfcs.contains(ifcToBeInserted)) { // avoid duplicates! - classFilled.implementedInterfaceRefs += _TypeReferenceNode(state, TypeUtils.createTypeRef(ifcToBeInserted)); - } - } - - /** - * Create and insert a copy of memberToBeInserted into classFilled. - * - * @param classFilled the class declaration to be filled, must be contained in the intermediate model. - * @param memberToBeInserted this member is expected to be contained in the AST of the filling resource, so this is - * neither contained in the original AST (of the resource to compile) nor the intermediate model! - */ - def private void insertMember(N4ClassDefinition classFilled, N4MemberDeclaration memberToBeInserted) { - val existing = classFilled.ownedMembers.findFirst[ - eClass===memberToBeInserted.eClass && name==memberToBeInserted.name - ]; - val copy = copyAlienElement(memberToBeInserted); - // store elements that are referenced from within this member - // (e.g. from within the body, from initializer expressions of fields, from default expressions of fpars, etc.) - referencedElements += copy.eAllContents.filter(ReferencingElement_IM).map[rewiredTarget].filter(SymbolTableEntryOriginal).toList; - if(existing!==null) { - replace(existing, copy); - } else { - classFilled.ownedMembersRaw += copy; - } - state.info.setOriginalDefinedMember(copy, memberToBeInserted.definedTypeElement); - } - - def private void addImportIfRequired(SymbolTableEntryOriginal ste, N4JSResource fillingResource) { - if(ste.importSpecifier===null) { - val originalTarget = ste.originalTarget; - val isNested = originalTarget instanceof TMember || originalTarget instanceof TEnumLiteral; - if(!isNested || N4JSGlobals.DTS_FILE_EXTENSION == URIUtils.fileExtension(originalTarget.eResource.URI)) { - val isLocal = originalTarget.eResource === state.resource; - if(!isLocal && ScriptDependencyResolver.shouldBeImported(fillingResource.module, originalTarget)) { - addImport(ste, fillingResource); - } - } - } - } - - /** - * Add an import for the element represented by the given SymbolTableEntry in the intermediate model. - */ - def private void addImport(SymbolTableEntryOriginal ste, N4JSResource fillingResource) { - val importedElement = ste.originalTarget; - val isNamespace = importedElement instanceof ModuleNamespaceVirtualType; - - // search original import specification (in original AST of fillingResource) - val impSpecs = fillingResource.script.scriptElements.filter(ImportDeclaration).map[importSpecifiers].flatten; - val impSpec_original = if(isNamespace) { - impSpecs.filter(NamespaceImportSpecifier).findFirst[definedType===importedElement] - } else { - impSpecs.filter(NamedImportSpecifier).findFirst[it.importedElement===importedElement] - }; - val impDecl_original = impSpec_original?.eContainer; - - - // if all this was successful, go ahead and add the import ... - if(impDecl_original instanceof ImportDeclaration) { - val alias = chooseNewUniqueAlias(impSpec_original.alias ?: ste.exportedName ?: "unnamed"); - val impSpec = if(isNamespace) { - _NamespaceImportSpecifier(alias, true) - } else if (impSpec_original instanceof DefaultImportSpecifier) { - _DefaultImportSpecifier(alias, true) - } else { - _NamedImportSpecifier(ste.exportedName, alias, true) - }; - - // obtain module from which we import importedElement - val remoteModule = if(isNamespace) { - // warning: in case of namespaces, importedElement resides in the TModule of the fillingResource! - // -> so we cannot just get the containing TModule in this case - (importedElement as ModuleNamespaceVirtualType).module - } else { - // standard case: just find the containing TModule of importedElement - impDecl_original.module - }; - - val impDecl = _ImportDecl(impSpec); - impDecl.moduleSpecifierForm = impDecl_original.moduleSpecifierForm; - state.im.scriptElements.add(0, impDecl); - ste.name = alias; - ste.importSpecifier = impSpec; - state.tracer.setOriginalASTNode(impDecl, impDecl_original); - state.tracer.setOriginalASTNode(impSpec, impSpec_original); - // store the imported module in information registry - // (required my ModuleWrappingTransformation) - state.info.setImportedModule(impDecl, remoteModule); - } - } - - def private String chooseNewUniqueAlias(String baseName) { - var String alias; - var cnt = 0; - do { - val cntStr = if(cnt===0) "" else Integer.toString(cnt); - alias = baseName + cntStr + "$polyfilled"; - cnt++; - } while(referencedElementsAliases.contains(alias)); - referencedElementsAliases.add(alias); - return alias; - } - - def private static final String getAlias(ImportSpecifier impSpec) { - return switch(impSpec) { - NamedImportSpecifier: impSpec.alias - NamespaceImportSpecifier: impSpec.alias - }; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TemplateStringTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TemplateStringTransformation.java new file mode 100644 index 0000000000..a1ed513985 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TemplateStringTransformation.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2017 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._AdditiveExpression; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Parenthesis; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.generator.GeneratorOption; +import org.eclipse.n4js.n4JS.AdditiveOperator; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ParameterizedAccess; +import org.eclipse.n4js.n4JS.PrimaryExpression; +import org.eclipse.n4js.n4JS.StringLiteral; +import org.eclipse.n4js.n4JS.TaggedTemplateString; +import org.eclipse.n4js.n4JS.TemplateLiteral; +import org.eclipse.n4js.n4JS.TemplateSegment; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.TransformationDependency.Optional; +import org.eclipse.n4js.utils.parser.conversion.ValueConverterUtils; + +import com.google.common.collect.Lists; + +/** + * Transforms ES2015 template literals to a corresponding ES5 equivalent. + */ +@Optional(GeneratorOption.TemplateStringLiterals) +public class TemplateStringTransformation extends Transformation { + + @Override + public void assertPreConditions() { + // true + } + + @Override + public void assertPostConditions() { + // true + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + for (TemplateLiteral tl : collectNodes(getState().im, TemplateLiteral.class, true)) { + transformTemplateLiteral(tl); + } + } + + private void transformTemplateLiteral(TemplateLiteral template) { + EObject parent = template.eContainer(); + if (parent instanceof TaggedTemplateString) { + transformTaggedTemplateLiteral((TaggedTemplateString) parent, template); + } else { + transformPlainTemplateLiteral(template); + } + } + + private void transformTaggedTemplateLiteral(TaggedTemplateString taggedTemplate, TemplateLiteral template) { + // (1) collect string segments and expression segments + List stringSegments = new ArrayList<>(); + List expressionSegments = new ArrayList<>(); + for (Expression segment : Lists.newArrayList(template.getSegments())) { // avoid concurrent modification + if (segment instanceof TemplateSegment) { + TemplateSegment tSegm = (TemplateSegment) segment; + stringSegments.add(_StringLiteral(tSegm.getValueAsString(), wrapAndQuote(tSegm.getValueAsString()))); + } else { + expressionSegments.add(segment); + } + } + // (2) transform the tagged template literal (invoke the tag function with the segments as arguments) + Expression[] wrappedExprs = wrapIfRequired(expressionSegments); + Expression[] args = new Expression[1 + expressionSegments.size()]; + args[0] = _ArrLit(stringSegments.toArray(new Expression[0])); + for (int i = 0; i < wrappedExprs.length; i++) { + args[i + 1] = wrappedExprs[i]; + } + Expression replacement = _CallExpr( + taggedTemplate.getTarget(), + taggedTemplate.isOptionalChaining(), + args); + replace(taggedTemplate, replacement); + } + + private void transformPlainTemplateLiteral(TemplateLiteral template) { + // (1) transform contained template segments to string literals + for (Expression segment : Lists.newArrayList(template.getSegments())) { // avoid concurrent modification + if (segment instanceof TemplateSegment) { + TemplateSegment tSegm = (TemplateSegment) segment; + replace(segment, _StringLiteral(tSegm.getValueAsString(), wrapAndQuote(tSegm.getValueAsString()))); + } else { + // the segment is an ordinary expression -> leave without change + } + } + // (2) transform template literal itself + Expression replacement; + switch (template.getSegments().size()) { + case 0: + replacement = _StringLiteral(""); + break; + case 1: + replacement = template.getSegments().get(0); + break; + default: + replacement = _Parenthesis( + _AdditiveExpression(AdditiveOperator.ADD, wrapIfRequired(template.getSegments()))); + } + replace(template, replacement); + } + + private static Expression[] wrapIfRequired(List expressions) { + // required due to possible concurrent modification (see below) + List copy = new ArrayList<>(); + for (Expression expr : expressions) { + boolean needParentheses = !(expr instanceof PrimaryExpression || expr instanceof ParameterizedAccess); + if (needParentheses) { + // this might change iterable 'expressions' (if an EMF containment reference was passed in) + copy.add(_Parenthesis(expr)); + } else { + copy.add(expr); + } + } + return copy.toArray(new Expression[0]); + } + + /** + * put raw into double quote and escape all existing double-quotes {@code '"' -> '\"' } and newlines + * {@code '\n' -> '\\n'}. + */ + private static String wrapAndQuote(String raw) { + return '"' + ValueConverterUtils.convertToEscapedString(raw, true) + '"'; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TemplateStringTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TemplateStringTransformation.xtend deleted file mode 100644 index 69ffb98d32..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TemplateStringTransformation.xtend +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2017 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import com.google.common.collect.Lists -import java.util.ArrayList -import org.eclipse.n4js.n4JS.AdditiveOperator -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ParameterizedAccess -import org.eclipse.n4js.n4JS.PrimaryExpression -import org.eclipse.n4js.n4JS.TaggedTemplateString -import org.eclipse.n4js.n4JS.TemplateLiteral -import org.eclipse.n4js.n4JS.TemplateSegment -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.TransformationDependency.Optional -import org.eclipse.n4js.utils.parser.conversion.ValueConverterUtils - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -/** - * Transforms ES2015 template literals to a corresponding ES5 equivalent. - */ -@Optional(TemplateStringLiterals) -class TemplateStringTransformation extends Transformation { - - - override assertPreConditions() { - // true - } - - override assertPostConditions() { - // true - } - - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, TemplateLiteral, true).forEach[transformTemplateLiteral]; - } - - def private void transformTemplateLiteral(TemplateLiteral template) { - val parent = template.eContainer; - if (parent instanceof TaggedTemplateString) { - transformTaggedTemplateLiteral(parent, template); - } else { - transformPlainTemplateLiteral(template); - } - } - - def private void transformTaggedTemplateLiteral(TaggedTemplateString taggedTemplate, TemplateLiteral template) { - // (1) collect string segments and expression segments - val stringSegments = newArrayList; - val expressionSegments = newArrayList; - for(segment : Lists.newArrayList(template.segments)) { // avoid concurrent modification - if(segment instanceof TemplateSegment) { - stringSegments += _StringLiteral(segment.valueAsString, segment.valueAsString.wrapAndQuote); - } else { - expressionSegments += segment; - } - } - // (2) transform the tagged template literal (invoke the tag function with the segments as arguments) - val replacement = _CallExpr( - taggedTemplate.target, - taggedTemplate.optionalChaining, - #[ _ArrLit(stringSegments) ] + expressionSegments.wrapIfRequired - ); - replace(taggedTemplate, replacement); - } - - def private void transformPlainTemplateLiteral(TemplateLiteral template) { - // (1) transform contained template segments to string literals - for(segment : Lists.newArrayList(template.segments)) { // avoid concurrent modification - if(segment instanceof TemplateSegment) { - replace(segment, _StringLiteral(segment.valueAsString, segment.valueAsString.wrapAndQuote)); - } else { - // the segment is an ordinary expression -> leave without change - } - } - // (2) transform template literal itself - val replacement = switch(template.segments.size) { - case 0: - _StringLiteral("") - case 1: - template.segments.get(0) - default: - _Parenthesis( - _AdditiveExpression(AdditiveOperator.ADD, template.segments.wrapIfRequired) - ) - }; - replace(template, replacement); - } - - def private static Iterable wrapIfRequired(Iterable expressions) { - val copy = new ArrayList(expressions.toList); // required due to possible concurrent modification (see below) - return copy.map[expr| - val boolean needParentheses = !(expr instanceof PrimaryExpression || expr instanceof ParameterizedAccess); - if(needParentheses) { - return _Parenthesis(expr) // this might change iterable 'expressions' (if an EMF containment reference was passed in) - } else { - return expr - } - ]; - } - - /** put raw into double quote and escape all existing double-quotes {@code '"' -> '\"' } and newlines {@code '\n' -> '\\n'}. */ - def private static String wrapAndQuote(String raw){ - '"' + ValueConverterUtils.convertToEscapedString(raw, true) + '"' - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TrimTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TrimTransformation.java new file mode 100644 index 0000000000..968db166dc --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TrimTransformation.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.es.transform; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.N4TypeAliasDeclaration; +import org.eclipse.n4js.n4JS.N4TypeVariable; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.n4JS.TypedElement; +import org.eclipse.n4js.transpiler.Transformation; +import org.eclipse.n4js.transpiler.utils.TranspilerUtils; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.Type; + +/** + * Removes all nodes from the intermediate model that are not required in the final output. Since such nodes might + * provide important information to other transformations, this transformation should run rather late. + *

+ * Examples of removed stuff: + *

    + *
  • declared type references of {@link TypedElement}s + *
  • type variable references of {@link Type} + *
  • type alias declarations + *
+ * Note: cast expressions are already removed by {@link ExpressionTransformation}. + */ +public class TrimTransformation extends Transformation { + + @Override + public void assertPreConditions() { + // empty + } + + @Override + public void assertPostConditions() { + // empty + } + + @Override + public void analyze() { + // ignore + } + + @Override + public void transform() { + for (EObject node : collectNodes(getState().im, false, + TypeRef.class, + TypeReferenceNode.class, + N4TypeVariable.class, + N4TypeAliasDeclaration.class)) { + + removeNode(node); + } + } + + private void removeNode(EObject nodeInIM) { + EObject actualNodeToRemove = TranspilerUtils.orContainingExportDeclaration(nodeInIM); + remove(actualNodeToRemove); + } +} diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TrimTransformation.xtend b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TrimTransformation.xtend deleted file mode 100644 index 5e29d97b9b..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/TrimTransformation.xtend +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.es.transform - -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.N4TypeAliasDeclaration -import org.eclipse.n4js.n4JS.N4TypeVariable -import org.eclipse.n4js.n4JS.TypeReferenceNode -import org.eclipse.n4js.n4JS.TypedElement -import org.eclipse.n4js.transpiler.Transformation -import org.eclipse.n4js.transpiler.utils.TranspilerUtils -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.Type - -/** - * Removes all nodes from the intermediate model that are not required in the final output. Since such nodes might - * provide important information to other transformations, this transformation should run rather late. - *

- * Examples of removed stuff: - *

    - *
  • declared type references of {@link TypedElement}s - *
  • type variable references of {@link Type} - *
  • type alias declarations - *
- * Note: cast expressions are already removed by {@link ExpressionTransformation}. - */ -class TrimTransformation extends Transformation { - - - override assertPreConditions() { - } - - override assertPostConditions() { - } - - override analyze() { - // ignore - } - - override transform() { - collectNodes(state.im, false, - TypeRef, - TypeReferenceNode, - N4TypeVariable, - N4TypeAliasDeclaration).forEach[removeNode] - } - - def private removeNode(EObject nodeInIM) { - val actualNodeToRemove = TranspilerUtils.orContainingExportDeclaration(nodeInIM); - remove(actualNodeToRemove); - } -} diff --git a/plugins/org.eclipse.n4js.transpiler.es/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.transpiler.es/xtend-gen/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/plugins/org.eclipse.n4js.transpiler.es/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/plugins/org.eclipse.n4js.transpiler/.classpath b/plugins/org.eclipse.n4js.transpiler/.classpath index 3bc8444c24..bc000d436d 100644 --- a/plugins/org.eclipse.n4js.transpiler/.classpath +++ b/plugins/org.eclipse.n4js.transpiler/.classpath @@ -1,7 +1,6 @@ - diff --git a/plugins/org.eclipse.n4js.transpiler/build.properties b/plugins/org.eclipse.n4js.transpiler/build.properties index d9f34fffdd..0c38f4c755 100644 --- a/plugins/org.eclipse.n4js.transpiler/build.properties +++ b/plugins/org.eclipse.n4js.transpiler/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/,\ emf-gen/ output.. = bin/ bin.includes = META-INF/,\ diff --git a/plugins/org.eclipse.n4js.transpiler/pom.xml b/plugins/org.eclipse.n4js.transpiler/pom.xml index 786855639e..142a1e8d89 100644 --- a/plugins/org.eclipse.n4js.transpiler/pom.xml +++ b/plugins/org.eclipse.n4js.transpiler/pom.xml @@ -41,10 +41,6 @@ Contributors: maven-resources-plugin ${maven-resources-plugin.version} - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/SymbolTableManagement.java b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/SymbolTableManagement.java new file mode 100644 index 0000000000..a67b88f9a0 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/SymbolTableManagement.java @@ -0,0 +1,484 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.NamedElement; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.transpiler.im.ImFactory; +import org.eclipse.n4js.transpiler.im.ImPackage; +import org.eclipse.n4js.transpiler.im.ReferencingElement_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryIMOnly; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.im.TypeReferenceNode_IM; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.NameAndAccess; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TModule; + +/** + */ +public class SymbolTableManagement { + + /** + * Create a symbol table entry for a given original target (either a TModule element OR a variable in the original + * AST, in case of non-exported top-level variables, local variables, formal parameters, etc.). + */ + public static SymbolTableEntryOriginal createSymbolTableEntryOriginal(TranspilerState state, + IdentifiableElement originalTarget) { + if (originalTarget == null) { + throw new IllegalArgumentException("original target may not be null"); + } + SymbolTableEntryOriginal newEntry = ImFactory.eINSTANCE.createSymbolTableEntryOriginal(); + newEntry.setName(originalTarget.getName()); + newEntry.setOriginalTarget(originalTarget); + if (originalTarget instanceof NamedElement) { + newEntry.getElementsOfThisName().add((NamedElement) originalTarget); + } + + // if a namespace import exists for the module containing 'originalTarget', we use it for this new STE + newEntry.setImportSpecifier( + getExistingNamespaceImportSpecifierForModule(state, originalTarget.getContainingModule())); + + addOriginal(state, newEntry); + + return newEntry; + } + + private static NamespaceImportSpecifier getExistingNamespaceImportSpecifierForModule(TranspilerState state, + TModule module) { + if (module != null && state.steCache.mapImportedModule_2_STE.get(module) != null) { + ImportSpecifier importSpec = state.steCache.mapImportedModule_2_STE.get(module).getImportSpecifier(); + if (importSpec instanceof NamespaceImportSpecifier) { + return (NamespaceImportSpecifier) importSpec; + } + } + return null; + } + + /** add a {@link SymbolTableEntryOriginal} */ + static public void addOriginal(TranspilerState state, SymbolTableEntryOriginal steOriginal) { + addOriginal(state.steCache, steOriginal); + } + + /** + * NOTE: Internal usage in preparation step, please call + * {@link #addOriginal(TranspilerState,SymbolTableEntryOriginal)} + */ + static public void addOriginal(TranspilerState.STECache steCache, SymbolTableEntryOriginal steOriginal) { + + SymbolTableEntryOriginal old = steCache.mapOriginal.put(steOriginal.getOriginalTarget(), steOriginal); + if (old != null) + throw new IllegalStateException( + "It is not allowed to register more then one STEOriginal for the same original Target. Already had: " + + old); + IdentifiableElement originalTarget = steOriginal.getOriginalTarget(); + if (originalTarget instanceof ModuleNamespaceVirtualType) { + TModule namespaceModule = ((ModuleNamespaceVirtualType) originalTarget).getModule(); + if (namespaceModule != null) { + steCache.mapImportedModule_2_STE.put(namespaceModule, steOriginal); + } + } + steCache.im.getSymbolTable().getEntries().add(steOriginal); + inverseMap(steCache, steOriginal); + } + + static private void inverseMap(TranspilerState.STECache steManager, SymbolTableEntryOriginal steOriginal) { + // register elements of this name. + for (NamedElement ele : steOriginal.getElementsOfThisName()) { + steManager.mapNamedElement_2_STE.put(ele, steOriginal); + } + } + + /** + * Create a symbol table entry for an element in the intermediate model. This should only be used if the element in + * the IM does not have a corresponding original target (either a TModule element or an element in the + * original AST, in case of non-exported variables), for example because it was newly created by an AST + * transformation. + */ + public static SymbolTableEntryIMOnly createSymbolTableEntryIMOnly(TranspilerState state, NamedElement elementInIM) { + if (elementInIM == null) { + throw new IllegalArgumentException("element in intermediate model may not be null"); + } + if (elementInIM.getName() == null) { + throw new IllegalArgumentException( + "element in intermediate model may not be unnamed when creating a symbol table entry for it"); + } + SymbolTableEntryIMOnly newEntry = ImFactory.eINSTANCE.createSymbolTableEntryIMOnly(); + newEntry.setName(elementInIM.getName()); + newEntry.getElementsOfThisName().add(elementInIM); + addIMOnly(state, newEntry); + return newEntry; + } + + /** + * Create an internal symbol table entry. They are special and should be used only in rare exception cases. + * See {@link SymbolTableEntryInternal} for details. + */ + public static SymbolTableEntryInternal createSymbolTableEntryInternal(TranspilerState state, String name) { + if (name == null) { + throw new IllegalArgumentException("name may not be null"); + } + SymbolTableEntryInternal newEntry = ImFactory.eINSTANCE.createSymbolTableEntryInternal(); + newEntry.setName(name); + addInteral(state, newEntry); + return newEntry; + } + + /** add a {@link SymbolTableEntryInternal} */ + private static void addInteral(TranspilerState state, SymbolTableEntryInternal ste) { + SymbolTableEntryInternal old = state.steCache.mapInternal.put(ste.getName(), ste); + if (old != null) { + throw new IllegalStateException( + "It is not allowed to put the same SymbolTableEntryInternal twice into the Symboltable " + old); + } + state.im.getSymbolTable().getEntries().add(ste); + } + + /** + * Search an STE by original target and create it if not found. + */ + public static SymbolTableEntryOriginal getSymbolTableEntryOriginal(TranspilerState state, + IdentifiableElement originalTarget, boolean create) { + if (originalTarget == null) { + throw new IllegalArgumentException("original target may not be null"); + } + SymbolTableEntryOriginal existingEntry = getSteOriginal(state, originalTarget); + + if (existingEntry != null) { + return existingEntry; + } + if (create) { + return createSymbolTableEntryOriginal(state, originalTarget); + } + return null; + } + + /** + * Convenience method for {@link #getSymbolTableEntryOriginal(TranspilerState, IdentifiableElement, boolean)}, + * allowing to retrieve the member by name and access from its parent classifier. + */ + public static SymbolTableEntryOriginal getSymbolTableEntryForMember(TranspilerState state, TClassifier type, + String memberName, boolean writeAccess, boolean staticAccess, boolean create) { + if (type == null || memberName == null || memberName.isEmpty()) { + throw new IllegalArgumentException("type may not be null and memberName may not be null or empty"); + } + TMember m = type.findOwnedMember(memberName, writeAccess, staticAccess); + if (m == null) { + NameAndAccess nameAndAccess = new NameAndAccess(memberName, writeAccess, staticAccess); + throw new IllegalArgumentException("no such member found in given type: " + nameAndAccess); + } + return getSymbolTableEntryOriginal(state, m, create); + } + + /** + * Search an internal STE by name and create it if not found. + */ + public static SymbolTableEntryInternal getSymbolTableEntryInternal(TranspilerState state, String name, + boolean create) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("name may not be null or empty"); + } + + SymbolTableEntryInternal existingEntry = getSteInternal(state, name); + + if (existingEntry != null) { + return existingEntry; + } + if (create) { + return createSymbolTableEntryInternal(state, name); + } + return null; + } + + /** + * Will look up the STE for the given named element in the IM. If not found and create is set to + * true a {@code SymbolTableEntryIMOnly} is created, otherwise null is returned. + *

+ * WARNING: during look up it will find both {@link SymbolTableEntryOriginal}s and + * {@link SymbolTableEntryIMOnly}s, but when creating a new STE, it will always create a + * {@code SymbolTableEntryIMOnly} which is invalid if there exists an original target for the given + * elementInIM (then a {@link SymbolTableEntryOriginal} would have to be created)!. In such a case, + * this method must not be used.
+ * Most of the time, this won't be the case and it is safe to use this method, because all + * {@code SymbolTableEntryOriginal}s will be created up-front during the {@link PreparationStep}; in some special + * cases, however, a new element is introduced into the IM that actually has an original target (so far, static + * polyfills are the only case of this). + */ + public static SymbolTableEntry findSymbolTableEntryForElement(TranspilerState state, NamedElement elementInIM, + boolean create) { + if (elementInIM == null) { + throw new IllegalArgumentException("element in intermediate model may not be null"); + } + SymbolTableEntry existingEntry = byElementsOfThisName(state, elementInIM); + + if (existingEntry != null) { + return existingEntry; + } + if (create) { + return createSymbolTableEntryIMOnly(state, elementInIM); + } + return null; + } + + /** + * Search STE for the given name space import. + */ + public static SymbolTableEntryOriginal findSymbolTableEntryForNamespaceImport(TranspilerState state, + NamespaceImportSpecifier importspec) { + // 1. linear version: + // state.im.symbolTable.entries.filter(SymbolTableEntryOriginal) + // .filter[it.importSpecifier == importspec] + // .filter[it.originalTarget instanceof ModuleNamespaceVirtualType] + // .head + + // 2. parallel version: + // return state.im.symbolTable.entries.parallelStream() + // .filter[it instanceof SymbolTableEntryOriginal].map[ it as SymbolTableEntryOriginal] + // .filter[it.importSpecifier == importspec] + // .filter[it.originalTarget instanceof ModuleNamespaceVirtualType] + // .findAny().orElse(null); + + // 3. only the originals: + // Should be safe to use the cache. + for (SymbolTableEntryOriginal steo : state.steCache.mapOriginal.values()) { + if (steo.getImportSpecifier() == importspec + && steo.getOriginalTarget() instanceof ModuleNamespaceVirtualType) { + return steo; + } + } + + return null; + } + + // let's try to keep this at "package" visibility for now (but there'll probably be special cases when a + // transformation needs to rewire something special by calling this directly) + /* package */ static void rewireSymbolTable(TranspilerState state, EObject from, EObject to) { + + if (!requiresRewiringOfSymbolTable(from) && !requiresRewiringOfSymbolTable(to)) { + return; // nothing to rewire! + } + + if (from instanceof ReferencingElement_IM && to instanceof ReferencingElement_IM) { + // case 1 + EReference eRefThatMightPointToOriginal = ImPackage.eINSTANCE.getSymbolTableEntry_ReferencingElements(); + // TODO can be speed up + for (SymbolTableEntry ste : state.im.getSymbolTable().getEntries()) { + replaceInEReference(ste, eRefThatMightPointToOriginal, from, to); + } + + } else if (from instanceof ImportSpecifier && to instanceof ImportSpecifier) { + // case 2 + EReference eRefThatMightPointToOriginal = ImPackage.eINSTANCE.getSymbolTableEntryOriginal_ImportSpecifier(); + // TODO can be speed up + for (SymbolTableEntry ste : state.im.getSymbolTable().getEntries()) { + if (ste instanceof SymbolTableEntryOriginal) { + replaceInEReference(ste, eRefThatMightPointToOriginal, from, to); + } + } + + } else if (from instanceof NamedElement && to instanceof NamedElement) { + // case 3 // Most relevant case according to profiler + EReference eRefThatMightPointToOriginal = ImPackage.eINSTANCE.getSymbolTableEntry_ElementsOfThisName(); + // Slow version: + // state.im.symbolTable.entries_.forEach[ + // replaceInEReference(it, x, from, to); + // ]; + + SymbolTableEntry steFrom = byElementsOfThisName(state, (NamedElement) from); + if (steFrom != null) { + replaceInEReference(steFrom, eRefThatMightPointToOriginal, from, to); + // update STECache: + replacedElementOfThisName(state, steFrom, (NamedElement) from, (NamedElement) to); + } + } else { + throw new IllegalArgumentException("rewiring symbol table entries from type " + from.eClass().getName() + + " to type " + to.eClass().getName() + " is not supported yet"); + } + } + + private static boolean requiresRewiringOfSymbolTable(EObject obj) { + return obj instanceof ReferencingElement_IM || obj instanceof ImportSpecifier || obj instanceof NamedElement; + } + + private static void replaceInEReference(EObject obj, EReference eRef, T original, + TN replacement) { + // note: cannot use EcoreUtil#replace() here, because it throws exceptions if original is not in reference! + if (eRef.isMany()) { + @SuppressWarnings("unchecked") + EList l = (EList) obj.eGet(eRef); + for (int idx = 0; idx < l.size(); idx++) { + if (l.get(idx) == original) { + l.set(idx, replacement); + } + } + } else { + if (obj.eGet(eRef) == original) { + obj.eSet(eRef, replacement); + } + } + } + + /** add a {@link SymbolTableEntryIMOnly} */ + static public void addIMOnly(TranspilerState state, SymbolTableEntryIMOnly only) { + // assumption 1: freshly generated - always connected to a named element. (IDEBUG-777) + if (only.getElementsOfThisName().size() != 1) { + throw new IllegalArgumentException( + "got a STEImOnly with elmentsOfThisName != 1 : " + only.getElementsOfThisName().size()); + } + // assumption 2: there are no other things registered by this name. (IDEBUG-777) + SymbolTableEntry old = state.steCache.mapNamedElement_2_STE.put(only.getElementsOfThisName().get(0), only); + if (old != null) { + throw new IllegalStateException("tries to install STEImOnly but already had one for the NamedElmeent = " + + only.getElementsOfThisName().get(0)); + } + state.im.getSymbolTable().getEntries().add(only); + } + + /** lookup a {@link SymbolTableEntryIMOnly} associated to an {@link IdentifiableElement} */ + static public SymbolTableEntryOriginal getSteOriginal(TranspilerState state, IdentifiableElement element) { + return state.steCache.mapOriginal.get(element); + } + + /** lookup an {@link SymbolTableEntryInternal} based on a plain name ({@link String}) */ + static public SymbolTableEntryInternal getSteInternal(TranspilerState state, String name) { + return state.steCache.mapInternal.get(name); + } + + /** lookup a {@link SymbolTableEntry} based on a {@link NamedElement} contained in the IM */ + static public SymbolTableEntry byElementsOfThisName(TranspilerState state, NamedElement elementInIM) { + SymbolTableEntry lookup = state.steCache.mapNamedElement_2_STE.get(elementInIM); + if (lookup != null) { + if (lookup.getElementsOfThisName().contains(elementInIM)) { + return lookup; + } + throw new IllegalStateException( + "Did find STE by NamedElement which is not contained in the list STE.elementsOfThisName. elementInIM=" + + elementInIM + " found wrong STE=" + lookup); + } + + return null; + } + + /** + * Update data structure for NamedElements after the list of {@link SymbolTableEntry#getElementsOfThisName()} of + * {@code entry} has been modified + * + * @param entry + * the updated STE (wherein elmentsOfThisName has been modified to contain {@code to} instead of + * {@code from} + * @param from + * old NamedElement + * @param to + * new NamedElement + */ + static public void replacedElementOfThisName(TranspilerState state, SymbolTableEntry entry, NamedElement from, + NamedElement to) { + + // internal check: + SymbolTableEntry steRegisteredWithFrom = state.steCache.mapNamedElement_2_STE.get(from); + if (steRegisteredWithFrom != entry) + throw new IllegalArgumentException( + "This method must be called directly after the replacement and only once." + + "Expected from=" + from + " to be related to entry=" + entry + + " in mapNamedElement_2_STE but found: " + steRegisteredWithFrom); + // repair map: + state.steCache.mapNamedElement_2_STE.remove(from); + state.steCache.mapNamedElement_2_STE.put(to, entry); + } + + /***/ + static public SymbolTableEntryOriginal findSymbolTableEntryForNamedImport(TranspilerState state, + NamedImportSpecifier importspec) { + + for (SymbolTableEntryOriginal steo : state.steCache.mapOriginal.values()) { + if (steo.getImportSpecifier() == importspec) { + return steo; + } + } + return null; + } + + /** + * This method defaults to {@link #findSymbolTableEntryForNamedImport(TranspilerState, NamedImportSpecifier)}. + */ + static public Collection findSymbolTableEntriesForVersionedTypeImport( + TranspilerState state, NamedImportSpecifier importspec) { + return List.of(findSymbolTableEntryForNamedImport(state, importspec)); + } + + /** + * Records in property {@link TypeReferenceNode_IM#getRewiredReferences() rewiredReferences} that the given type + * reference node refers to the type represented by the given symbol table entry. + */ + static public void recordReferenceToType(TranspilerState state, TypeReferenceNode_IM typeRefNode, + SymbolTableEntryOriginal ste) { + + // 1) record the reference to the type represented by 'ste' itself + typeRefNode.addRewiredTarget(ste); + // 2) record the reference to the namespace iff the type represented by 'ste' was imported via a namespace + // import + ImportSpecifier importSpec = ste.getImportSpecifier(); + if (importSpec instanceof NamespaceImportSpecifier) { + ModuleNamespaceVirtualType namespaceType = state.info + .getOriginalDefinedType((NamespaceImportSpecifier) importSpec); + if (namespaceType != null) { + SymbolTableEntryOriginal namespaceSTE = getSymbolTableEntryOriginal(state, namespaceType, false); + if (namespaceSTE != null) { + typeRefNode.addRewiredTarget(namespaceSTE); + } + } + } + } + + /***/ + static public void rename(SymbolTableEntry entry, String name) { + if (entry instanceof SymbolTableEntryInternal) { + throw new UnsupportedOperationException("cannot rename internal STEs " + entry); + + } else if (entry instanceof SymbolTableEntryIMOnly) { + entry.setName(name); + + } else if (entry instanceof SymbolTableEntryOriginal) { + + entry.setName(name); + + // should do something like the following: + // (not possible at the moment, because NamedElement does not have a setter for property 'name') + // entry.elementsOfThisName.forEach[it.name=newName]; + + if (((SymbolTableEntryOriginal) entry).getImportSpecifier() != null) { + throw new UnsupportedOperationException( + "renaming of symbol table entries not tested yet for imported elements!"); + } + // should be something like the following: + // switch(impSpec) { + // NamedImportSpecifier: if(impSpec.alias!=null) impSpec.alias = newName + // NamespaceImportSpecifier: if(impSpec.alias!=null) impSpec.alias = newName + // } + } else { + throw new UnsupportedOperationException( + "Rename request for SymboltableEntries of unkown type : " + entry); + } + } + +} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/SymbolTableManagement.xtend b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/SymbolTableManagement.xtend deleted file mode 100644 index 744a9e1ae8..0000000000 --- a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/SymbolTableManagement.xtend +++ /dev/null @@ -1,451 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler - -import java.util.Collection -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EReference -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.NamedElement -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.transpiler.im.ImFactory -import org.eclipse.n4js.transpiler.im.ImPackage -import org.eclipse.n4js.transpiler.im.ReferencingElement_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.transpiler.im.SymbolTableEntryIMOnly -import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.transpiler.im.TypeReferenceNode_IM -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.NameAndAccess -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TModule - -/** - */ -class SymbolTableManagement { - - /** - * Create a symbol table entry for a given original target (either a TModule element OR a variable in the original - * AST, in case of non-exported top-level variables, local variables, formal parameters, etc.). - */ - def public static SymbolTableEntryOriginal createSymbolTableEntryOriginal(TranspilerState state, IdentifiableElement originalTarget) { - if(originalTarget===null) { - throw new IllegalArgumentException("original target may not be null"); - } - val newEntry = ImFactory.eINSTANCE.createSymbolTableEntryOriginal; - newEntry.name = originalTarget.name; - newEntry.originalTarget = originalTarget; - if(originalTarget instanceof NamedElement) { - newEntry.elementsOfThisName += originalTarget as NamedElement; - } - - // if a namespace import exists for the module containing 'originalTarget', we use it for this new STE - newEntry.importSpecifier = getExistingNamespaceImportSpecifierForModule(state, originalTarget.containingModule); - - state.addOriginal(newEntry) - - return newEntry; - } - - def private static NamespaceImportSpecifier getExistingNamespaceImportSpecifierForModule(TranspilerState state, TModule module) { - if (module !== null) { - val importSpec = state.steCache.mapImportedModule_2_STE.get(module)?.importSpecifier; - if (importSpec instanceof NamespaceImportSpecifier) { - return importSpec; - } - } - return null; - } - - /** add a {@link SymbolTableEntryOriginal} */ - def static public void addOriginal(TranspilerState state, SymbolTableEntryOriginal steOriginal) { - addOriginal ( state.steCache, steOriginal ) ; - } - - /** NOTE: Internal usage in preparation step, please call {@link #addOriginal(TranspilerState,SymbolTableEntryOriginal)} */ - def static public void addOriginal(TranspilerState.STECache steCache, SymbolTableEntryOriginal steOriginal) { - - val SymbolTableEntryOriginal old = steCache.mapOriginal.put(steOriginal.getOriginalTarget(), steOriginal); - if (old !== null) - throw new IllegalStateException( - "It is not allowed to register more then one STEOriginal for the same original Target. Already had: " - + old); - val originalTarget = steOriginal.originalTarget; - if (originalTarget instanceof ModuleNamespaceVirtualType) { - val namespaceModule = originalTarget.module; - if (namespaceModule !== null) { - steCache.mapImportedModule_2_STE.put(namespaceModule, steOriginal); - } - } - steCache.im.getSymbolTable().getEntries().add(steOriginal); - steCache.inverseMap(steOriginal); - } - - - def static private void inverseMap(TranspilerState.STECache steManager, SymbolTableEntryOriginal steOriginal) { - // register elements of this name. - steOriginal.getElementsOfThisName().forEach[ele | steManager.mapNamedElement_2_STE.put(ele, steOriginal)]; - } - - - /** - * Create a symbol table entry for an element in the intermediate model. This should only be used if the element - * in the IM does not have a corresponding original target (either a TModule element or an element - * in the original AST, in case of non-exported variables), for example because it was newly created by an AST - * transformation. - */ - def public static SymbolTableEntryIMOnly createSymbolTableEntryIMOnly(TranspilerState state, NamedElement elementInIM) { - if(elementInIM===null) { - throw new IllegalArgumentException("element in intermediate model may not be null"); - } - if(elementInIM.name===null) { - throw new IllegalArgumentException("element in intermediate model may not be unnamed when creating a symbol table entry for it"); - } - val newEntry = ImFactory.eINSTANCE.createSymbolTableEntryIMOnly; - newEntry.name = elementInIM.name; - newEntry.elementsOfThisName += elementInIM; - state.addIMOnly( newEntry ); - return newEntry; - } - - /** - * Create an internal symbol table entry. They are special and should be used only in rare exception cases. - * See {@link SymbolTableEntryInternal} for details. - */ - def public static SymbolTableEntryInternal createSymbolTableEntryInternal(TranspilerState state, String name) { - if(name===null) { - throw new IllegalArgumentException("name may not be null"); - } - val newEntry = ImFactory.eINSTANCE.createSymbolTableEntryInternal; - newEntry.name = name; - state.addInteral( newEntry ); - return newEntry; - } - - - - /** add a {@link SymbolTableEntryInternal} */ - def private static void addInteral(TranspilerState state, SymbolTableEntryInternal ste) { - val SymbolTableEntryInternal old = state.steCache.mapInternal.put(ste.getName(), ste); - if (old !== null) - throw new IllegalStateException( - "It is not allowed to put the same SymbolTableEntryInternal twice into the Symboltable " + old); - state.im.getSymbolTable().getEntries().add(ste); - } - - - - /** - * Search an STE by original target and create it if not found. - */ - def public static SymbolTableEntryOriginal getSymbolTableEntryOriginal(TranspilerState state, IdentifiableElement originalTarget, boolean create) { - if(originalTarget===null) { - throw new IllegalArgumentException("original target may not be null"); - } - val existingEntry = state.getSteOriginal(originalTarget); - - if(existingEntry!==null) { - return existingEntry; - } - if(create) { - return createSymbolTableEntryOriginal(state, originalTarget); - } - return null; - } - - /** - * Convenience method for {@link #getSymbolTableEntryOriginal(TranspilerState, IdentifiableElement, boolean}, - * allowing to retrieve the member by name and access from its parent classifier. - */ - def public static SymbolTableEntryOriginal getSymbolTableEntryForMember(TranspilerState state, TClassifier type, - String memberName, boolean writeAccess, boolean staticAccess, boolean create) { - if(type===null || memberName===null || memberName.empty) { - throw new IllegalArgumentException("type may not be null and memberName may not be null or empty"); - } - val m = type.findOwnedMember(memberName, writeAccess, staticAccess); - if(m===null) { - val nameAndAccess = new NameAndAccess(memberName, writeAccess, staticAccess); - throw new IllegalArgumentException("no such member found in given type: " + nameAndAccess); - } - return getSymbolTableEntryOriginal(state, m, create); - } - - /** - * Search an internal STE by name and create it if not found. - */ - def public static SymbolTableEntryInternal getSymbolTableEntryInternal(TranspilerState state, String name, boolean create) { - if(name===null || name.empty) { - throw new IllegalArgumentException("name may not be null or empty"); - } - - val existingEntry = state.getSteInternal(name); - - if(existingEntry!==null) { - return existingEntry; - } - if(create) { - return createSymbolTableEntryInternal(state, name); - } - return null; - } - - /** - * Will look up the STE for the given named element in the IM. If not found and create is set to - * true a {@code SymbolTableEntryIMOnly} is created, otherwise null is returned. - *

- * WARNING: during look up it will find both {@link SymbolTableEntryOriginal}s and {@link SymbolTableEntryIMOnly}s, - * but when creating a new STE, it will always create a {@code SymbolTableEntryIMOnly} which is invalid if there - * exists an original target for the given elementInIM (then a {@link SymbolTableEntryOriginal} would - * have to be created)!. In such a case, this method must not be used.
- * Most of the time, this won't be the case and it is safe to use this method, because all - * {@code SymbolTableEntryOriginal}s will be created up-front during the {@link PreparationStep}; in some special - * cases, however, a new element is introduced into the IM that actually has an original target (so far, static - * polyfills are the only case of this). - */ - def public static SymbolTableEntry findSymbolTableEntryForElement(TranspilerState state, NamedElement elementInIM, boolean create) { - if(elementInIM===null) { - throw new IllegalArgumentException("element in intermediate model may not be null"); - } - val existingEntry = state.byElementsOfThisName(elementInIM); - - if(existingEntry!==null) { - return existingEntry; - } - if(create) { - return createSymbolTableEntryIMOnly(state, elementInIM); - } - return null; - } - - /** - * Search STE for the given name space import. - */ - def public static SymbolTableEntryOriginal findSymbolTableEntryForNamespaceImport(TranspilerState state, NamespaceImportSpecifier importspec) { - // 1. linear version: - // state.im.symbolTable.entries.filter(SymbolTableEntryOriginal) - // .filter[it.importSpecifier === importspec] - // .filter[it.originalTarget instanceof ModuleNamespaceVirtualType] - // .head - - // 2. parallel version: - // return state.im.symbolTable.entries.parallelStream() - // .filter[it instanceof SymbolTableEntryOriginal].map[ it as SymbolTableEntryOriginal] - // .filter[it.importSpecifier === importspec] - // .filter[it.originalTarget instanceof ModuleNamespaceVirtualType] - // .findAny().orElse(null); - - // 3. only the originals: - // Should be safe to use the cache. - return state.steCache.mapOriginal.values.parallelStream() - .filter[it.importSpecifier === importspec] - .filter[it.originalTarget instanceof ModuleNamespaceVirtualType] - .findAny().orElse(null); - } - - - - // let's try to keep this at "package" visibility for now (but there'll probably be special cases when a - // transformation needs to rewire something special by calling this directly) - def /*package*/ static void rewireSymbolTable(TranspilerState state, EObject from, EObject to) { - if(!from.requiresRewiringOfSymbolTable && !to.requiresRewiringOfSymbolTable) { - return; // nothing to rewire! - } - if(from instanceof ReferencingElement_IM && to instanceof ReferencingElement_IM) { - // case 1 - val eRefThatMightPointToOriginal = ImPackage.eINSTANCE.symbolTableEntry_ReferencingElements; - // TODO can be speed up - state.im.symbolTable.entries.parallelStream - .forEach[ - replaceInEReference(it, eRefThatMightPointToOriginal, from, to); - ]; - - } else if(from instanceof ImportSpecifier && to instanceof ImportSpecifier) { - // case 2 - val eRefThatMightPointToOriginal = ImPackage.eINSTANCE.symbolTableEntryOriginal_ImportSpecifier; - // TODO can be speed up - state.im.symbolTable.entries.parallelStream.filter[it instanceof SymbolTableEntryOriginal] - .forEach[ - replaceInEReference(it, eRefThatMightPointToOriginal, from, to); - ]; - - } else if(from instanceof NamedElement && to instanceof NamedElement) { - // case 3 // Most relevant case according to profiler - val eRefThatMightPointToOriginal = ImPackage.eINSTANCE.symbolTableEntry_ElementsOfThisName; - // Slow version: - // state.im.symbolTable.entries_.forEach[ - // replaceInEReference(it, x, from, to); - // ]; - - val steFrom = state.byElementsOfThisName(from as NamedElement); - if( steFrom !== null ) { - replaceInEReference(steFrom, eRefThatMightPointToOriginal, from , to ); - // update STECache: - state.replacedElementOfThisName( steFrom, from as NamedElement, to as NamedElement ) - } - } else { - throw new IllegalArgumentException("rewiring symbol table entries from type " + from.eClass.name + - " to type " + to.eClass.name + " is not supported yet"); - } - } - - def private static boolean requiresRewiringOfSymbolTable(EObject obj) { - return obj instanceof ReferencingElement_IM || obj instanceof ImportSpecifier || obj instanceof NamedElement; - } - - def private static void replaceInEReference(EObject obj, EReference eRef, T original, TN replacement) { - // note: cannot use EcoreUtil#replace() here, because it throws exceptions if original is not in reference! - if(eRef.many) { - val l = obj.eGet(eRef) as EList; - for(idx : 0.. findSymbolTableEntriesForVersionedTypeImport(TranspilerState state, NamedImportSpecifier importspec) { - return #[findSymbolTableEntryForNamedImport(state, importspec)]; - } - - - /** - * Records in property {@link TypeReferenceNode_IM#getRewiredReferences() rewiredReferences} that the given type reference node refers - * to the type represented by the given symbol table entry. - */ - def static public void recordReferenceToType(TranspilerState state, TypeReferenceNode_IM typeRefNode, SymbolTableEntryOriginal ste) { - // 1) record the reference to the type represented by 'ste' itself - typeRefNode.addRewiredTarget(ste); - // 2) record the reference to the namespace iff the type represented by 'ste' was imported via a namespace import - val importSpec = ste.importSpecifier; - if (importSpec instanceof NamespaceImportSpecifier) { - val namespaceType = state.info.getOriginalDefinedType(importSpec); - if (namespaceType !== null) { - val namespaceSTE = getSymbolTableEntryOriginal(state, namespaceType, false); - if (namespaceSTE !== null) { - typeRefNode.addRewiredTarget(namespaceSTE); - } - } - } - } - - - def static public void rename(TranspilerState state, SymbolTableEntry entry, String name) { - - if (entry instanceof SymbolTableEntryInternal) { - throw new UnsupportedOperationException("cannot rename internal STEs " + entry); - - } else if (entry instanceof SymbolTableEntryIMOnly) { - entry.setName(name); - - } else if (entry instanceof SymbolTableEntryOriginal) { - - entry.setName(name); - - // should do something like the following: - // (not possible at the moment, because NamedElement does not have a setter for property 'name') - // entry.elementsOfThisName.forEach[it.name=newName]; - - if (entry.getImportSpecifier() !== null) - throw new UnsupportedOperationException( - "renaming of symbol table entries not tested yet for imported elements!"); - // should be something like the following: - // switch(impSpec) { - // NamedImportSpecifier: if(impSpec.alias!==null) impSpec.alias = newName - // NamespaceImportSpecifier: if(impSpec.alias!==null) impSpec.alias = newName - // } - } else { - throw new UnsupportedOperationException( - "Rename request for SymboltableEntries of unkown type : " + entry); - } - - } -} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerBuilderBlocks.java b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerBuilderBlocks.java new file mode 100644 index 0000000000..1840cbeb79 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerBuilderBlocks.java @@ -0,0 +1,954 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler; + +import static com.google.common.collect.Iterables.toArray; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.isEmpty; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.reduce; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.ListExtensions.map; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.AdditiveExpression; +import org.eclipse.n4js.n4JS.AdditiveOperator; +import org.eclipse.n4js.n4JS.AnnotableN4MemberDeclaration; +import org.eclipse.n4js.n4JS.Annotation; +import org.eclipse.n4js.n4JS.AnnotationList; +import org.eclipse.n4js.n4JS.Argument; +import org.eclipse.n4js.n4JS.ArrayElement; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.ArrayPadding; +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.AssignmentExpression; +import org.eclipse.n4js.n4JS.AssignmentOperator; +import org.eclipse.n4js.n4JS.BinaryLogicalExpression; +import org.eclipse.n4js.n4JS.BinaryLogicalOperator; +import org.eclipse.n4js.n4JS.BindingProperty; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.BooleanLiteral; +import org.eclipse.n4js.n4JS.CommaExpression; +import org.eclipse.n4js.n4JS.ConditionalExpression; +import org.eclipse.n4js.n4JS.DefaultImportSpecifier; +import org.eclipse.n4js.n4JS.EmptyStatement; +import org.eclipse.n4js.n4JS.EqualityExpression; +import org.eclipse.n4js.n4JS.EqualityOperator; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.ExportableElement; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ExpressionStatement; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionDeclaration; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.IfStatement; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.IndexedAccessExpression; +import org.eclipse.n4js.n4JS.IntLiteral; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4EnumLiteral; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4JSFactory; +import org.eclipse.n4js.n4JS.N4MemberAnnotationList; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.N4Modifier; +import org.eclipse.n4js.n4JS.N4SetterDeclaration; +import org.eclipse.n4js.n4JS.N4TypeVariable; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.NewExpression; +import org.eclipse.n4js.n4JS.NullLiteral; +import org.eclipse.n4js.n4JS.NumericLiteral; +import org.eclipse.n4js.n4JS.ObjectBindingPattern; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.ParenExpression; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.PropertyAssignmentAnnotationList; +import org.eclipse.n4js.n4JS.PropertyGetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameKind; +import org.eclipse.n4js.n4JS.PropertyNameOwner; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.RelationalExpression; +import org.eclipse.n4js.n4JS.RelationalOperator; +import org.eclipse.n4js.n4JS.ReturnStatement; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.n4JS.StringLiteral; +import org.eclipse.n4js.n4JS.SuperLiteral; +import org.eclipse.n4js.n4JS.ThisLiteral; +import org.eclipse.n4js.n4JS.ThrowStatement; +import org.eclipse.n4js.n4JS.UnaryExpression; +import org.eclipse.n4js.n4JS.UnaryOperator; +import org.eclipse.n4js.n4JS.VariableBinding; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableDeclarationOrBinding; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.n4JS.VariableStatementKeyword; +import org.eclipse.n4js.n4JS.YieldExpression; +import org.eclipse.n4js.postprocessing.CompileTimeExpressionProcessor; +import org.eclipse.n4js.postprocessing.ComputedNameProcessor; +import org.eclipse.n4js.transpiler.im.IdentifierRef_IM; +import org.eclipse.n4js.transpiler.im.ImFactory; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.Snippet; +import org.eclipse.n4js.transpiler.im.StringLiteralForSTE; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.TypeReferenceNode_IM; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.xtext.xbase.lib.Pair; + +/** + * Builder methods for intermediate elements. + */ +@SuppressWarnings("javadoc") +public class TranspilerBuilderBlocks { + + // ############################################################################################ + // n4js.xcore + + public static ImportDeclaration _ImportDecl(ImportSpecifier... importSpecifiers) { + ImportDeclaration result = N4JSFactory.eINSTANCE.createImportDeclaration(); + result.setModule(null); // must always be null, because we are in the intermediate model + result.getImportSpecifiers().addAll(toList(filterNull(Arrays.asList(importSpecifiers)))); + result.setImportFrom(result.getImportSpecifiers().size() > 0); + return result; + } + + public static NamedImportSpecifier _NamedImportSpecifier(String importedElementName, String alias, + boolean usedInCode) { + + NamedImportSpecifier result = N4JSFactory.eINSTANCE.createNamedImportSpecifier(); + result.setImportedElement(null); // must always be null, because we are in the intermediate model + result.setImportedElementAsText(importedElementName); + result.setAlias(alias); + result.setFlaggedUsedInCode(usedInCode); + return result; + } + + public static NamedImportSpecifier _DefaultImportSpecifier(String importedElementName, boolean usedInCode) { + DefaultImportSpecifier result = N4JSFactory.eINSTANCE.createDefaultImportSpecifier(); + result.setImportedElement(null); // must always be null, because we are in the intermediate model + result.setImportedElementAsText(importedElementName); + result.setFlaggedUsedInCode(usedInCode); + return result; + } + + public static NamespaceImportSpecifier _NamespaceImportSpecifier(String namespaceName, boolean usedInCode) { + NamespaceImportSpecifier result = N4JSFactory.eINSTANCE.createNamespaceImportSpecifier(); + result.setAlias(namespaceName); + result.setFlaggedUsedInCode(usedInCode); + return result; + } + + public static VariableStatement _VariableStatement(VariableDeclaration... varDecls) { + return _VariableStatement(VariableStatementKeyword.VAR, varDecls); + } + + public static VariableStatement _VariableStatement(VariableStatementKeyword keyword, + VariableDeclarationOrBinding... varDecls) { + + VariableStatement result = N4JSFactory.eINSTANCE.createVariableStatement(); + result.setVarStmtKeyword(keyword); + result.getVarDeclsOrBindings().addAll(toList(filterNull(Arrays.asList(varDecls)))); + return result; + } + + public static VariableDeclaration _VariableDeclaration(String name) { + VariableDeclaration result = N4JSFactory.eINSTANCE.createVariableDeclaration(); + result.setName(name); + return result; + } + + public static VariableDeclaration _VariableDeclaration(String name, Expression exp) { + VariableDeclaration result = N4JSFactory.eINSTANCE.createVariableDeclaration(); + result.setName(name); + result.setExpression(exp); + return result; + } + + public static VariableBinding _VariableBinding(Iterable properties, Expression exp) { + ObjectBindingPattern pattern = N4JSFactory.eINSTANCE.createObjectBindingPattern(); + pattern.getProperties().addAll(toList(properties)); + VariableBinding result = N4JSFactory.eINSTANCE.createVariableBinding(); + result.setPattern(pattern); + result.setExpression(exp); + return result; + } + + public static ExportDeclaration _ExportDeclaration(ExportableElement exported) { + ExportDeclaration result = N4JSFactory.eINSTANCE.createExportDeclaration(); + result.setExportedElement(exported); + return result; + } + + public static ReturnStatement _ReturnStmnt() { + ReturnStatement result = N4JSFactory.eINSTANCE.createReturnStatement(); + return result; + } + + public static ReturnStatement _ReturnStmnt(Expression expr) { + ReturnStatement result = N4JSFactory.eINSTANCE.createReturnStatement(); + result.setExpression(expr); + return result; + } + + public static IfStatement _IfStmnt(Expression condition, Statement ifStmnt) { + return _IfStmnt(condition, ifStmnt, null); + } + + public static IfStatement _IfStmnt(Expression condition, Statement ifStmnt, Statement elseStmnt) { + IfStatement result = N4JSFactory.eINSTANCE.createIfStatement(); + result.setExpression(condition); + result.setIfStmt(ifStmnt); + result.setElseStmt(elseStmnt); + return result; + } + + public static ThrowStatement _ThrowStmnt(Expression expression) { + ThrowStatement result = N4JSFactory.eINSTANCE.createThrowStatement(); + result.setExpression(expression); + return result; + } + + public static ConditionalExpression _ConditionalExpr(Expression condition, Expression trueExpr, + Expression falseExpr) { + + ConditionalExpression result = N4JSFactory.eINSTANCE.createConditionalExpression(); + result.setExpression(condition); + result.setTrueExpression(trueExpr); + result.setFalseExpression(falseExpr); + return result; + } + + public static YieldExpression _YieldExpr(Expression expr) { + YieldExpression result = N4JSFactory.eINSTANCE.createYieldExpression(); + result.setExpression(expr); + return result; + } + + public static ParameterizedCallExpression _CallExpr() { + ParameterizedCallExpression result = N4JSFactory.eINSTANCE.createParameterizedCallExpression(); + return result; + } + + public static ParameterizedCallExpression _CallExpr(Expression target, Expression... arguments) { + return _CallExpr(target, false, arguments); + } + + public static ParameterizedCallExpression _CallExpr(Expression target, boolean optionalChaining, + Expression... arguments) { + ParameterizedCallExpression result = N4JSFactory.eINSTANCE.createParameterizedCallExpression(); + result.setTarget(target); + result.setOptionalChaining(optionalChaining); + result.getArguments().addAll(toList(map(filterNull(Arrays.asList(arguments)), arg -> _Argument(arg)))); + return result; + } + + public static Argument _Argument(Expression expression) { + return _Argument(false, expression); + } + + public static Argument _Argument(boolean spread, Expression expression) { + Argument result = N4JSFactory.eINSTANCE.createArgument(); + result.setSpread(spread); + result.setExpression(expression); + return result; + } + + public static ExpressionStatement _ExprStmnt(Expression expr) { + ExpressionStatement result = N4JSFactory.eINSTANCE.createExpressionStatement(); + result.setExpression(expr); + return result; + } + + public static AssignmentExpression _AssignmentExpr() { + AssignmentExpression result = N4JSFactory.eINSTANCE.createAssignmentExpression(); + result.setOp(AssignmentOperator.ASSIGN); + return result; + } + + public static AssignmentExpression _AssignmentExpr(Expression lhs, Expression rhs) { + AssignmentExpression result = N4JSFactory.eINSTANCE.createAssignmentExpression(); + result.setLhs(lhs); + result.setOp(AssignmentOperator.ASSIGN); + result.setRhs(rhs); + return result; + } + + public static ParameterizedPropertyAccessExpression_IM _PropertyAccessExpr() { + ParameterizedPropertyAccessExpression_IM result = ImFactory.eINSTANCE + .createParameterizedPropertyAccessExpression_IM(); + return result; + } + + public static ParameterizedPropertyAccessExpression_IM _PropertyAccessExpr(SymbolTableEntry target, + SymbolTableEntry... properties) { + return _PropertyAccessExpr(_IdentRef(target), properties); + } + + public static ParameterizedPropertyAccessExpression_IM _PropertyAccessExpr(Expression target, + SymbolTableEntry... properties) { + if (properties == null || exists(Arrays.asList(properties), p -> p == null)) { + throw new IllegalArgumentException("none of the properties may be null"); + } + var result = ImFactory.eINSTANCE.createParameterizedPropertyAccessExpression_IM(); + result.setTarget(target); + if (properties.length > 0) { + result.setRewiredTarget(properties[0]); + for (int idx = 1; idx < properties.length; idx++) { + ParameterizedPropertyAccessExpression_IM newResult = ImFactory.eINSTANCE + .createParameterizedPropertyAccessExpression_IM(); + newResult.setTarget(result); + newResult.setRewiredTarget(properties[idx]); + result = newResult; + } + } + return result; + } + + public static IndexedAccessExpression _IndexAccessExpr() { + return _IndexAccessExpr((Expression) null, null); + } + + public static IndexedAccessExpression _IndexAccessExpr(SymbolTableEntry target, Expression index) { + return _IndexAccessExpr(_IdentRef(target), index); + } + + public static IndexedAccessExpression _IndexAccessExpr(Expression target, Expression index) { + IndexedAccessExpression result = N4JSFactory.eINSTANCE.createIndexedAccessExpression(); + result.setTarget(target); + result.setIndex(index); + return result; + } + + public static NewExpression _NewExpr(Expression callee, Expression... arguments) { + NewExpression result = N4JSFactory.eINSTANCE.createNewExpression(); + result.setCallee(callee); + result.setWithArgs(!isEmpty(filterNull(Arrays.asList(arguments)))); + result.getArguments().addAll(toList(map(filterNull(Arrays.asList(arguments)), a -> _Argument(a)))); + return result; + } + + public static RelationalExpression _RelationalExpr(Expression lhs, RelationalOperator op, Expression rhs) { + RelationalExpression result = N4JSFactory.eINSTANCE.createRelationalExpression(); + result.setLhs(lhs); + result.setOp(op); + result.setRhs(rhs); + return result; + } + + public static EqualityExpression _EqualityExpr(Expression lhs, EqualityOperator op, Expression rhs) { + EqualityExpression result = N4JSFactory.eINSTANCE.createEqualityExpression(); + result.setLhs(lhs); + result.setOp(op); + result.setRhs(rhs); + return result; + } + + public static UnaryExpression _NOT(Expression expr) { + return _UnaryExpr(UnaryOperator.NOT, expr); + } + + public static Expression _OR(Expression... operands) { + return reduce(Arrays.asList(operands), (op1, op2) -> _BinaryLogicalExpr(op1, BinaryLogicalOperator.OR, op2)); + } + + public static Expression _AND(Expression... operands) { + return reduce(Arrays.asList(operands), (op1, op2) -> _BinaryLogicalExpr(op1, BinaryLogicalOperator.AND, op2)); + } + + public static UnaryExpression _Void0() { + return _Void(_NumericLiteral(0)); + } + + public static UnaryExpression _Void(Expression expr) { + return _UnaryExpr(UnaryOperator.VOID, expr); + } + + public static UnaryExpression _UnaryExpr(UnaryOperator op, Expression expr) { + UnaryExpression result = N4JSFactory.eINSTANCE.createUnaryExpression(); + result.setOp(op); + result.setExpression(expr); + return result; + } + + public static BinaryLogicalExpression _BinaryLogicalExpr(Expression lhs, BinaryLogicalOperator op, Expression rhs) { + BinaryLogicalExpression result = N4JSFactory.eINSTANCE.createBinaryLogicalExpression(); + result.setLhs(lhs); + result.setOp(op); + result.setRhs(rhs); + return result; + } + + public static CommaExpression _CommaExpression(Expression... expressions) { + CommaExpression result = N4JSFactory.eINSTANCE.createCommaExpression(); + result.getExprs().addAll(Arrays.asList(expressions)); + return result; + } + + public static AdditiveExpression _AdditiveExpression(Expression lhs, AdditiveOperator op, Expression rhs) { + AdditiveExpression result = N4JSFactory.eINSTANCE.createAdditiveExpression(); + result.setLhs(lhs); + result.setOp(op); + result.setRhs(rhs); + return result; + } + + public static AdditiveExpression _AdditiveExpression(AdditiveOperator op, Expression... operands) { + if (operands == null || exists(Arrays.asList(operands), e -> e == null)) { + throw new IllegalArgumentException("none of the operands may be null"); + } + if (operands.length < 2) { + throw new IllegalArgumentException("need at least two operands"); + } + var result = N4JSFactory.eINSTANCE.createAdditiveExpression(); + result.setLhs(operands[0]); + result.setOp(op); + result.setRhs(operands[1]); + for (int idx = 2; idx < operands.length; idx++) { + AdditiveExpression newResult = N4JSFactory.eINSTANCE.createAdditiveExpression(); + newResult.setLhs(result); + newResult.setOp(op); + newResult.setRhs(operands[idx]); + result = newResult; + } + return result; + } + + public static ObjectLiteral _ObjLit() { // required to resolve the ambiguity between the other two methods + return _ObjLit((PropertyAssignment[]) null); + } + + /** + * Convenience method for creating object literals that only contain {@link PropertyNameValuePair}s. It is legal to + * pass in one or more null values (they will be ignored). + */ + @SuppressWarnings("unchecked") + public static ObjectLiteral _ObjLit(Pair... nameValuePairs) { + return _ObjLit(toArray(map(filterNull(Arrays.asList(nameValuePairs)), + p -> _PropertyNameValuePair(p.getKey(), p.getValue())), PropertyNameValuePair.class)); + } + + public static ObjectLiteral _ObjLit(PropertyAssignment... pas) { + ObjectLiteral result = N4JSFactory.eINSTANCE.createObjectLiteral(); + if (pas != null) { + result.getPropertyAssignments().addAll(toList(filterNull(Arrays.asList(pas)))); + } + return result; + } + + public static PropertyNameValuePair _PropertyNameValuePair(String name, Expression value) { + return _PropertyNameValuePair(_LiteralOrComputedPropertyName(name), value); + } + + public static PropertyNameValuePair _PropertyNameValuePair(LiteralOrComputedPropertyName name, Expression value) { + PropertyNameValuePair result = N4JSFactory.eINSTANCE.createPropertyNameValuePair(); + result.setDeclaredName(name); + result.setExpression(value); + return result; + } + + public static PropertyGetterDeclaration _PropertyGetterDecl(String name, Statement... stmnts) { + PropertyGetterDeclaration result = N4JSFactory.eINSTANCE.createPropertyGetterDeclaration(); + result.setDeclaredName(_LiteralOrComputedPropertyName(name)); + result.setBody(_Block(stmnts)); + return result; + } + + public static ArrayLiteral _ArrLit() { // required to resolve the ambiguity between the other two methods + return _ArrLit((ArrayElement[]) null); + } + + public static ArrayLiteral _ArrLit(Expression... elements) { + return _ArrLit( + toArray(map(filterNull(Arrays.asList(elements)), e -> _ArrayElement(e)), ArrayElement.class)); + } + + public static ArrayLiteral _ArrLit(ArrayElement... elements) { + ArrayLiteral result = N4JSFactory.eINSTANCE.createArrayLiteral(); + if (elements != null) { + result.getElements().addAll(toList(filterNull(Arrays.asList(elements)))); + } + return result; + } + + public static ArrayElement _ArrayElement(Expression expression) { + return _ArrayElement(false, expression); + } + + public static ArrayElement _ArrayElement(boolean spread, Expression expression) { + ArrayElement result = N4JSFactory.eINSTANCE.createArrayElement(); + result.setSpread(spread); + result.setExpression(expression); + return result; + } + + public static ArrayPadding _ArrayPadding() { + ArrayPadding result = N4JSFactory.eINSTANCE.createArrayPadding(); + return result; + } + + public static FunctionDeclaration _FunDecl(String name, Statement... statements) { + return _FunDecl(name, new FormalParameter[0], statements); + } + + public static FunctionDeclaration _FunDecl(String name, FormalParameter[] fpars, Statement... statements) { + FunctionDeclaration result = N4JSFactory.eINSTANCE.createFunctionDeclaration(); + result.setName(name); + result.getFpars().addAll(Arrays.asList(fpars)); + result.setBody(_Block(statements)); + return result; + } + + public static FunctionExpression _FunExpr(boolean async, Statement... statements) { + return _FunExpr(async, null, new FormalParameter[0], statements); + } + + public static FunctionExpression _FunExpr(boolean async, String name, Statement... statements) { + return _FunExpr(async, name, new FormalParameter[0], statements); + } + + public static FunctionExpression _FunExpr(boolean async, String name, FormalParameter... formalParams) { + return _FunExpr(async, name, formalParams, new Statement[0]); + } + + public static FunctionExpression _FunExpr(boolean async, String name, FormalParameter[] fpars, + Statement... statements) { + if (statements != null && statements.length == 1 && statements[0] instanceof Block) { + // safe guard: in case complex EMF inheritance hierarchy causes wrong overload to be invoked + return _FunExprWithBlock(async, name, fpars, (Block) statements[0]); + } + FunctionExpression result = N4JSFactory.eINSTANCE.createFunctionExpression(); + result.setDeclaredAsync(async); + result.setName(name); + result.getFpars().addAll(Arrays.asList(fpars)); + result.setBody(_Block(statements)); + return result; + } + + public static FormalParameter _FormalParameter(String name) { + FormalParameter result = N4JSFactory.eINSTANCE.createFormalParameter(); + result.setName(name); + return result; + } + + public static FunctionExpression _FunExpr(boolean async, String name, FormalParameter[] fpars, Block block) { + return _FunExprWithBlock(async, name, fpars, block); + } + + private static FunctionExpression _FunExprWithBlock(boolean async, String name, FormalParameter[] fpars, + Block block) { + FunctionExpression result = N4JSFactory.eINSTANCE.createFunctionExpression(); + result.setDeclaredAsync(async); + result.setName(name); + result.getFpars().addAll(Arrays.asList(fpars)); + result.setBody(block); + return result; + } + + /** Creates a {@link ArrowFunction#isSingleExprImplicitReturn() single-expression arrow function}. */ + public static ArrowFunction _ArrowFunc(boolean async, FormalParameter[] fpars, Expression expression) { + ArrowFunction result = N4JSFactory.eINSTANCE.createArrowFunction(); + result.setDeclaredAsync(async); + result.getFpars().addAll(Arrays.asList(fpars)); + result.setBody(_Block(_ExprStmnt(expression))); + result.setHasBracesAroundBody(false); + return result; + } + + public static ArrowFunction _ArrowFunc(boolean async, FormalParameter[] fpars, Statement... statements) { + ArrowFunction result = N4JSFactory.eINSTANCE.createArrowFunction(); + result.setDeclaredAsync(async); + result.getFpars().addAll(Arrays.asList(fpars)); + result.setBody(_Block(statements)); + result.setHasBracesAroundBody(true); + return result; + } + + public static N4MemberDeclaration _N4MemberDecl(TMember template, Statement... statements) { + if (template instanceof TField && statements.length > 0) { + throw new IllegalArgumentException("fields cannot have statements"); + } + PropertyNameOwner result; + if (template instanceof TField) { + result = N4JSFactory.eINSTANCE.createN4FieldDeclaration(); + } else if (template instanceof TGetter) { + result = N4JSFactory.eINSTANCE.createN4GetterDeclaration(); + } else if (template instanceof TSetter) { + result = N4JSFactory.eINSTANCE.createN4SetterDeclaration(); + } else if (template instanceof TMethod) { + result = N4JSFactory.eINSTANCE.createN4MethodDeclaration(); + } else { + throw new IllegalArgumentException("unsupported subtype of TMember: " + template.eClass().getName()); + } + + AnnotableN4MemberDeclaration resultAsMD = (AnnotableN4MemberDeclaration) result; + + // basic properties + result.setDeclaredName(_LiteralOrComputedPropertyName(template.getName())); + // body + if (result instanceof FunctionOrFieldAccessor) { + ((FunctionOrFieldAccessor) result).setBody(_Block( + toArray(filterNull(Arrays.asList(statements)), Statement.class))); + } + // formal parameters + if (template instanceof TSetter) { + String fparName = "value"; + if (((TSetter) template).getFpar() != null) { + fparName = ((TSetter) template).getFpar().getName(); + } + ((N4SetterDeclaration) result).setFpar(_Fpar(fparName)); + } + if (template instanceof TMethod) { + TMethod tMethod = (TMethod) template; + ((N4MethodDeclaration) result).getFpars().addAll( + toList(map(tMethod.getFpars(), fpar -> _Fpar(fpar.getName())))); + ((N4MethodDeclaration) result).setDeclaredAsync(tMethod.isDeclaredAsync()); + } + // static / non-static + if (template.isStatic()) { + resultAsMD.getDeclaredModifiers().add(N4Modifier.STATIC); + } + // access modifiers + switch (template.getMemberAccessModifier()) { + case PUBLIC: + resultAsMD.getDeclaredModifiers().add(N4Modifier.PUBLIC); + break; + case PUBLIC_INTERNAL: + resultAsMD.getDeclaredModifiers().add(N4Modifier.PUBLIC); + getOrCreateMemberAnnotationList(resultAsMD).getAnnotations() + .add(_Annotation(AnnotationDefinition.INTERNAL)); + break; + case PROTECTED: + resultAsMD.getDeclaredModifiers().add(N4Modifier.PROTECTED); + break; + case PROTECTED_INTERNAL: + resultAsMD.getDeclaredModifiers().add(N4Modifier.PROTECTED); + getOrCreateMemberAnnotationList(resultAsMD).getAnnotations() + .add(_Annotation(AnnotationDefinition.INTERNAL)); + break; + case PROJECT: + resultAsMD.getDeclaredModifiers().add(N4Modifier.PROJECT); + break; + case PRIVATE: + resultAsMD.getDeclaredModifiers().add(N4Modifier.PRIVATE); + break; + case UNDEFINED: { + /* NOP */} + } + + return resultAsMD; + } + + private static N4MemberAnnotationList getOrCreateMemberAnnotationList(AnnotableN4MemberDeclaration memberDecl) { + N4MemberAnnotationList annList = memberDecl.getAnnotationList(); + if (annList == null) { + annList = N4JSFactory.eINSTANCE.createN4MemberAnnotationList(); + memberDecl.setAnnotationList(annList); + } + return annList; + } + + public static N4FieldDeclaration _N4FieldDecl(boolean isStatic, String declaredName, Expression initExpr) { + return _N4FieldDecl(isStatic, _LiteralOrComputedPropertyName(declaredName), initExpr); + } + + public static N4FieldDeclaration _N4FieldDecl(boolean isStatic, LiteralOrComputedPropertyName declaredName, + Expression initExpr) { + N4FieldDeclaration result = N4JSFactory.eINSTANCE.createN4FieldDeclaration(); + if (isStatic) { + result.getDeclaredModifiers().add(N4Modifier.STATIC); + } + result.setDeclaredName(declaredName); + result.setExpression(initExpr); + return result; + } + + public static N4GetterDeclaration _N4GetterDecl(LiteralOrComputedPropertyName declaredName, Block body) { + N4GetterDeclaration result = N4JSFactory.eINSTANCE.createN4GetterDeclaration(); + result.setDeclaredName(declaredName); + result.setBody(body); + return result; + } + + public static N4SetterDeclaration _N4SetterDecl(LiteralOrComputedPropertyName declaredName, FormalParameter fpar, + Block body) { + N4SetterDeclaration result = N4JSFactory.eINSTANCE.createN4SetterDeclaration(); + result.setDeclaredName(declaredName); + result.setFpar(fpar); + result.setBody(body); + return result; + } + + public static N4MethodDeclaration _N4MethodDecl(String name, Statement... statements) { + return _N4MethodDecl(name, new FormalParameter[0], statements); + } + + public static N4MethodDeclaration _N4MethodDecl(String name, FormalParameter[] fpars, Statement... statements) { + return _N4MethodDecl(false, _LiteralOrComputedPropertyName(name), fpars, + _Block(toArray(filterNull(Arrays.asList(statements)), Statement.class))); + } + + public static N4MethodDeclaration _N4MethodDecl(LiteralOrComputedPropertyName declaredName, Block body) { + return _N4MethodDecl(false, declaredName, new FormalParameter[0], body); + } + + public static N4MethodDeclaration _N4MethodDecl(boolean isStatic, LiteralOrComputedPropertyName declaredName, + FormalParameter[] fpars, Block body) { + N4MethodDeclaration result = N4JSFactory.eINSTANCE.createN4MethodDeclaration(); + if (isStatic) { + result.getDeclaredModifiers().add(N4Modifier.STATIC); + } + result.setDeclaredName(declaredName); + result.getFpars().addAll(Arrays.asList(fpars)); + result.setBody(body); + return result; + } + + public static FormalParameter _Fpar() { + return _Fpar(null, false, false); + } + + public static FormalParameter _Fpar(String name) { + return _Fpar(name, false, false); + } + + public static FormalParameter _Fpar(String name, boolean variadic) { + return _Fpar(name, variadic, false); + } + + public static FormalParameter _Fpar(String name, boolean variadic, boolean isSpecFpar) { + FormalParameter result = N4JSFactory.eINSTANCE.createFormalParameter(); + result.setName(name); + result.setVariadic(variadic); + if (isSpecFpar) { + result.getAnnotations().add(_Annotation(AnnotationDefinition.SPEC)); + } + return result; + } + + public static Annotation _Annotation(AnnotationDefinition annDef) { + Annotation result = N4JSFactory.eINSTANCE.createAnnotation(); + result.setName(annDef.name); + return result; + } + + public static AnnotationList _AnnotationList(List annDef) { + AnnotationList result = N4JSFactory.eINSTANCE.createAnnotationList(); + if (annDef != null) + result.getAnnotations().addAll(map(annDef, ad -> _Annotation(ad))); + return result; + } + + public static PropertyAssignmentAnnotationList _PropertyAssignmentAnnotationList(Annotation[] annotations) { + PropertyAssignmentAnnotationList result = N4JSFactory.eINSTANCE.createPropertyAssignmentAnnotationList(); + result.getAnnotations().addAll(Arrays.asList(annotations)); + return result; + } + + public static Block _Block(Statement... statements) { + Block result = N4JSFactory.eINSTANCE.createBlock(); + result.getStatements().addAll(toList(filterNull(Arrays.asList(statements)))); + return result; + } + + public static ParenExpression _Parenthesis(Expression expr) { + ParenExpression result = N4JSFactory.eINSTANCE.createParenExpression(); + result.setExpression(expr); + return result; + } + + public static ParameterizedCallExpression _ParameterizedCallExpression(Expression expr) { + ParameterizedCallExpression result = N4JSFactory.eINSTANCE.createParameterizedCallExpression(); + result.setTarget(expr); + return result; + } + + public static NullLiteral _NULL() { + return N4JSFactory.eINSTANCE.createNullLiteral(); + } + + public static BooleanLiteral _TRUE() { + return _BooleanLiteral(true); + } + + public static BooleanLiteral _FALSE() { + return _BooleanLiteral(false); + } + + public static BooleanLiteral _BooleanLiteral(boolean value) { + BooleanLiteral result = N4JSFactory.eINSTANCE.createBooleanLiteral(); + result.setTrue(value); + return result; + } + + public static NumericLiteral _NumericLiteral(int num) { + return _NumericLiteral(BigDecimal.valueOf(num)); + } + + public static NumericLiteral _NumericLiteral(BigDecimal num) { + NumericLiteral result = N4JSFactory.eINSTANCE.createNumericLiteral(); + result.setValue(num); + return result; + } + + public static StringLiteral _StringLiteral(String s, String rawValue) { + StringLiteral result = _StringLiteral(s); + result.setRawValue(rawValue); + return result; + } + + public static StringLiteral _StringLiteral(String s) { + StringLiteral result = N4JSFactory.eINSTANCE.createStringLiteral(); + result.setValue(s); + return result; + } + + public static StringLiteral _StringLiteralForSTE(SymbolTableEntry symbolTableEntry) { + return _StringLiteralForSTE(symbolTableEntry, false); + } + + public static StringLiteral _StringLiteralForSTE(SymbolTableEntry symbolTableEntry, boolean useExportedName) { + StringLiteralForSTE result = ImFactory.eINSTANCE.createStringLiteralForSTE(); + result.setEntry(symbolTableEntry); + result.setUseExportedName(useExportedName); + return result; + } + + public static IntLiteral _IntLiteral(int i) { + IntLiteral result = N4JSFactory.eINSTANCE.createIntLiteral(); + result.setValue(BigDecimal.valueOf(i)); + return result; + } + + public static ThisLiteral _ThisLiteral() { + ThisLiteral result = N4JSFactory.eINSTANCE.createThisLiteral(); + return result; + } + + public static SuperLiteral _SuperLiteral() { + SuperLiteral result = N4JSFactory.eINSTANCE.createSuperLiteral(); + return result; + } + + public static EmptyStatement _emptyStatement() { + return N4JSFactory.eINSTANCE.createEmptyStatement(); + } + + public static N4EnumDeclaration _EnumDeclaration(String name, List literals) { + N4EnumDeclaration result = N4JSFactory.eINSTANCE.createN4EnumDeclaration(); + result.setName(name); + result.getLiterals().addAll(literals); + return result; + } + + public static N4EnumLiteral _EnumLiteral(String name, String value) { + N4EnumLiteral result = N4JSFactory.eINSTANCE.createN4EnumLiteral(); + result.setName(name); + result.setValueExpression((value != null) ? _StringLiteral(value) : null); + return result; + } + + public static N4ClassDeclaration _N4ClassDeclaration(String name) { + N4ClassDeclaration result = N4JSFactory.eINSTANCE.createN4ClassDeclaration(); + result.setName(name); + return result; + } + + public static N4InterfaceDeclaration _N4InterfaceDeclaration(String name) { + N4InterfaceDeclaration result = N4JSFactory.eINSTANCE.createN4InterfaceDeclaration(); + result.setName(name); + return result; + } + + public static N4TypeVariable _N4TypeVariable(String name, boolean covariant, boolean contravariant) { + N4TypeVariable result = N4JSFactory.eINSTANCE.createN4TypeVariable(); + result.setName(name); + result.setDeclaredCovariant(covariant); + result.setDeclaredContravariant(contravariant); + return result; + } + + public static LiteralOrComputedPropertyName _LiteralOrComputedPropertyName(String name) { + LiteralOrComputedPropertyName result = N4JSFactory.eINSTANCE.createLiteralOrComputedPropertyName(); + result.setKind(PropertyNameKind.STRING); + result.setLiteralName(name); + return result; + } + + /** + * @param computedName + * the string representation of the computed property name. This should be the value that is usually + * computed and set by {@link CompileTimeExpressionProcessor} and {@link ComputedNameProcessor}. + */ + public static LiteralOrComputedPropertyName _LiteralOrComputedPropertyName(Expression nameExpr, + String computedName) { + LiteralOrComputedPropertyName result = N4JSFactory.eINSTANCE.createLiteralOrComputedPropertyName(); + result.setKind(PropertyNameKind.COMPUTED); + result.setExpression(nameExpr); + result.setComputedName(computedName); + return result; + } + + // ############################################################################################ + // IM.xcore + + public static TypeReferenceNode_IM _TypeReferenceNode(TranspilerState state, + TypeRef typeRef) { + TypeReferenceNode_IM result = ImFactory.eINSTANCE.createTypeReferenceNode_IM(); + if (typeRef != null) { + state.info.setOriginalProcessedTypeRef_internal(result, TypeUtils.copyIfContained(typeRef)); + } + return result; + } + + public static IdentifierRef_IM _IdentRef(SymbolTableEntry symbolTableEntry) { + if (symbolTableEntry == null) { + throw new IllegalArgumentException("when creating an IdentifierRef_IM: symbol table entry may not be null"); + } + IdentifierRef_IM result = ImFactory.eINSTANCE.createIdentifierRef_IM(); + result.setRewiredTarget(symbolTableEntry); + return result; + } + + public static SymbolTableEntry _SymbolTableEntry(@SuppressWarnings("unused") String name) { + throw new UnsupportedOperationException( + "do not manually create symbol table entries; use methods #createSymbolTableEntry() or #getSymbolTableEntry() instead"); + } + + public static ExpressionStatement _SnippetAsStmnt(String code) { + return _ExprStmnt(_Snippet(code)); + } + + public static Snippet _Snippet(String codeToEmit) { + Snippet result = ImFactory.eINSTANCE.createSnippet(); + result.setCodeToEmit(codeToEmit); + return result; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerBuilderBlocks.xtend b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerBuilderBlocks.xtend deleted file mode 100644 index 561f6121a2..0000000000 --- a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerBuilderBlocks.xtend +++ /dev/null @@ -1,870 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler - -import java.math.BigDecimal -import java.util.List -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.AdditiveExpression -import org.eclipse.n4js.n4JS.AdditiveOperator -import org.eclipse.n4js.n4JS.AnnotableN4MemberDeclaration -import org.eclipse.n4js.n4JS.Annotation -import org.eclipse.n4js.n4JS.AnnotationList -import org.eclipse.n4js.n4JS.Argument -import org.eclipse.n4js.n4JS.ArrayElement -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.ArrayPadding -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.AssignmentExpression -import org.eclipse.n4js.n4JS.AssignmentOperator -import org.eclipse.n4js.n4JS.BinaryLogicalExpression -import org.eclipse.n4js.n4JS.BinaryLogicalOperator -import org.eclipse.n4js.n4JS.BindingProperty -import org.eclipse.n4js.n4JS.Block -import org.eclipse.n4js.n4JS.BooleanLiteral -import org.eclipse.n4js.n4JS.CommaExpression -import org.eclipse.n4js.n4JS.ConditionalExpression -import org.eclipse.n4js.n4JS.EqualityExpression -import org.eclipse.n4js.n4JS.EqualityOperator -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.ExportableElement -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ExpressionStatement -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.n4JS.FunctionDeclaration -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.n4JS.IfStatement -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.IndexedAccessExpression -import org.eclipse.n4js.n4JS.IntLiteral -import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName -import org.eclipse.n4js.n4JS.N4EnumDeclaration -import org.eclipse.n4js.n4JS.N4EnumLiteral -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.n4JS.N4JSFactory -import org.eclipse.n4js.n4JS.N4MemberAnnotationList -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.n4JS.N4Modifier -import org.eclipse.n4js.n4JS.N4SetterDeclaration -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.NewExpression -import org.eclipse.n4js.n4JS.NullLiteral -import org.eclipse.n4js.n4JS.NumericLiteral -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.n4JS.ParenExpression -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.PropertyAssignmentAnnotationList -import org.eclipse.n4js.n4JS.PropertyGetterDeclaration -import org.eclipse.n4js.n4JS.PropertyNameKind -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.RelationalExpression -import org.eclipse.n4js.n4JS.RelationalOperator -import org.eclipse.n4js.n4JS.ReturnStatement -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.n4JS.StringLiteral -import org.eclipse.n4js.n4JS.SuperLiteral -import org.eclipse.n4js.n4JS.ThisLiteral -import org.eclipse.n4js.n4JS.ThrowStatement -import org.eclipse.n4js.n4JS.UnaryExpression -import org.eclipse.n4js.n4JS.UnaryOperator -import org.eclipse.n4js.n4JS.VariableBinding -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableDeclarationOrBinding -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.n4JS.VariableStatementKeyword -import org.eclipse.n4js.n4JS.YieldExpression -import org.eclipse.n4js.postprocessing.CompileTimeExpressionProcessor -import org.eclipse.n4js.postprocessing.ComputedNameProcessor -import org.eclipse.n4js.transpiler.im.IdentifierRef_IM -import org.eclipse.n4js.transpiler.im.ImFactory -import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM -import org.eclipse.n4js.transpiler.im.Snippet -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.transpiler.im.TypeReferenceNode_IM -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TSetter -import org.eclipse.n4js.types.utils.TypeUtils - -/** - * Builder methods for intermediate elements. - */ -public class TranspilerBuilderBlocks -{ - - // ############################################################################################ - // n4js.xcore - - public static def ImportDeclaration _ImportDecl(ImportSpecifier... importSpecifiers) { - val result = N4JSFactory.eINSTANCE.createImportDeclaration; - result.module = null; // must always be null, because we are in the intermediate model - result.importSpecifiers += importSpecifiers.filterNull; - result.importFrom = importSpecifiers.length > 0; - return result; - } - - public static def NamedImportSpecifier _NamedImportSpecifier(String importedElementName, String alias, boolean usedInCode) { - val result = N4JSFactory.eINSTANCE.createNamedImportSpecifier; - result.importedElement = null; // must always be null, because we are in the intermediate model - result.importedElementAsText = importedElementName; - result.alias = alias; - result.flaggedUsedInCode = usedInCode; - return result; - } - - public static def NamedImportSpecifier _DefaultImportSpecifier(String importedElementName, boolean usedInCode) { - val result = N4JSFactory.eINSTANCE.createDefaultImportSpecifier; - result.importedElement = null; // must always be null, because we are in the intermediate model - result.importedElementAsText = importedElementName; - result.flaggedUsedInCode = usedInCode; - return result; - } - - public static def NamespaceImportSpecifier _NamespaceImportSpecifier(String namespaceName, boolean usedInCode) { - val result = N4JSFactory.eINSTANCE.createNamespaceImportSpecifier; - result.alias = namespaceName; - result.flaggedUsedInCode = usedInCode; - return result; - } - - public static def VariableStatement _VariableStatement(VariableDeclaration... varDecls) { - return _VariableStatement(VariableStatementKeyword.VAR, varDecls); - } - - public static def VariableStatement _VariableStatement(VariableStatementKeyword keyword, VariableDeclarationOrBinding... varDecls) { - val result = N4JSFactory.eINSTANCE.createVariableStatement; - result.varStmtKeyword = keyword; - result.varDeclsOrBindings += varDecls.filterNull; - return result; - } - - public static def VariableDeclaration _VariableDeclaration(String name) { - val result = N4JSFactory.eINSTANCE.createVariableDeclaration; - result.name = name; - return result; - } - - public static def VariableDeclaration _VariableDeclaration(String name, Expression exp) { - val result = N4JSFactory.eINSTANCE.createVariableDeclaration; - result.name = name; - result.expression = exp; - return result; - } - - public static def VariableBinding _VariableBinding(Iterable properties, Expression exp) { - val pattern = N4JSFactory.eINSTANCE.createObjectBindingPattern; - pattern.properties += properties; - val result = N4JSFactory.eINSTANCE.createVariableBinding; - result.pattern = pattern; - result.expression = exp; - return result; - } - - public static def ExportDeclaration _ExportDeclaration(ExportableElement exported) { - val result = N4JSFactory.eINSTANCE.createExportDeclaration; - result.exportedElement = exported - return result; - } - - public static def ReturnStatement _ReturnStmnt() { - val result = N4JSFactory.eINSTANCE.createReturnStatement; - return result; - } - - public static def ReturnStatement _ReturnStmnt(Expression expr) { - val result = N4JSFactory.eINSTANCE.createReturnStatement; - result.expression = expr; - return result; - } - - public static def IfStatement _IfStmnt(Expression condition, Statement ifStmnt) { - return _IfStmnt(condition, ifStmnt, null); - } - - public static def IfStatement _IfStmnt(Expression condition, Statement ifStmnt, Statement elseStmnt) { - val result = N4JSFactory.eINSTANCE.createIfStatement; - result.expression = condition; - result.ifStmt = ifStmnt; - result.elseStmt = elseStmnt; - return result; - } - - public static def ThrowStatement _ThrowStmnt(Expression expression) { - val result = N4JSFactory.eINSTANCE.createThrowStatement; - result.expression = expression; - return result; - } - - public static def ConditionalExpression _ConditionalExpr(Expression condition, Expression trueExpr, Expression falseExpr) { - val result = N4JSFactory.eINSTANCE.createConditionalExpression; - result.expression = condition; - result.trueExpression = trueExpr; - result.falseExpression = falseExpr; - return result; - } - - public static def YieldExpression _YieldExpr(Expression expr) { - val result = N4JSFactory.eINSTANCE.createYieldExpression; - result.expression = expr; - return result; - } - - public static def ParameterizedCallExpression _CallExpr() { - val result = N4JSFactory.eINSTANCE.createParameterizedCallExpression; - return result; - } - - public static def ParameterizedCallExpression _CallExpr(Expression target, Expression... arguments) { - return _CallExpr(target, false, arguments); - } - - public static def ParameterizedCallExpression _CallExpr(Expression target, boolean optionalChaining, Expression... arguments) { - val result = N4JSFactory.eINSTANCE.createParameterizedCallExpression; - result.target = target; - result.optionalChaining = optionalChaining; - result.arguments += arguments.filterNull.map[_Argument]; - return result; - } - - public static def Argument _Argument(Expression expression) { - return _Argument(false, expression); - } - public static def Argument _Argument(boolean spread, Expression expression) { - val result = N4JSFactory.eINSTANCE.createArgument; - result.spread = spread; - result.expression = expression; - return result; - } - - public static def ExpressionStatement _ExprStmnt(Expression expr) { - val result = N4JSFactory.eINSTANCE.createExpressionStatement; - result.expression = expr; - return result; - } - - public static def AssignmentExpression _AssignmentExpr() { - val result = N4JSFactory.eINSTANCE.createAssignmentExpression; - result.op = AssignmentOperator.ASSIGN; - return result; - } - - public static def AssignmentExpression _AssignmentExpr(Expression lhs, Expression rhs) { - val result = N4JSFactory.eINSTANCE.createAssignmentExpression; - result.lhs = lhs; - result.op = AssignmentOperator.ASSIGN; - result.rhs = rhs; - return result; - } - - public static def ParameterizedPropertyAccessExpression_IM _PropertyAccessExpr() { - val result = ImFactory.eINSTANCE.createParameterizedPropertyAccessExpression_IM; - return result; - } - - public static def ParameterizedPropertyAccessExpression_IM _PropertyAccessExpr(SymbolTableEntry target, - SymbolTableEntry... properties) { - return _PropertyAccessExpr(_IdentRef(target), properties); - } - - public static def ParameterizedPropertyAccessExpression_IM _PropertyAccessExpr(Expression target, - SymbolTableEntry... properties) { - if(properties===null || properties.exists[it===null]) { - throw new IllegalArgumentException("none of the properties may be null") - } - var result = ImFactory.eINSTANCE.createParameterizedPropertyAccessExpression_IM; - result.target = target; - if(properties.length>0) { - result.rewiredTarget = properties.get(0); - for(idx : 1..null values (they will be ignored). - */ - public static def ObjectLiteral _ObjLit(Pair... nameValuePairs) { - return _ObjLit(nameValuePairs.filterNull.map[_PropertyNameValuePair(key,value)]); - } - - public static def ObjectLiteral _ObjLit(PropertyAssignment... pas) { - val result = N4JSFactory.eINSTANCE.createObjectLiteral; - if(pas!==null) { - result.propertyAssignments += pas.filterNull; - } - return result; - } - - public static def PropertyNameValuePair _PropertyNameValuePair(String name, Expression value) { - return _PropertyNameValuePair(_LiteralOrComputedPropertyName(name), value); - } - public static def PropertyNameValuePair _PropertyNameValuePair(LiteralOrComputedPropertyName name, Expression value) { - val result = N4JSFactory.eINSTANCE.createPropertyNameValuePair; - result.declaredName = name; - result.expression = value; - return result; - } - - public static def PropertyGetterDeclaration _PropertyGetterDecl(String name, Statement... stmnts) { - val result = N4JSFactory.eINSTANCE.createPropertyGetterDeclaration; - result.declaredName = _LiteralOrComputedPropertyName(name); - result.body = _Block(stmnts); - return result; - } - - public static def ArrayLiteral _ArrLit() { // required to resolve the ambiguity between the other two methods - return _ArrLit(null as ArrayElement[]); - } - - public static def ArrayLiteral _ArrLit(Expression... elements) { - return _ArrLit(elements.filterNull.map[_ArrayElement(it)]); - } - - public static def ArrayLiteral _ArrLit(ArrayElement... elements) { - val result = N4JSFactory.eINSTANCE.createArrayLiteral; - if(elements!==null) { - result.elements += elements.filterNull; - } - return result; - } - - public static def ArrayElement _ArrayElement(Expression expression) { - return _ArrayElement(false, expression); - } - public static def ArrayElement _ArrayElement(boolean spread, Expression expression) { - val result = N4JSFactory.eINSTANCE.createArrayElement; - result.spread = spread; - result.expression = expression; - return result; - } - public static def ArrayPadding _ArrayPadding() { - val result = N4JSFactory.eINSTANCE.createArrayPadding; - return result; - } - - public static def FunctionDeclaration _FunDecl(String name, Statement... statements) { - return _FunDecl(name, #[], statements); - } - - public static def FunctionDeclaration _FunDecl(String name, FormalParameter[] fpars, Statement... statements) { - val result = N4JSFactory.eINSTANCE.createFunctionDeclaration; - result.name = name; - result.fpars += fpars; - result.body = _Block(statements); - return result; - } - - public static def FunctionExpression _FunExpr(boolean async, Statement... statements) { - return _FunExpr(async, null, #[], statements); - } - - public static def FunctionExpression _FunExpr(boolean async, String name, Statement... statements) { - return _FunExpr(async, name, #[], statements); - } - - public static def FunctionExpression _FunExpr(boolean async, String name, FormalParameter... formalParams) { - return _FunExpr(async, name, formalParams, #[]); - } - - public static def FunctionExpression _FunExpr(boolean async, String name, FormalParameter[] fpars, Statement... statements) { - if(statements !== null && statements.length===1 && statements.get(0) instanceof Block) { - // safe guard: in case complex EMF inheritance hierarchy causes wrong overload to be invoked - return _FunExprWithBlock(async, name, fpars, statements.get(0) as Block); - } - val result = N4JSFactory.eINSTANCE.createFunctionExpression; - result.declaredAsync = async; - result.name = name; - result.fpars += fpars; - result.body = _Block(statements); - return result; - } - - public static def FormalParameter _FormalParameter(String name) { - val result = N4JSFactory.eINSTANCE.createFormalParameter; - result.name = name; - return result; - } - - public static def FunctionExpression _FunExpr(boolean async, String name, FormalParameter[] fpars, Block block) { - return _FunExprWithBlock(async, name, fpars, block); - } - private static def FunctionExpression _FunExprWithBlock(boolean async, String name, FormalParameter[] fpars, Block block) { - val result = N4JSFactory.eINSTANCE.createFunctionExpression; - result.declaredAsync = async; - result.name = name; - result.fpars += fpars; - result.body = block; - return result; - } - - /** Creates a {@link ArrowFunction#isSingleExprImplicitReturn() single-expression arrow function}. */ - public static def ArrowFunction _ArrowFunc(boolean async, FormalParameter[] fpars, Expression expression) { - val result = N4JSFactory.eINSTANCE.createArrowFunction; - result.declaredAsync = async; - result.fpars += fpars; - result.body = _Block(_ExprStmnt(expression)); - result.hasBracesAroundBody = false; - return result; - } - - public static def ArrowFunction _ArrowFunc(boolean async, FormalParameter[] fpars, Statement... statements) { - val result = N4JSFactory.eINSTANCE.createArrowFunction; - result.declaredAsync = async; - result.fpars += fpars; - result.body = _Block(statements); - result.hasBracesAroundBody = true; - return result; - } - - public static def N4MemberDeclaration _N4MemberDecl(TMember template, Statement... statements) { - if(template instanceof TField && !statements.empty) { - throw new IllegalArgumentException("fields cannot have statements"); - } - val result = switch(template) { - TField: N4JSFactory.eINSTANCE.createN4FieldDeclaration - TGetter: N4JSFactory.eINSTANCE.createN4GetterDeclaration - TSetter: N4JSFactory.eINSTANCE.createN4SetterDeclaration - TMethod: N4JSFactory.eINSTANCE.createN4MethodDeclaration - default: throw new IllegalArgumentException("unsupported subtype of TMember: " + template.eClass.name) - }; - // basic properties - result.declaredName = _LiteralOrComputedPropertyName(template.name); - // body - if(result instanceof FunctionOrFieldAccessor) { - result.body = _Block(statements.filterNull); - } - // formal parameters - if(template instanceof TSetter) { - val fparName = template.fpar?.name ?: "value"; - (result as N4SetterDeclaration).fpar = _Fpar(fparName); - } - if(template instanceof TMethod) { - (result as N4MethodDeclaration).fpars += template.fpars.map[_Fpar(name)]; - (result as N4MethodDeclaration).declaredAsync = template.declaredAsync; - } - // static / non-static - if(template.static) { - result.declaredModifiers += N4Modifier.STATIC; - } - // access modifiers - switch(template.memberAccessModifier) { - case PUBLIC: result.declaredModifiers += N4Modifier.PUBLIC - case PUBLIC_INTERNAL: { - result.declaredModifiers += N4Modifier.PUBLIC; - result.getOrCreateMemberAnnotationList.annotations += _Annotation(AnnotationDefinition.INTERNAL); - } - case PROTECTED: result.declaredModifiers += N4Modifier.PROTECTED - case PROTECTED_INTERNAL: { - result.declaredModifiers += N4Modifier.PROTECTED; - result.getOrCreateMemberAnnotationList.annotations += _Annotation(AnnotationDefinition.INTERNAL); - } - case PROJECT: result.declaredModifiers += N4Modifier.PROJECT - case PRIVATE: result.declaredModifiers += N4Modifier.PRIVATE - case UNDEFINED: {/* NOP */} - } - return result; - } - private static def N4MemberAnnotationList getOrCreateMemberAnnotationList(AnnotableN4MemberDeclaration memberDecl) { - var annList = memberDecl.annotationList; - if(annList===null) { - annList = N4JSFactory.eINSTANCE.createN4MemberAnnotationList; - memberDecl.annotationList = annList; - } - return annList; - } - - public static def N4FieldDeclaration _N4FieldDecl(boolean isStatic, String declaredName, Expression initExpr) { - return _N4FieldDecl(isStatic, _LiteralOrComputedPropertyName(declaredName), initExpr); - } - - public static def N4FieldDeclaration _N4FieldDecl(boolean isStatic, LiteralOrComputedPropertyName declaredName, Expression initExpr) { - val result = N4JSFactory.eINSTANCE.createN4FieldDeclaration; - if (isStatic) { - result.declaredModifiers += N4Modifier.STATIC; - } - result.declaredName = declaredName; - result.expression = initExpr; - return result; - } - - public static def N4GetterDeclaration _N4GetterDecl(LiteralOrComputedPropertyName declaredName, Block body) { - val result = N4JSFactory.eINSTANCE.createN4GetterDeclaration; - result.declaredName = declaredName; - result.body = body; - return result; - } - - public static def N4SetterDeclaration _N4SetterDecl(LiteralOrComputedPropertyName declaredName, FormalParameter fpar, Block body) { - val result = N4JSFactory.eINSTANCE.createN4SetterDeclaration; - result.declaredName = declaredName; - result.fpar = fpar; - result.body = body; - return result; - } - - public static def N4MethodDeclaration _N4MethodDecl(String name, Statement... statements) { - return _N4MethodDecl(name, #[], statements); - } - public static def N4MethodDeclaration _N4MethodDecl(String name, FormalParameter[] fpars, Statement... statements) { - return _N4MethodDecl(false, _LiteralOrComputedPropertyName(name), fpars, _Block(statements.filterNull)); - } - public static def N4MethodDeclaration _N4MethodDecl(LiteralOrComputedPropertyName declaredName, Block body) { - return _N4MethodDecl(false, declaredName, #[], body); - } - public static def N4MethodDeclaration _N4MethodDecl(boolean isStatic, LiteralOrComputedPropertyName declaredName, FormalParameter[] fpars, Block body) { - val result = N4JSFactory.eINSTANCE.createN4MethodDeclaration; - if (isStatic) { - result.declaredModifiers += N4Modifier.STATIC - } - result.declaredName = declaredName; - result.fpars += fpars; - result.body = body; - return result; - } - - public static def FormalParameter _Fpar() { - return _Fpar(null, false, false); - } - public static def FormalParameter _Fpar(String name) { - return _Fpar(name, false, false); - } - public static def FormalParameter _Fpar(String name, boolean variadic) { - return _Fpar(name, variadic, false); - } - public static def FormalParameter _Fpar(String name, boolean variadic, boolean isSpecFpar) { - val result = N4JSFactory.eINSTANCE.createFormalParameter; - result.name = name; - result.variadic = variadic; - if(isSpecFpar) { - result.annotations += _Annotation(AnnotationDefinition.SPEC); - } - return result; - } - - public static def Annotation _Annotation(AnnotationDefinition annDef) { - val result = N4JSFactory.eINSTANCE.createAnnotation; - result.name = annDef.name; - return result; - } - - public static def AnnotationList _AnnotationList(List annDef) { - val result = N4JSFactory.eINSTANCE.createAnnotationList; - if( annDef !== null ) - result.annotations += annDef.map[ _Annotation(it) ]; - return result; - } - - public static def PropertyAssignmentAnnotationList _PropertyAssignmentAnnotationList(Annotation[] annotations) { - val result = N4JSFactory.eINSTANCE.createPropertyAssignmentAnnotationList; - result.annotations += annotations; - return result; - } - - public static def Block _Block(Statement... statements) { - val result = N4JSFactory.eINSTANCE.createBlock; - result.statements += statements.filterNull; - return result; - } - - public static def ParenExpression _Parenthesis(Expression expr) { - val result = N4JSFactory.eINSTANCE.createParenExpression; - result.expression = expr; - return result; - } - - public static def ParameterizedCallExpression _ParameterizedCallExpression(Expression expr) { - val result = N4JSFactory.eINSTANCE.createParameterizedCallExpression; - result.target = expr; - return result; - } - - public static def NullLiteral _NULL() { - return N4JSFactory.eINSTANCE.createNullLiteral; - } - - public static def BooleanLiteral _TRUE() { - return _BooleanLiteral(true); - } - public static def BooleanLiteral _FALSE() { - return _BooleanLiteral(false); - } - public static def BooleanLiteral _BooleanLiteral(boolean value) { - val result = N4JSFactory.eINSTANCE.createBooleanLiteral; - result.^true = value; - return result; - } - - public static def NumericLiteral _NumericLiteral(int num) { - return _NumericLiteral(BigDecimal.valueOf(num)); - } - - public static def NumericLiteral _NumericLiteral(BigDecimal num) { - val result = N4JSFactory.eINSTANCE.createNumericLiteral; - result.value = num; - return result; - } - - public static def StringLiteral _StringLiteral(String s, String rawValue) { - val result = _StringLiteral(s); - result.rawValue = rawValue; - return result; - } - - public static def StringLiteral _StringLiteral(String s) { - val result = N4JSFactory.eINSTANCE.createStringLiteral; - result.value = s; - return result; - } - - public static def StringLiteral _StringLiteralForSTE(SymbolTableEntry symbolTableEntry) { - return _StringLiteralForSTE(symbolTableEntry, false); - } - - public static def StringLiteral _StringLiteralForSTE(SymbolTableEntry symbolTableEntry, boolean useExportedName) { - val result = ImFactory.eINSTANCE.createStringLiteralForSTE; - result.entry = symbolTableEntry; - result.useExportedName = useExportedName; - return result; - } - - public static def IntLiteral _IntLiteral(int i) { - val result = N4JSFactory.eINSTANCE.createIntLiteral; - result.value = BigDecimal.valueOf(i); - return result; - } - - public static def ThisLiteral _ThisLiteral() { - val result = N4JSFactory.eINSTANCE.createThisLiteral; - return result; - } - - public static def SuperLiteral _SuperLiteral() { - val result = N4JSFactory.eINSTANCE.createSuperLiteral; - return result; - } - - public static def _emptyStatement() { - return N4JSFactory.eINSTANCE.createEmptyStatement - } - - public static def N4EnumDeclaration _EnumDeclaration(String name, List literals) { - val result = N4JSFactory.eINSTANCE.createN4EnumDeclaration; - result.name = name; - result.literals += literals; - return result; - } - - public static def _EnumLiteral(String name, String value) { - val result = N4JSFactory.eINSTANCE.createN4EnumLiteral; - result.name = name; - result.valueExpression = if (value !== null) _StringLiteral(value); - return result; - } - - public static def _N4ClassDeclaration(String name){ - val result = N4JSFactory.eINSTANCE.createN4ClassDeclaration; - result.name = name; - return result; - } - - public static def _N4InterfaceDeclaration(String name){ - val result = N4JSFactory.eINSTANCE.createN4InterfaceDeclaration; - result.name = name; - return result; - } - - public static def _N4TypeVariable(String name, boolean covariant, boolean contravariant) { - val result = N4JSFactory.eINSTANCE.createN4TypeVariable; - result.name = name; - result.declaredCovariant = covariant; - result.declaredContravariant = contravariant; - return result; - } - - public static def _LiteralOrComputedPropertyName(String name) { - val result = N4JSFactory.eINSTANCE.createLiteralOrComputedPropertyName; - result.kind = PropertyNameKind.STRING; - result.literalName = name; - return result; - } - - /** - * @param computedName the string representation of the computed property name. This should be the value that - * is usually computed and set by {@link CompileTimeExpressionProcessor} and {@link ComputedNameProcessor}. - */ - public static def _LiteralOrComputedPropertyName(Expression nameExpr, String computedName) { - val result = N4JSFactory.eINSTANCE.createLiteralOrComputedPropertyName; - result.kind = PropertyNameKind.COMPUTED; - result.expression = nameExpr; - result.computedName = computedName; - return result; - } - - // ############################################################################################ - // IM.xcore - - public static def TypeReferenceNode_IM _TypeReferenceNode(TranspilerState state, TypeRef typeRef) { - val TypeReferenceNode_IM result = ImFactory.eINSTANCE.createTypeReferenceNode_IM(); - if (typeRef !== null) { - state.info.setOriginalProcessedTypeRef_internal(result, TypeUtils.copyIfContained(typeRef)); - } - return result; - } - - public static def IdentifierRef_IM _IdentRef(SymbolTableEntry symbolTableEntry) { - if(symbolTableEntry===null) { - throw new IllegalArgumentException("when creating an IdentifierRef_IM: symbol table entry may not be null"); - } - val result = ImFactory.eINSTANCE.createIdentifierRef_IM; - result.rewiredTarget = symbolTableEntry; - return result; - } - - public static def SymbolTableEntry _SymbolTableEntry(String name) { - throw new UnsupportedOperationException("do not manually create symbol table entries; use methods #createSymbolTableEntry() or #getSymbolTableEntry() instead"); - } - - public static def ExpressionStatement _SnippetAsStmnt(String code) { - return _ExprStmnt(_Snippet(code)); - } - - public static def Snippet _Snippet(String codeToEmit) { - val result = ImFactory.eINSTANCE.createSnippet; - result.codeToEmit = codeToEmit; - return result; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerComponent.java b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerComponent.java index 85f9363a4f..746941abf3 100644 --- a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerComponent.java +++ b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerComponent.java @@ -12,6 +12,7 @@ import java.util.Collection; import java.util.List; +import java.util.function.Consumer; import org.eclipse.emf.ecore.EObject; import org.eclipse.n4js.n4JS.ArrowFunction; @@ -49,7 +50,6 @@ import org.eclipse.n4js.ts.types.TClassifier; import org.eclipse.n4js.ts.types.TModule; import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; -import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -130,7 +130,7 @@ protected void setTarget(ParameterizedPropertyAccessExpression_IM accExpr, Expre @SuppressWarnings("javadoc") protected void addArgument(ParameterizedCallExpression callExpr, int index, Expression newArgument) { - TranspilerStateOperations.addArgument(state, callExpr, index, newArgument); + TranspilerStateOperations.addArgument(callExpr, index, newArgument); } @SuppressWarnings("javadoc") @@ -207,19 +207,18 @@ protected void replaceAndRelocate(FormalParameter fPar_to_remove, VariableStatem @SuppressWarnings("javadoc") protected void wrapExistingExpression(T exprToWrap, Expression outerExpr_without_exprToWrap, - Procedure1 inserterFunction) { - TranspilerStateOperations.wrapExistingExpression(state, exprToWrap, outerExpr_without_exprToWrap, - inserterFunction); + Consumer inserterFunction) { + TranspilerStateOperations.wrapExistingExpression(exprToWrap, outerExpr_without_exprToWrap, inserterFunction); } - /** Delegates to {@link TranspilerStateOperations#insertBefore(TranspilerState, EObject, EObject...)}. */ + /** Delegates to {@link TranspilerStateOperations#insertBefore( EObject, EObject...)}. */ protected void insertBefore(EObject elementInIntermediateModel, EObject... newElements) { - TranspilerStateOperations.insertBefore(state, elementInIntermediateModel, newElements); + TranspilerStateOperations.insertBefore(elementInIntermediateModel, newElements); } - /** Delegates to {@link TranspilerStateOperations#insertAfter(TranspilerState, EObject, EObject...)}. */ + /** Delegates to {@link TranspilerStateOperations#insertAfter( EObject, EObject...)}. */ protected void insertAfter(EObject elementInIntermediateModel, EObject... newElements) { - TranspilerStateOperations.insertAfter(state, elementInIntermediateModel, newElements); + TranspilerStateOperations.insertAfter(elementInIntermediateModel, newElements); } /** Delegates to {@link TranspilerStateOperations#copy(TranspilerState, EObject)}. */ @@ -307,7 +306,7 @@ protected void recordReferenceToType(TypeReferenceNode_IM typeRefNode, Symbol @SuppressWarnings("javadoc") protected void rename(SymbolTableEntry entry, String newName) { - TranspilerStateOperations.rename(state, entry, newName); + TranspilerStateOperations.rename(entry, newName); } @SuppressWarnings("javadoc") diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerStateOperations.java b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerStateOperations.java new file mode 100644 index 0000000000..d7c090b1f2 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerStateOperations.java @@ -0,0 +1,653 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler; + +import static org.eclipse.n4js.transpiler.SymbolTableManagement.findSymbolTableEntryForElement; +import static org.eclipse.n4js.transpiler.SymbolTableManagement.getSymbolTableEntryOriginal; +import static org.eclipse.n4js.transpiler.SymbolTableManagement.rewireSymbolTable; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._Argument; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ImportDecl; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NamedImportSpecifier; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NamespaceImportSpecifier; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ReturnStmnt; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableDeclaration; +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._VariableStatement; +import static org.eclipse.n4js.utils.Strings.join; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.util.EObjectContainmentEList; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.EmptyStatement; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ExpressionStatement; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionDeclaration; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.ReturnStatement; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.ScriptElement; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.n4JS.VariableBinding; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableEnvironmentElement; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.n4JS.VariableStatementKeyword; +import org.eclipse.n4js.transpiler.im.ImPackage; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.ReferencingElement_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryIMOnly; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.utils.TranspilerUtils; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.common.collect.Lists; + +/** + * Methods of this class provide elementary operations on a transpiler state, mainly on the intermediate model. The + * intermediate model should only be changed through the operations defined by this class. + *

+ * Main clients are AST transformations, but they should not invoke these operations directly, but instead use the + * delegation methods in {@link Transformation}. + */ +public class TranspilerStateOperations { + + /** + * Creates a new namespace import for the given module and adds it to the intermediate model of the given transpiler + * state. The returned symbol table entry can be used to create references to the namespace, e.g. by passing it to + * {@link TranspilerBuilderBlocks#_IdentRef(SymbolTableEntry)}. The newly created import can be obtained by calling + * {@link SymbolTableEntryOriginal#getImportSpecifier()} on the returned symbol table entry. + *

+ * IMPORTANT: this method does not check if an import for the given module exists already or if the given namespace + * name is unique (i.e. does not avoid name clashes!). + */ + public static SymbolTableEntryOriginal addNamespaceImport(TranspilerState state, TModule moduleToImport, + String namespaceName) { + + // 1) create import declaration & specifier + NamespaceImportSpecifier importSpec = _NamespaceImportSpecifier(namespaceName, true); + ImportDeclaration importDecl = _ImportDecl(importSpec); + // 2) create a temporary type to use as original target + ModuleNamespaceVirtualType typeForNamespace = TypesFactory.eINSTANCE.createModuleNamespaceVirtualType(); + typeForNamespace.setName(namespaceName); + state.resource.addTemporaryType(typeForNamespace); // make sure our temporary type is contained in a resource + // 3) create a symbol table entry + SymbolTableEntryOriginal steForNamespace = getSymbolTableEntryOriginal(state, typeForNamespace, true); + steForNamespace.setImportSpecifier(importSpec); + // 4) add import to intermediate model + EList scriptElements = state.im.getScriptElements(); + if (scriptElements.isEmpty()) { + scriptElements.add(importDecl); + } else { + insertBefore(scriptElements.get(0), importDecl); + } + // 5) update info registry + state.info.setImportedModule(importDecl, moduleToImport); + return steForNamespace; + } + + /** + * Creates a new named import for the given element and adds it to the intermediate model of the given transpiler + * state. The returned symbol table entry can be used to create references to the imported element, e.g. by passing + * it to {@link TranspilerBuilderBlocks#_IdentRef(SymbolTableEntry)}. The newly created import can be obtained by + * calling {@link SymbolTableEntryOriginal#getImportSpecifier()} on the returned symbol table entry. + *

+ * If a named import already exists for the given element, nothing will be changed in the intermediate model and its + * symbol table entry will be returned as described above. If the given element is of type + * {@link ModuleNamespaceVirtualType} an exception will be thrown (because only namespace imports can be created for + * those types). + *

+ * IMPORTANT: this method does not check if the given namespace name is unique (i.e. does not avoid name clashes!). + */ + public static SymbolTableEntryOriginal addNamedImport(TranspilerState state, IdentifiableElement elementToImport, + String aliasOrNull) { + SymbolTableEntryOriginal steOfElementToImport = getSymbolTableEntryOriginal(state, elementToImport, true); + addNamedImport(state, steOfElementToImport, aliasOrNull); + return steOfElementToImport; + } + + /** + * Creates a new named import for the given STE and adds it to the intermediate model of the given transpiler state. + * The passed-in symbol table entry can be used to create references to the imported element, e.g. by passing it to + * {@link TranspilerBuilderBlocks#_IdentRef(SymbolTableEntry)}. The newly created import can be obtained by calling + * {@link SymbolTableEntryOriginal#getImportSpecifier()} on the passed-in symbol table entry. + *

+ * If a named import already exists for the given element, nothing will be changed in the intermediate model. If the + * original target of the given symbol table entry is of type {@link ModuleNamespaceVirtualType} an exception will + * be thrown (because only namespace imports can be created for those types). + *

+ * IMPORTANT: this method does not check if the given namespace name is unique (i.e. does not avoid name clashes!). + */ + public static void addNamedImport(TranspilerState state, SymbolTableEntryOriginal steOfElementToImport, + String aliasOrNull) { + // check for valid type of element to be imported (i.e. the original target) + IdentifiableElement originalTarget = steOfElementToImport.getOriginalTarget(); + if (originalTarget instanceof ModuleNamespaceVirtualType) { + throw new IllegalArgumentException("cannot create named import for a ModuleNamespaceVirtualType"); + } + // check for existing import + ImportSpecifier existingImportSpec = steOfElementToImport.getImportSpecifier(); + if (existingImportSpec != null) { + // import already exists, nothing to be done + return; + } + + // 1) create import declaration & specifier + NamedImportSpecifier importSpec = _NamedImportSpecifier(steOfElementToImport.getExportedName(), aliasOrNull, + true); + ImportDeclaration importDecl = _ImportDecl(importSpec); + // 2) add import to intermediate model + EList scriptElements = state.im.getScriptElements(); + if (scriptElements.isEmpty()) { + scriptElements.add(importDecl); + } else { + insertBefore(scriptElements.get(0), importDecl); + } + // 3) link symbol table entry to its newly created import specifier + steOfElementToImport.setImportSpecifier(importSpec); + // 4) update info registry + TModule moduleOfOriginalTarget = originalTarget.getContainingModule(); + state.info.setImportedModule(importDecl, moduleOfOriginalTarget); + } + + /** + * Adds an "empty" import to the intermediate model, i.e. an import of the form: + * + *

+	 * import "<moduleSpecifier>";
+	 * 
+ */ + public static void addEmptyImport(TranspilerState state, String moduleSpecifier) { + // 1) create import declaration + ImportDeclaration importDecl = _ImportDecl(); + importDecl.setModuleSpecifierAsText(moduleSpecifier); + // 2) add import to intermediate model + EList scriptElements = state.im.getScriptElements(); + if (scriptElements.isEmpty()) { + scriptElements.add(importDecl); + } else { + insertBefore(scriptElements.get(0), importDecl); + } + } + + /** + * Returns the symbol table entry to a temporary variable with the given name, intended for use at the location of + * "nodeInIM" in the intermediate model. If no such variable exists yet, a new variable statement and declaration + * will be created. + *

+ * When newly created, the temporary declarations will be added to the body of the closest ancestor + * function/accessor (or on the top level if no such ancestor exists), even if a temporary variable of the same name + * already exists in an outer variable environment (i.e. an outer function/accessor or on top level if inside a + * function/accessor). + */ + public static SymbolTableEntryIMOnly addOrGetTemporaryVariable(TranspilerState state, String name, + EObject nodeInIM) { + FunctionOrFieldAccessor contextFunctionOrAccessor = getContextFunctionOrAccessor(nodeInIM); + VariableEnvironmentElement context = contextFunctionOrAccessor != null ? contextFunctionOrAccessor : state.im; + SymbolTableEntryIMOnly tempVarSTE = state.temporaryVariables.get(Pair.of(context, name)); + if (tempVarSTE != null) { + return tempVarSTE; + } + // need to create a new temporary variable below context + VariableStatement tempVarStmnt = addOrGetTemporaryVariableStatement(state, context); + VariableDeclaration tempVarDecl = _VariableDeclaration(name); + tempVarStmnt.getVarDeclsOrBindings().add(tempVarDecl); + SymbolTableEntryIMOnly tempVarSTENew = (SymbolTableEntryIMOnly) findSymbolTableEntryForElement(state, + tempVarDecl, true); + state.temporaryVariables.put(Pair.of(context, name), tempVarSTENew); + return tempVarSTENew; + } + + private static FunctionOrFieldAccessor getContextFunctionOrAccessor(EObject nodeInIM) { + if (nodeInIM == null) { + return null; + } + if (nodeInIM instanceof FunctionOrFieldAccessor) { + return (FunctionOrFieldAccessor) nodeInIM; + } + EObject parent = nodeInIM.eContainer(); + if (parent instanceof FormalParameter + && parent.eContainer() instanceof FunctionOrFieldAccessor + && ((FormalParameter) parent).getInitializer() == nodeInIM) { + // special case: since the expression of a default parameter cannot access a function"s local variables, + // the directly containing function of a default parameter is not a valid context function for temporary + // variables used in the default parameter"s initializer expression. + EObject parentOfContainingFunctionOrAccessor = parent.eContainer().eContainer(); + return getContextFunctionOrAccessor(parentOfContainingFunctionOrAccessor); + } + return getContextFunctionOrAccessor(parent); + } + + /** If context is absent, then the temporary variable statement will be created on the top level. */ + private static VariableStatement addOrGetTemporaryVariableStatement(TranspilerState state, + VariableEnvironmentElement context) { + VariableStatement tempVarStmnt = state.temporaryVariableStatements.get(context); + if (tempVarStmnt != null) { + return tempVarStmnt; + } + // need to create a new temporary variable statement + VariableStatement tempVarStmntNew = _VariableStatement(VariableStatementKeyword.LET); + state.temporaryVariableStatements.put(context, tempVarStmntNew); + if (context instanceof FunctionOrFieldAccessor) { + // add to body of function/accessor + if (context instanceof ArrowFunction) { + ArrowFunction af = (ArrowFunction) context; + if (!af.isHasBracesAroundBody()) { + // to allow for declarations inside the body, we have to turn single-expression arrow functions into + // ordinary arrow functions + if (af.isSingleExprImplicitReturn()) { + ExpressionStatement singleExprStmnt = (ExpressionStatement) af.getBody().getStatements().get(0); // we + // know + // this, + // because + // #isSingleExprImplicitReturn() + // returned + // true + replace(state, singleExprStmnt, _ReturnStmnt(singleExprStmnt.getExpression())); + } + af.setHasBracesAroundBody(true); + } + } + ((FunctionOrFieldAccessor) context).getBody().getStatements().add(0, tempVarStmntNew); + } else if (context instanceof Script) { + Script script = (Script) context; + // add on top level before the first non-empty, non-import statement + Iterator iter = script.getScriptElements().iterator(); + ScriptElement elem; + do { + elem = (iter.hasNext()) ? iter.next() : null; + } while (elem instanceof EmptyStatement || elem instanceof ImportDeclaration); + if (elem != null) { + insertBefore(elem, tempVarStmntNew); + } else { + script.getScriptElements().add(tempVarStmntNew); + } + } + return tempVarStmntNew; + } + + /***/ + public static void setTarget(TranspilerState state, ParameterizedCallExpression callExpr, Expression newTarget) { + Expression oldTarget = callExpr.getTarget(); + if (oldTarget != null) { + replaceWithoutRewire(state, oldTarget, newTarget); + } else { + callExpr.setTarget(newTarget); + } + } + + /***/ + public static void setTarget(TranspilerState state, ParameterizedPropertyAccessExpression_IM accExpr, + Expression newTarget) { + Expression oldTarget = accExpr.getTarget(); + if (oldTarget != null) { + replaceWithoutRewire(state, oldTarget, newTarget); + } else { + accExpr.setTarget(newTarget); + } + } + + /***/ + public static void addArgument(ParameterizedCallExpression callExpr, int index, Expression newArgument) { + callExpr.getArguments().add(index, _Argument(newArgument)); + } + + /***/ + public static void removeAll(TranspilerState state, Iterable elementsInIM) { + for (EObject elementInIM : Lists.newArrayList(elementsInIM)) { + remove(state, elementInIM); + } + } + + /***/ + public static void remove(TranspilerState state, EObject elementInIM) { + replaceWithoutRewire(state, elementInIM); // i.e. replace with nothing (will update tracer) + if (elementInIM instanceof ReferencingElement_IM) { + ((ReferencingElement_IM) elementInIM).setRewiredTarget(null); // important here: will remove elementInIM + // from its symbol table entry"s + // "referencingElements" list! + // note: this update of the symbol table is incomplete; elementInIM may be the root of an entire subtree + // of the IM, so we would have to iterate over all successors + } + } + + /** + * Removes the export-container (ExportDeclaration) by creating a new VariableStatement {@code varStmt}, moving all + * content from {@code exVarStmnt} into it and replacing the ExportDeclaration with the newly created + * {@code varStmt} + */ + public static void removeExport(TranspilerState state, VariableStatement exVarStmnt) { + + if (!TranspilerUtils.isIntermediateModelElement(exVarStmnt)) { + throw new IllegalArgumentException("not an element in the intermediate model: " + exVarStmnt); + } + + ExportDeclaration exportDecl = (ExportDeclaration) exVarStmnt.eContainer(); + + replaceWithoutRewire(state, exportDecl, exVarStmnt); + } + + /***/ + public static void replace(TranspilerState state, Statement stmnt, ReturnStatement returnStmnt) { + replaceWithoutRewire(state, stmnt, returnStmnt); + } + + /***/ + public static void replace(TranspilerState state, N4ClassDeclaration classDecl, FunctionDeclaration funDecl) { + replaceWithoutRewire(state, classDecl, funDecl); + rewireSymbolTable(state, classDecl, funDecl); + } + + /** + * Replace an interface declaration by a variable declaration. The variable declaration will be wrapped in a newly + * created [Exported]VariableStatement. + */ + public static void replace(TranspilerState state, N4InterfaceDeclaration ifcDecl, VariableDeclaration varDecl) { + VariableStatement varStmnt = _VariableStatement(VariableStatementKeyword.CONST, varDecl); + replaceWithoutRewire(state, ifcDecl, varStmnt); + rewireSymbolTable(state, ifcDecl, varDecl); + } + + /***/ + public static void replace(TranspilerState state, N4EnumDeclaration enumDecl, N4ClassDeclaration classDecl) { + replaceWithoutRewire(state, enumDecl, classDecl); + rewireSymbolTable(state, enumDecl, classDecl); + } + + /***/ + public static void replace(TranspilerState state, FunctionDeclaration funDecl, VariableDeclaration varDecl) { + VariableStatement varStmnt = _VariableStatement(varDecl); + replaceWithoutRewire(state, funDecl, varStmnt); + rewireSymbolTable(state, funDecl, varDecl); + // need to rewire the local arguments variable, to enable renaming: + Expression varValue = varDecl.getExpression(); + if (varValue instanceof FunctionExpression) { + rewireSymbolTable(state, funDecl.getImplicitArgumentsVariable(), + ((FunctionExpression) varValue).getImplicitArgumentsVariable()); + } else { + throw new IllegalArgumentException( + "when replacing a function declaration by a variable declaration, " + + "we expect the variable to be initialized with a function expression"); + } + } + + /***/ + public static void replace(TranspilerState state, FunctionDeclaration functionDecl, ExpressionStatement stmt) { + replaceWithoutRewire(state, functionDecl, stmt); + } + + /***/ + public static void replace(TranspilerState state, N4MemberDeclaration memberDecl, N4MemberDeclaration replacement) { + replaceWithoutRewire(state, memberDecl, replacement); + rewireSymbolTable(state, memberDecl, replacement); + } + + /***/ + public static void replace(TranspilerState state, VariableStatement varStmnt, Statement... newStmnts) { + replaceWithoutRewire(state, varStmnt, newStmnts); + } + + /***/ + public static void replace(TranspilerState state, VariableBinding varBinding, VariableDeclaration... varDecls) { + replaceWithoutRewire(state, varBinding, varDecls); + } + + /***/ + public static void replace(TranspilerState state, Expression exprOld, Expression exprNew) { + replaceWithoutRewire(state, exprOld, exprNew); + } + + /***/ + public static void replace(TranspilerState state, ArrowFunction exprOld, ParameterizedCallExpression exprNew, + FunctionExpression rewireTarget) { + replaceWithoutRewire(state, exprOld, exprNew); + rewireSymbolTable(state, exprOld, rewireTarget); + } + + /** Replace formal parameter with a variableStmt. Rewire the fpar to the VariableDeclaration. Relocate the Stmt */ + public static void replaceAndRelocate(TranspilerState state, FormalParameter fPar_to_remove, + VariableStatement varStmnt, + VariableDeclaration varDecl_wireTo, Block newContainer) { + if (varDecl_wireTo.eContainer() != varStmnt) { + throw new IllegalArgumentException("varDecl must be contained in varStmnt"); + } + replaceAndRelocateWithoutRewire_internal(state, fPar_to_remove, varStmnt, newContainer.getStatements(), 0); + + rewireSymbolTable(state, fPar_to_remove, varDecl_wireTo); + } + + /***/ + public static void wrapExistingExpression(T exprToWrap, + Expression outerExpr_without_exprToWrap, Consumer inserterFunction) { + + insertOrReplace_internal(exprToWrap, List.of(outerExpr_without_exprToWrap), true, false); + inserterFunction.accept(exprToWrap); + } + + /* + * append( pos < 0 or > current size ), prepend(pos==0) or insert at {@code pos} the object {@code insertThis} to + * {@code newContainer}. Also delete {@code removeThis} from the IM. Does not rewire. But keeps trace. + */ + private static void replaceAndRelocateWithoutRewire_internal(TranspilerState state, EObject removeThis, + EObject insertThis, + EList newContainer, int pos) { + + EReference eRefRemove = checkedContainmentFeature(removeThis); + + if (insertThis.eContainer() != null) + throw new IllegalArgumentException("The new element must not be contained anywhere." + + " insertThis=" + insertThis + " is currently contained in " + insertThis.eContainer()); + + if (newContainer instanceof EObjectContainmentEList) { + @SuppressWarnings("unchecked") + EObjectContainmentEList newContainerCasted = (EObjectContainmentEList) newContainer; + + //// Tracing: + state.tracer.copyTrace(removeThis, insertThis); + state.tracer.discardIntermediateModelNode(removeThis); + + //////////////////////////////////// + //// remove: + if (eRefRemove.getUpperBound() == 1) { // single-value + if (eRefRemove.isUnsettable()) + removeThis.eContainer().eUnset(eRefRemove); + else + removeThis.eContainer().eSet(eRefRemove, null); + } else { // multivalue + @SuppressWarnings("unchecked") + List l = (List) removeThis.eContainer().eGet(eRefRemove); // c.f. type check above + int idx = l.indexOf(removeThis); + l.remove(idx); + } + + //////////////////////////////////// + //// insert: + int idx = pos; // insert + + if (pos < 0 || pos >= newContainer.size()) { + // append + idx = newContainer.size(); + } + newContainerCasted.add(idx, insertThis); + + } else { + throw new IllegalArgumentException( + "designated new container-list must be a subtype of type EObjectContainmentList"); + } + } + + /** + * {@code elementInIntermediateModel} is going away (ie, should be garbage-collected) and therefore we clear all + * references to it from tracing. The {@code replacements} IM nodes take over whatever AST node was previously + * traced-back-to via the element going away. + */ + private static void replaceWithoutRewire(TranspilerState state, EObject elementInIntermediateModel, + EObject... replacements) { + + state.tracer.copyTrace(elementInIntermediateModel, replacements); + state.tracer.discardIntermediateModelNode(elementInIntermediateModel); + insertOrReplace_internal(elementInIntermediateModel, Arrays.asList(replacements), true, false); + } + + /***/ + public static void insertBefore(EObject elementInIntermediateModel, EObject... newElements) { + insertOrReplace_internal(elementInIntermediateModel, Arrays.asList(newElements), false, false); + } + + /***/ + public static void insertAfter(EObject elementInIntermediateModel, EObject... newElements) { + insertOrReplace_internal(elementInIntermediateModel, Arrays.asList(newElements), false, true); + } + + private static void insertOrReplace_internal(EObject elementInIntermediateModel, + List newElements, boolean replace, boolean after) { + if (newElements.isEmpty() && !replace) { + return; // nothing to be inserted + } + EReference eRef = checkedContainmentFeature(elementInIntermediateModel); + EClass eRefType = eRef.getEReferenceType(); + List replElemsOfWrongType = toList( + filter(newElements, elem -> !eRefType.isSuperTypeOf(elem.eClass()))); + if (!replElemsOfWrongType.isEmpty()) { + throw new IllegalArgumentException("one or more elements are of wrong type, expected: " + + eRef.getEReferenceType().getName() + ", actual: " + + join(", ", map(replElemsOfWrongType, eobj -> eobj.eClass().getName()))); + } + if (eRef.getUpperBound() == 1) { + // single valued + if (newElements.size() > 1) { + throw new IllegalArgumentException( + "the single-valued reference " + eRef.getName() + " in class " + eRef.getEContainingClass() + + " is not able to hold " + newElements.size() + " elements."); + } + if (newElements.size() == 1) { + if (!replace) { + throw new IllegalArgumentException( + "Cannot insert another element into a single-valued containment reference " + eRef.getName() + + " in class " + eRef.getEContainingClass()); + } + elementInIntermediateModel.eContainer().eSet(eRef, newElements.get(0)); + } else { + if (!replace) { + throw new IllegalArgumentException("Inserting zero elements with replace==false is pointless."); + } + // no element, so remove + if (eRef.isUnsettable()) { + elementInIntermediateModel.eContainer().eUnset(eRef); + } else { + elementInIntermediateModel.eContainer().eSet(eRef, null); + } + } + + } else { + // multi-valued + @SuppressWarnings("unchecked") + // c.f. type check above + List l = (List) elementInIntermediateModel.eContainer().eGet(eRef); + int idx = l.indexOf(elementInIntermediateModel); + if (replace) { + l.remove(idx); + } else { + // note: before/after only applicable if !replace + if (after) { + idx++; // always safe to increment, because we know there exists an element at index "idx" + } + } + l.addAll(idx, newElements); + } + } + + /** + * Retrieves the EReference of the Container. Throws Exceptions if a) not part of the IM or b) not contained + * anywhere + */ + private static EReference checkedContainmentFeature(EObject elementInIntermediateModel) { + if (!TranspilerUtils.isIntermediateModelElement(elementInIntermediateModel)) { + throw new IllegalArgumentException( + "not an element in the intermediate model: " + elementInIntermediateModel); + } + EReference eRef = elementInIntermediateModel.eContainmentFeature(); + if (eRef == null) { + throw new IllegalArgumentException("element is not contained anywhere"); + } + return eRef; + } + + /** + * Rename the given symbol table entry and all named elements in the intermediate model that are using this name. + * During AST transformations in the transpiler, the "name" property of a symbol table entry should never be changed + * directly, but this operation should be used instead. + *

+ * WARNING: renaming is currently only implemented partially and used only in a single, very specific use + * case; if renaming is required in the future, then the implementation of this method has to be complemented! + */ + public static void rename(SymbolTableEntry entry, String newName) { + SymbolTableManagement.rename(entry, newName); + } + + /** + * Copy a subtree of the intermediate model. + */ + @SuppressWarnings("unchecked") + public static T copy(TranspilerState state, T elementInIM) { + // create a copy with a special copier to take care of reference ReferencingElement_IM#rewiredTarget + IM2IMCopier copier = new IM2IMCopier(); + EObject result = copier.copy(elementInIM); + copier.copyReferences(); + // copy tracing information + state.tracer.copyTrace(elementInIM, result); // note: copying trace for all nodes would be more fine grained + return (T) result; + } + + private static final class IM2IMCopier extends EcoreUtil.Copier { + private static final EReference eRef__ReferencingElement_IM__rewiredTarget = ImPackage.eINSTANCE + .getReferencingElement_IM_RewiredTarget(); + + @Override + protected void copyReference(EReference eReference, EObject eObject, EObject copyEObject) { + if (eReference == eRef__ReferencingElement_IM__rewiredTarget) { + ((ReferencingElement_IM) copyEObject) + .setRewiredTarget(((ReferencingElement_IM) eObject).getRewiredTarget()); + } else { + super.copyReference(eReference, eObject, copyEObject); + } + } + + } +} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerStateOperations.xtend b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerStateOperations.xtend deleted file mode 100644 index 3da26775c5..0000000000 --- a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/TranspilerStateOperations.xtend +++ /dev/null @@ -1,571 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler - -import com.google.common.collect.Lists -import java.util.List -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EReference -import org.eclipse.emf.ecore.util.EObjectContainmentEList -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.Block -import org.eclipse.n4js.n4JS.EmptyStatement -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ExpressionStatement -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.n4JS.FunctionDeclaration -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4EnumDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.n4JS.ReturnStatement -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.ScriptElement -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.n4JS.VariableBinding -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableEnvironmentElement -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.n4JS.VariableStatementKeyword -import org.eclipse.n4js.transpiler.im.ImPackage -import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM -import org.eclipse.n4js.transpiler.im.ReferencingElement_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.transpiler.im.SymbolTableEntryIMOnly -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.transpiler.utils.TranspilerUtils -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TypesFactory - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.transpiler.SymbolTableManagement.* - -/** - * Methods of this class provide elementary operations on a transpiler state, mainly on the intermediate model. The - * intermediate model should only be changed through the operations defined by this class. - *

- * Main clients are AST transformations, but they should not invoke these operations directly, but instead use the - * delegation methods in {@link Transformation}. - */ -class TranspilerStateOperations { - - /** - * Creates a new namespace import for the given module and adds it to the intermediate model of the given transpiler - * state. The returned symbol table entry can be used to create references to the namespace, e.g. by passing it to - * {@link TranspilerBuilderBlocks#_IdentRef(SymbolTableEntry)}. The newly created import can be obtained by calling - * {@link SymbolTableEntryOriginal#getImportSpecifier()} on the returned symbol table entry. - *

- * IMPORTANT: this method does not check if an import for the given module exists already or if the given namespace - * name is unique (i.e. does not avoid name clashes!). - */ - def public static SymbolTableEntryOriginal addNamespaceImport(TranspilerState state, TModule moduleToImport, - String namespaceName) { - - // 1) create import declaration & specifier - val importSpec = _NamespaceImportSpecifier(namespaceName, true); - val importDecl = _ImportDecl(importSpec); - // 2) create a temporary type to use as original target - val typeForNamespace = TypesFactory.eINSTANCE.createModuleNamespaceVirtualType(); - typeForNamespace.name = namespaceName; - state.resource.addTemporaryType(typeForNamespace); // make sure our temporary type is contained in a resource - // 3) create a symbol table entry - val steForNamespace = getSymbolTableEntryOriginal(state, typeForNamespace, true); - steForNamespace.importSpecifier = importSpec; - // 4) add import to intermediate model - val scriptElements = state.im.scriptElements; - if(scriptElements.empty) { - scriptElements.add(importDecl); - } else { - insertBefore(state, scriptElements.get(0), importDecl); - } - // 5) update info registry - state.info.setImportedModule(importDecl, moduleToImport); - return steForNamespace; - } - - /** - * Creates a new named import for the given element and adds it to the intermediate model of the given transpiler - * state. The returned symbol table entry can be used to create references to the imported element, e.g. by passing - * it to {@link TranspilerBuilderBlocks#_IdentRef(SymbolTableEntry)}. The newly created import can be obtained by - * calling {@link SymbolTableEntryOriginal#getImportSpecifier()} on the returned symbol table entry. - *

- * If a named import already exists for the given element, nothing will be changed in the intermediate model and its - * symbol table entry will be returned as described above. If the given element is of type - * {@link ModuleNamespaceVirtualType} an exception will be thrown (because only namespace imports can be created for - * those types). - *

- * IMPORTANT: this method does not check if the given namespace name is unique (i.e. does not avoid name clashes!). - */ - def public static SymbolTableEntryOriginal addNamedImport(TranspilerState state, IdentifiableElement elementToImport, String aliasOrNull) { - val steOfElementToImport = getSymbolTableEntryOriginal(state, elementToImport, true); - addNamedImport(state, steOfElementToImport, aliasOrNull); - return steOfElementToImport; - } - - /** - * Creates a new named import for the given STE and adds it to the intermediate model of the given transpiler - * state. The passed-in symbol table entry can be used to create references to the imported element, e.g. by passing - * it to {@link TranspilerBuilderBlocks#_IdentRef(SymbolTableEntry)}. The newly created import can be obtained by - * calling {@link SymbolTableEntryOriginal#getImportSpecifier()} on the passed-in symbol table entry. - *

- * If a named import already exists for the given element, nothing will be changed in the intermediate model. If the - * original target of the given symbol table entry is of type {@link ModuleNamespaceVirtualType} an exception will - * be thrown (because only namespace imports can be created for those types). - *

- * IMPORTANT: this method does not check if the given namespace name is unique (i.e. does not avoid name clashes!). - */ - def public static void addNamedImport(TranspilerState state, SymbolTableEntryOriginal steOfElementToImport, String aliasOrNull) { - // check for valid type of element to be imported (i.e. the original target) - val originalTarget = steOfElementToImport.originalTarget; - if(originalTarget instanceof ModuleNamespaceVirtualType) { - throw new IllegalArgumentException("cannot create named import for a ModuleNamespaceVirtualType"); - } - // check for existing import - val existingImportSpec = steOfElementToImport.importSpecifier; - if(existingImportSpec!==null) { - // import already exists, nothing to be done - return; - } - - // 1) create import declaration & specifier - val importSpec = _NamedImportSpecifier(steOfElementToImport.exportedName, aliasOrNull, true); - val importDecl = _ImportDecl(importSpec); - // 2) add import to intermediate model - val scriptElements = state.im.scriptElements; - if(scriptElements.empty) { - scriptElements.add(importDecl); - } else { - insertBefore(state, scriptElements.get(0), importDecl); - } - // 3) link symbol table entry to its newly created import specifier - steOfElementToImport.importSpecifier = importSpec; - // 4) update info registry - val moduleOfOriginalTarget = originalTarget.containingModule; - state.info.setImportedModule(importDecl, moduleOfOriginalTarget); - } - - /** - * Adds an "empty" import to the intermediate model, i.e. an import of the form: - *

-	 * import "<moduleSpecifier>";
-	 * 
- */ - def public static void addEmptyImport(TranspilerState state, String moduleSpecifier) { - // 1) create import declaration - val importDecl = _ImportDecl() => [ - moduleSpecifierAsText = moduleSpecifier - ]; - // 2) add import to intermediate model - val scriptElements = state.im.scriptElements; - if(scriptElements.empty) { - scriptElements.add(importDecl); - } else { - insertBefore(state, scriptElements.get(0), importDecl); - } - } - - /** - * Returns the symbol table entry to a temporary variable with the given name, intended for use at the location - * of 'nodeInIM' in the intermediate model. If no such variable exists yet, a new variable statement and declaration - * will be created. - *

- * When newly created, the temporary declarations will be added to the body of the closest ancestor function/accessor - * (or on the top level if no such ancestor exists), even if a temporary variable of the same name already exists in - * an outer variable environment (i.e. an outer function/accessor or on top level if inside a function/accessor). - */ - def public static SymbolTableEntryIMOnly addOrGetTemporaryVariable(TranspilerState state, String name, EObject nodeInIM) { - val contextFunctionOrAccessor = getContextFunctionOrAccessor(nodeInIM); - val context = contextFunctionOrAccessor ?: state.im; - val tempVarSTE = state.temporaryVariables.get(context -> name); - if (tempVarSTE !== null) { - return tempVarSTE; - } - // need to create a new temporary variable below context - val tempVarStmnt = state.addOrGetTemporaryVariableStatement(context); - val tempVarDecl = _VariableDeclaration(name); - tempVarStmnt.varDeclsOrBindings += tempVarDecl; - val tempVarSTENew = state.findSymbolTableEntryForElement(tempVarDecl, true) as SymbolTableEntryIMOnly; - state.temporaryVariables.put(context -> name, tempVarSTENew); - return tempVarSTENew; - } - - def private static FunctionOrFieldAccessor getContextFunctionOrAccessor(EObject nodeInIM) { - if (nodeInIM === null) { - return null; - } - if (nodeInIM instanceof FunctionOrFieldAccessor) { - return nodeInIM; - } - val parent = nodeInIM.eContainer(); - if (parent instanceof FormalParameter - && parent.eContainer() instanceof FunctionOrFieldAccessor - && (parent as FormalParameter).initializer === nodeInIM) { - // special case: since the expression of a default parameter cannot access a function's local variables, - // the directly containing function of a default parameter is not a valid context function for temporary - // variables used in the default parameter's initializer expression. - val parentOfContainingFunctionOrAccessor = parent.eContainer().eContainer(); - return getContextFunctionOrAccessor(parentOfContainingFunctionOrAccessor); - } - return getContextFunctionOrAccessor(parent); - } - - /** If context is absent, then the temporary variable statement will be created on the top level. */ - def private static VariableStatement addOrGetTemporaryVariableStatement(TranspilerState state, VariableEnvironmentElement context) { - val tempVarStmnt = state.temporaryVariableStatements.get(context); - if (tempVarStmnt !== null) { - return tempVarStmnt; - } - // need to create a new temporary variable statement - val tempVarStmntNew = _VariableStatement(VariableStatementKeyword.LET); - state.temporaryVariableStatements.put(context, tempVarStmntNew); - if (context instanceof FunctionOrFieldAccessor) { - // add to body of function/accessor - if (context instanceof ArrowFunction) { - if (!context.hasBracesAroundBody) { - // to allow for declarations inside the body, we have to turn single-expression arrow functions into ordinary arrow functions - if (context.isSingleExprImplicitReturn) { - val singleExprStmnt = context.body.statements.head as ExpressionStatement; // we know this, because #isSingleExprImplicitReturn() returned true - state.replace(singleExprStmnt, _ReturnStmnt(singleExprStmnt.expression)); - } - context.hasBracesAroundBody = true; - } - } - context.body.statements.add(0, tempVarStmntNew); - } else if (context instanceof Script) { - // add on top level before the first non-empty, non-import statement - val iter = context.scriptElements.iterator; - var ScriptElement elem; - do { - elem = if (iter.hasNext()) iter.next() else null; - } while(elem instanceof EmptyStatement || elem instanceof ImportDeclaration); - if (elem !== null) { - state.insertBefore(elem, tempVarStmntNew); - } else { - context.scriptElements += tempVarStmntNew; - } - } - return tempVarStmntNew; - } - - def public static void setTarget(TranspilerState state, ParameterizedCallExpression callExpr, Expression newTarget) { - val oldTarget = callExpr.target; - if(oldTarget!==null) { - state.replaceWithoutRewire(oldTarget, newTarget); - } else { - callExpr.target = newTarget; - } - } - - def public static void setTarget(TranspilerState state, ParameterizedPropertyAccessExpression_IM accExpr, Expression newTarget) { - val oldTarget = accExpr.target; - if(oldTarget!==null) { - state.replaceWithoutRewire(oldTarget, newTarget); - } else { - accExpr.target = newTarget; - } - } - - def public static void addArgument(TranspilerState state, ParameterizedCallExpression callExpr, int index, Expression newArgument) { - callExpr.arguments.add(index, _Argument(newArgument)); - } - - def public static void removeAll(TranspilerState state, Iterable elementsInIM) { - for (EObject elementInIM : Lists.newArrayList(elementsInIM)) { - remove(state, elementInIM); - } - } - - def public static void remove(TranspilerState state, EObject elementInIM) { - state.replaceWithoutRewire(elementInIM) // i.e. replace with nothing (will update tracer) - if(elementInIM instanceof ReferencingElement_IM) { - elementInIM.rewiredTarget = null; // important here: will remove elementInIM from its symbol table entry's 'referencingElements' list! - // note: this update of the symbol table is incomplete; elementInIM may be the root of an entire subtree - // of the IM, so we would have to iterate over all successors - } - } - - /** - * Removes the export-container (ExportDeclaration) by creating a new VariableStatement {@code varStmt}, moving all content from {@code exVarStmnt} - * into it and replacing the ExportDeclaration with the newly created {@code varStmt} - * @return newly created {@code varStmt} (already part of the intermediate model). - */ - def public static void removeExport(TranspilerState state, VariableStatement exVarStmnt) { - - if(!TranspilerUtils.isIntermediateModelElement(exVarStmnt)) { - throw new IllegalArgumentException("not an element in the intermediate model: " + exVarStmnt); - } - - val exportDecl = exVarStmnt.eContainer as ExportDeclaration - - state.replaceWithoutRewire(exportDecl,exVarStmnt); - } - - - def public static void replace(TranspilerState state, Statement stmnt, ReturnStatement returnStmnt) { - state.replaceWithoutRewire(stmnt, returnStmnt); - } - - def public static void replace(TranspilerState state, N4ClassDeclaration classDecl, FunctionDeclaration funDecl) { - state.replaceWithoutRewire(classDecl, funDecl); - state.rewireSymbolTable(classDecl, funDecl); - } - - /** - * Replace an interface declaration by a variable declaration. The variable declaration will be wrapped in a - * newly created [Exported]VariableStatement. - */ - def public static void replace(TranspilerState state, N4InterfaceDeclaration ifcDecl, VariableDeclaration varDecl) { - val varStmnt = _VariableStatement(VariableStatementKeyword.CONST, varDecl); - state.replaceWithoutRewire(ifcDecl, varStmnt); - state.rewireSymbolTable(ifcDecl, varDecl); - } - - def public static void replace(TranspilerState state, N4EnumDeclaration enumDecl, N4ClassDeclaration classDecl) { - state.replaceWithoutRewire(enumDecl, classDecl); - state.rewireSymbolTable(enumDecl, classDecl); - } - - def public static void replace(TranspilerState state, FunctionDeclaration funDecl, VariableDeclaration varDecl) { - val varStmnt = _VariableStatement(varDecl); - state.replaceWithoutRewire(funDecl, varStmnt); - state.rewireSymbolTable(funDecl,varDecl); - // need to rewire the local arguments variable, to enable renaming: - val varValue = varDecl.expression; - if(varValue instanceof FunctionExpression) { - state.rewireSymbolTable(funDecl.implicitArgumentsVariable, varValue.implicitArgumentsVariable); - } else { - throw new IllegalArgumentException( - "when replacing a function declaration by a variable declaration, " + - "we expect the variable to be initialized with a function expression"); - } - } - - def public static void replace(TranspilerState state, FunctionDeclaration functionDecl, ExpressionStatement stmt) { - state.replaceWithoutRewire(functionDecl, stmt); - } - - def public static void replace(TranspilerState state, N4MemberDeclaration memberDecl, N4MemberDeclaration replacement) { - state.replaceWithoutRewire(memberDecl, replacement); - state.rewireSymbolTable(memberDecl, replacement); - } - - def public static void replace(TranspilerState state, VariableStatement varStmnt, Statement... newStmnts) { - state.replaceWithoutRewire(varStmnt, newStmnts); - } - - def public static void replace(TranspilerState state, VariableBinding varBinding, VariableDeclaration... varDecls) { - state.replaceWithoutRewire(varBinding, varDecls); - } - - def public static void replace(TranspilerState state, Expression exprOld, Expression exprNew) { - state.replaceWithoutRewire(exprOld, exprNew); - } - - def public static void replace(TranspilerState state, ArrowFunction exprOld, ParameterizedCallExpression exprNew, FunctionExpression rewireTarget) { - state.replaceWithoutRewire(exprOld, exprNew); - state.rewireSymbolTable(exprOld, rewireTarget); - } - - - /** Replace formal parameter with a variableStmt. Rewire the fpar to the VariableDeclaration. Relocate the Stmt */ - def public static void replaceAndRelocate(TranspilerState state, FormalParameter fPar_to_remove, VariableStatement varStmnt, - VariableDeclaration varDecl_wireTo, Block newContainer ) { - if(varDecl_wireTo.eContainer!==varStmnt) { - throw new IllegalArgumentException("varDecl must be contained in varStmnt"); - } - state.replaceAndRelocateWithoutRewire_internal(fPar_to_remove, varStmnt, newContainer.statements, 0); - - state.rewireSymbolTable(fPar_to_remove, varDecl_wireTo); - } - - def public static void wrapExistingExpression(TranspilerState state, - T exprToWrap, Expression outerExpr_without_exprToWrap, (T)=>void inserterFunction - ) { - state.insertOrReplace_internal(exprToWrap, #[outerExpr_without_exprToWrap], true, false); - inserterFunction.apply(exprToWrap) - } - - /* append( pos < 0 or > current size ), prepend(pos==0) or insert at {@code pos} the object {@code insertThis} - * to {@code newContainer}. Also delete {@code removeThis} from the IM. Does not rewire. But keeps trace.*/ - def private static void replaceAndRelocateWithoutRewire_internal(TranspilerState state, EObject removeThis, EObject insertThis, - EList newContainer, int pos ) { - - - val eRefRemove = checkedContainmentFeature(removeThis); - - if( insertThis.eContainer !== null ) throw new IllegalArgumentException("The new element must not be contained anywhere."+ - " insertThis="+insertThis+" is currently contained in "+insertThis.eContainer); - - if( newContainer instanceof EObjectContainmentEList ){ - val newContainerCasted = newContainer as EObjectContainmentEList; - - //// Tracing: - state.tracer.copyTrace(removeThis, insertThis); - state.tracer.discardIntermediateModelNode(removeThis); - - //////////////////////////////////// - //// remove: - if( eRefRemove.upperBound == 1 ) { // single-value - if( eRefRemove.isUnsettable ) - removeThis.eContainer.eUnset(eRefRemove) - else - removeThis.eContainer.eSet(eRefRemove,null) - } else { // multivalue - val l = removeThis.eContainer.eGet(eRefRemove) as List; // c.f. type check above - var idx = l.indexOf(removeThis); - l.remove(idx); - } - - //////////////////////////////////// - //// insert: - val idx = if( pos < 0 || pos >= newContainer.size ) { - // append - newContainer.size - } else { - // insert - pos - }; - newContainerCasted.add(idx,insertThis) - - } else { - throw new IllegalArgumentException("designated new container-list must be a subtype of type EObjectContainmentList") - } - } - - /** - * {@code elementInIntermediateModel} is going away (ie, should be garbage-collected) and therefore we clear all - * references to it from tracing. The {@code replacements} IM nodes take over whatever AST node was previously - * traced-back-to via the element going away. - */ - def private static void replaceWithoutRewire(TranspilerState state, EObject elementInIntermediateModel, EObject... replacements) { - state.tracer.copyTrace(elementInIntermediateModel, replacements); - state.tracer.discardIntermediateModelNode(elementInIntermediateModel); - state.insertOrReplace_internal(elementInIntermediateModel, replacements, true, false); - } - - - def public static void insertBefore(TranspilerState state, EObject elementInIntermediateModel, EObject... newElements) { - state.insertOrReplace_internal(elementInIntermediateModel, newElements, false, false); - } - - def public static void insertAfter(TranspilerState state, EObject elementInIntermediateModel, EObject... newElements) { - state.insertOrReplace_internal(elementInIntermediateModel, newElements, false, true); - } - - def private static void insertOrReplace_internal(TranspilerState state, EObject elementInIntermediateModel, - EObject[] newElements, boolean replace, boolean after - ) { - if(newElements.empty && ! replace) { - return; // nothing to be inserted - } - val eRef = checkedContainmentFeature(elementInIntermediateModel); - val eRefType = eRef.getEReferenceType; - val replElemsOfWrongType = newElements.filter[!eRefType.isSuperTypeOf(it.eClass)]; - if(!replElemsOfWrongType.empty) { - throw new IllegalArgumentException("one or more elements are of wrong type, expected: " - + eRef.EReferenceType.name + ", actual: " + replElemsOfWrongType.map[eClass.name].join(', ')); - } - if( eRef.upperBound == 1 ) { - // single valued - if( newElements.length > 1 ) - throw new IllegalArgumentException("the single-valued reference "+eRef.name+" in class "+eRef.EContainingClass - + " is not able to hold "+newElements.length +" elements."); - if( newElements.length == 1 ) - { - if( !replace ) - throw new IllegalArgumentException("Cannot insert another element into a single-valued containment reference "+eRef.name+" in class "+eRef.EContainingClass ); - elementInIntermediateModel.eContainer.eSet(eRef,newElements.get(0)); - } else { - if( !replace ) - throw new IllegalArgumentException("Inserting zero elements with replace==false is pointless."); - // no element, so remove - if( eRef.isUnsettable ) - elementInIntermediateModel.eContainer.eUnset(eRef) - else - elementInIntermediateModel.eContainer.eSet(eRef,null) - } - - } else { - // multi-valued - val l = elementInIntermediateModel.eContainer.eGet(eRef) as List; // c.f. type check above - var idx = l.indexOf(elementInIntermediateModel); - if(replace) { - l.remove(idx); - } else { - // note: before/after only applicable if !replace - if(after) { - idx++; // always safe to increment, because we know there exists an element at index 'idx' - } - } - l.addAll(idx, newElements); - } - } - - /** Retrieves the EReference of the Container. Throws Exceptions if a) not part of the IM or b) not contained anywhere*/ - def private static EReference checkedContainmentFeature(EObject elementInIntermediateModel) { - if(!TranspilerUtils.isIntermediateModelElement(elementInIntermediateModel)) { - throw new IllegalArgumentException("not an element in the intermediate model: " + elementInIntermediateModel); - } - val eRef = elementInIntermediateModel.eContainmentFeature; - if(eRef===null) { - throw new IllegalArgumentException("element is not contained anywhere"); - } - return eRef; - } - - /** - * Rename the given symbol table entry and all named elements in the intermediate model that are using this name. - * During AST transformations in the transpiler, the 'name' property of a symbol table entry should never be changed - * directly, but this operation should be used instead. - *

- * WARNING: renaming is currently only implemented partially and used only in a single, very specific use - * case; if renaming is required in the future, then the implementation of this method has to be complemented! - */ - def public static void rename(TranspilerState state, SymbolTableEntry entry, String newName) { - SymbolTableManagement.rename(state, entry, newName ); - } - - /** - * Copy a subtree of the intermediate model. - */ - def public static T copy(TranspilerState state, T elementInIM) { - // create a copy with a special copier to take care of reference ReferencingElement_IM#rewiredTarget - val copier = new IM2IMCopier(); - val result = copier.copy(elementInIM); - copier.copyReferences(); - // copy tracing information - state.tracer.copyTrace(elementInIM, result); // note: copying trace for all nodes would be more fine grained - return result as T; - } - - private static final class IM2IMCopier extends EcoreUtil.Copier { - private static final EReference eRef__ReferencingElement_IM__rewiredTarget = ImPackage.eINSTANCE.referencingElement_IM_RewiredTarget; - - override protected copyReference(EReference eReference, EObject eObject, EObject copyEObject) { - if(eReference===eRef__ReferencingElement_IM__rewiredTarget) { - (copyEObject as ReferencingElement_IM).rewiredTarget = (eObject as ReferencingElement_IM).rewiredTarget; - } else { - super.copyReference(eReference, eObject, copyEObject); - } - } - - } -} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/assistants/TypeAssistant.java b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/assistants/TypeAssistant.java new file mode 100644 index 0000000000..2170c6c66e --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/assistants/TypeAssistant.java @@ -0,0 +1,228 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.assistants; + +import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4ObjectType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.symbolObjectType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.flatten; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.forall; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.AnnotationList; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.transpiler.AbstractTranspiler; +import org.eclipse.n4js.transpiler.InformationRegistry; +import org.eclipse.n4js.transpiler.TransformationAssistant; +import org.eclipse.n4js.transpiler.TranspilerState; +import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.transpiler.utils.ConcreteMembersOrderedForTranspiler; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.validation.JavaScriptVariantHelper; + +import com.google.inject.Inject; + +/** + */ +public class TypeAssistant extends TransformationAssistant { + + @Inject + private JavaScriptVariantHelper jsVariantHelper; + + /** + * Some assertions related to {@link N4ClassifierDeclaration}s that apply to several transformations and are + * therefore factored out into this helper method. + */ + public void assertClassifierPreConditions() { + if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { + List allClassifierDecls = collectNodes(getState().im, + N4ClassifierDeclaration.class, false); + assertTrue("all classifier declarations must have an original defined type", + forall(allClassifierDecls, cd -> getState().info.getOriginalDefinedType(cd) != null)); + + assertTrue("all class declarations must have a superClassRef pointing to a TClass (if non-null)", + forall(filterNull(map(filter(allClassifierDecls, N4ClassDeclaration.class), + cd -> cd.getSuperClassRef())), + scr -> { + TypeRef tRef = getState().info.getOriginalProcessedTypeRef(scr); + Type originalDeclType = tRef == null ? null : tRef.getDeclaredType(); + return originalDeclType instanceof TClass; + })); + + assertTrue( + "all classifier declarations must have all implementedOrExtendedInterfaceRefs pointing to a TInterface", + forall(flatten(map(allClassifierDecls, + cd -> cd.getImplementedOrExtendedInterfaceRefs())), + ir -> { + TypeRef tRef = getState().info.getOriginalProcessedTypeRef(ir); + Type originalDeclType = tRef == null ? null : tRef.getDeclaredType(); + return originalDeclType instanceof TInterface; + })); + + } + } + + /** + * Same as {@link InformationRegistry#getOriginalProcessedTypeRef(TypeReferenceNode)}. + */ + public TypeRef getOriginalOrContainedTypeRef(TypeReferenceNode typeRefNodeInIM) { + TypeRef originalTypeRef = getState().info.getOriginalProcessedTypeRef(typeRefNodeInIM); + if (originalTypeRef != null) { + return originalTypeRef; + } + // note: typeRefNodeInIM.getTypeRefInAST() will always be 'null', so no point in using that + return null; + } + + /***/ + // keep aligned to following method! + public SymbolTableEntryOriginal getOriginalDeclaredTypeSTE(TypeReferenceNode typeRefNodeInIM) { + TypeRef typeRef = getOriginalOrContainedTypeRef(typeRefNodeInIM); + Type declType = typeRef == null ? null : typeRef.getDeclaredType(); + if (declType != null) { + return getSymbolTableEntryOriginal(declType, true); + } + return null; + } + + /***/ + // keep aligned to previous method! + public Type getOriginalDeclaredType(TypeReferenceNode typeRefNodeInIM) { + TypeRef typeRef = getOriginalOrContainedTypeRef(typeRefNodeInIM); + return typeRef == null ? null : typeRef.getDeclaredType(); + } + + /** + * Returns symbol table entry for super class of given class declaration. + */ + public SymbolTableEntryOriginal getSuperClassSTE(N4ClassDeclaration classDecl) { + TypeReferenceNode superClassRef = classDecl == null ? null : classDecl.getSuperClassRef(); + if (superClassRef != null) { + SymbolTableEntryOriginal superClassSTE = getOriginalDeclaredTypeSTE(superClassRef); + if (superClassSTE != null) { + return superClassSTE; + } + } + return getSymbolTableEntryOriginal(n4ObjectType(getState().G), true); + } + + /** + * Returns super interfaces (i.e. implemented or extended interfaces) of given classifier. + */ + public List getSuperInterfacesSTEs(N4ClassifierDeclaration classifierDecl) { + List> superIfcRefNodes; + + if (classifierDecl instanceof N4ClassDeclaration) { + superIfcRefNodes = ((N4ClassDeclaration) classifierDecl).getImplementedInterfaceRefs(); + } else if (classifierDecl instanceof N4InterfaceDeclaration) { + superIfcRefNodes = ((N4InterfaceDeclaration) classifierDecl).getSuperInterfaceRefs(); + } else { + throw new IllegalStateException("unsupported subclass of N4ClassifierDeclaration: " + + (classifierDecl == null ? null : classifierDecl.getName())); + } + return toList(filterNull(map(superIfcRefNodes, si -> getOriginalDeclaredTypeSTE(si)))); + } + + /** + * Tells if the given classifier is declared on top level. + */ + public boolean isTopLevel(TypeDefiningElement typeDef) { + EObject parent = typeDef.eContainer(); + while (parent instanceof ExportDeclaration || parent instanceof AnnotationList) { + parent = parent.eContainer(); + } + return parent instanceof Script; + } + + /** + * Tells if the given type is defined in an N4JSD file. + *

+ * WARNING: for interfaces it is not enough to check {@link TInterface#isExternal()}, for this purpose, because + * structural interfaces in N4JSD files need not be declared external! + */ + public boolean inN4JSD(Type type) { + return jsVariantHelper.isExternalMode(type); + } + + /** + * For a member name that represents a symbol, such as #iterator, this method will return a property + * access expression that will evaluate to the corresponding symbol, e.g. Symbol.iterator. + */ + public ParameterizedPropertyAccessExpression_IM getMemberNameAsSymbol(String memberName) { + if (!memberName.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX)) { + throw new IllegalArgumentException("given member name does not denote a symbol"); + } + return _PropertyAccessExpr( + getSymbolTableEntryOriginal(symbolObjectType(getState().G), true), + getSymbolTableEntryInternal(memberName.substring(1), true)); + } + + /** + * Returns an instance of {@link ConcreteMembersOrderedForTranspiler} for the given classifier, using a cached + * instance if available. + */ + public ConcreteMembersOrderedForTranspiler getOrCreateCMOFT(TClassifier classifier) { + ConcreteMembersOrderedForTranspiler cachedCMOFT = getState().info.getCachedCMOFT(classifier); + if (cachedCMOFT != null) { + return cachedCMOFT; + } else { + ConcreteMembersOrderedForTranspiler newCMOFT = ConcreteMembersOrderedForTranspiler.create(getState(), + classifier); + getState().info.cacheCMOFT(classifier, newCMOFT); + return newCMOFT; + } + } + + /** + * From a given {@link FunctionDefinition} of the IM, this methods returns the {@link TypeRef} of the return type. + */ + public TypeRef getReturnTypeRef(TranspilerState state, FunctionDefinition funDef) { + EObject astNode = state.tracer.getOriginalASTNode(funDef); + if (astNode instanceof FunctionDefinition) { + TFunction tFunction = ((FunctionDefinition) astNode).getDefinedFunction(); + if (tFunction != null) { + TypeRef outerReturnTypeRef = tFunction.getReturnTypeRef(); + if (outerReturnTypeRef == null) { + // If you get an exception here: a transformation might have created an async and/or generator + // FunctionDefinition without the expected Promise<...> / [Async]Generator<...> return type + // (therefore the above call to method #hasExpectedSpecialReturnType() returned false); + // automatically deriving the outer from an inner return type is not supported for + // FunctionDefinitions created by transformations! + throw new IllegalStateException( + "unable to obtain outer return type of function from TModule"); + } + return outerReturnTypeRef; + } + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/assistants/TypeAssistant.xtend b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/assistants/TypeAssistant.xtend deleted file mode 100644 index 236867c783..0000000000 --- a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/assistants/TypeAssistant.xtend +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.assistants - -import com.google.inject.Inject -import java.util.List -import org.eclipse.n4js.n4JS.AnnotationList -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.n4JS.TypeReferenceNode -import org.eclipse.n4js.transpiler.AbstractTranspiler -import org.eclipse.n4js.transpiler.InformationRegistry -import org.eclipse.n4js.transpiler.TransformationAssistant -import org.eclipse.n4js.transpiler.TranspilerState -import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.transpiler.utils.ConcreteMembersOrderedForTranspiler -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.validation.JavaScriptVariantHelper - -import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - */ -class TypeAssistant extends TransformationAssistant { - - @Inject private JavaScriptVariantHelper jsVariantHelper; - - /** - * Some assertions related to {@link N4ClassifierDeclaration}s that apply to several transformations and are - * therefore factored out into this helper method. - */ - def public void assertClassifierPreConditions() { - if (AbstractTranspiler.DEBUG_PERFORM_ASSERTIONS) { - val allClassifierDecls = collectNodes(state.im, N4ClassifierDeclaration, false); - assertTrue("all classifier declarations must have an original defined type", - allClassifierDecls.forall[state.info.getOriginalDefinedType(it)!==null]); - assertTrue("all class declarations must have a superClassRef pointing to a TClass (if non-null)", - allClassifierDecls.filter(N4ClassDeclaration).map[superClassRef].filterNull - .forall[ - val originalDeclType = state.info.getOriginalProcessedTypeRef(it)?.declaredType; - return originalDeclType instanceof TClass; - ]); - assertTrue("all classifier declarations must have all implementedOrExtendedInterfaceRefs pointing to a TInterface", - allClassifierDecls.map[implementedOrExtendedInterfaceRefs].flatten - .forall[ - val originalDeclType = state.info.getOriginalProcessedTypeRef(it)?.declaredType; - return originalDeclType instanceof TInterface; - ]); - - } - } - - /** - * Same as {@link InformationRegistry#getOriginalProcessedTypeRef(TypeReferenceNode)}. - */ - def public TypeRef getOriginalOrContainedTypeRef(TypeReferenceNode typeRefNodeInIM) { - val originalTypeRef = state.info.getOriginalProcessedTypeRef(typeRefNodeInIM); - if (originalTypeRef !== null) { - return originalTypeRef; - } - // note: typeRefNodeInIM.getTypeRefInAST() will always be 'null', so no point in using that - return null; - } - - // keep aligned to following method! - def public SymbolTableEntryOriginal getOriginalDeclaredTypeSTE(TypeReferenceNode typeRefNodeInIM) { - val typeRef = getOriginalOrContainedTypeRef(typeRefNodeInIM); - val declType = typeRef?.declaredType; - if (declType !== null) { - return getSymbolTableEntryOriginal(declType, true); - } - return null; - } - - // keep aligned to previous method! - def public Type getOriginalDeclaredType(TypeReferenceNode typeRefNodeInIM) { - val typeRef = getOriginalOrContainedTypeRef(typeRefNodeInIM); - return typeRef?.declaredType; - } - - /** - * Returns symbol table entry for super class of given class declaration. - */ - def public SymbolTableEntryOriginal getSuperClassSTE(N4ClassDeclaration classDecl) { - val superClassRef = classDecl?.superClassRef; - if (superClassRef !== null) { - val superClassSTE = getOriginalDeclaredTypeSTE(superClassRef); - if(superClassSTE !== null) { - return superClassSTE; - } - } - return getSymbolTableEntryOriginal(state.G.n4ObjectType, true); - } - - /** - * Returns super interfaces (i.e. implemented or extended interfaces) of given classifier. - */ - def public List getSuperInterfacesSTEs(N4ClassifierDeclaration classifierDecl) { - val superIfcRefNodes = switch(classifierDecl) { - N4ClassDeclaration: classifierDecl.implementedInterfaceRefs - N4InterfaceDeclaration: classifierDecl.superInterfaceRefs - default: throw new IllegalStateException("unsupported subclass of N4ClassifierDeclaration: " + classifierDecl?.name) - } - return superIfcRefNodes - .map[getOriginalDeclaredTypeSTE(it)] - .filterNull - .toList; - } - - /** - * Tells if the given classifier is declared on top level. - */ - def public boolean isTopLevel(TypeDefiningElement typeDef) { - var parent = typeDef.eContainer; - while(parent instanceof ExportDeclaration || parent instanceof AnnotationList) { - parent = parent.eContainer; - } - return parent instanceof Script; - } - - /** - * Tells if the given type is defined in an N4JSD file. - *

- * WARNING: for interfaces it is not enough to check {@link TInterface#isExternal()}, for this purpose, - * because structural interfaces in N4JSD files need not be declared external! - */ - def public boolean inN4JSD(Type type) { - return jsVariantHelper.isExternalMode(type); - } - - /** - * For a member name that represents a symbol, such as #iterator, this method will return a property - * access expression that will evaluate to the corresponding symbol, e.g. Symbol.iterator. - */ - def public ParameterizedPropertyAccessExpression_IM getMemberNameAsSymbol(String memberName) { - if(!memberName.startsWith(N4JSLanguageUtils.SYMBOL_IDENTIFIER_PREFIX)) { - throw new IllegalArgumentException("given member name does not denote a symbol"); - } - return _PropertyAccessExpr( - getSymbolTableEntryOriginal(state.G.symbolObjectType, true), - getSymbolTableEntryInternal(memberName.substring(1), true) - ); - } - - /** - * Returns an instance of {@link ConcreteMembersOrderedForTranspiler} for the given classifier, using a cached - * instance if available. - */ - def public ConcreteMembersOrderedForTranspiler getOrCreateCMOFT(TClassifier classifier) { - val cachedCMOFT = state.info.getCachedCMOFT(classifier); - if(cachedCMOFT!==null) { - return cachedCMOFT; - } else { - val newCMOFT = ConcreteMembersOrderedForTranspiler.create(state, classifier); - state.info.cacheCMOFT(classifier, newCMOFT); - return newCMOFT; - } - } - - /** - * From a given {@link FunctionDefinition} of the IM, this methods returns the {@link TypeRef} of the return type. - */ - def public TypeRef getReturnTypeRef(TranspilerState state, FunctionDefinition funDef) { - val astNode = state.tracer.getOriginalASTNode(funDef); - if (astNode instanceof FunctionDefinition) { - val tFunction = astNode.getDefinedFunction(); - if (tFunction !== null) { - val outerReturnTypeRef = tFunction.getReturnTypeRef(); - if (outerReturnTypeRef === null) { - // If you get an exception here: a transformation might have created an async and/or generator - // FunctionDefinition without the expected Promise<...> / [Async]Generator<...> return type - // (therefore the above call to method #hasExpectedSpecialReturnType() returned false); - // automatically deriving the outer from an inner return type is not supported for - // FunctionDefinitions created by transformations! - throw new IllegalStateException( - "unable to obtain outer return type of function from TModule"); - } - return outerReturnTypeRef; - } - } - return null; - } -} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/utils/TranspilerDebugUtils.java b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/utils/TranspilerDebugUtils.java new file mode 100644 index 0000000000..db34156653 --- /dev/null +++ b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/utils/TranspilerDebugUtils.java @@ -0,0 +1,233 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.transpiler.utils; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.forall; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.findFirst; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedElement; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.transpiler.TranspilerState; +import org.eclipse.n4js.transpiler.im.Script_IM; +import org.eclipse.n4js.transpiler.im.SymbolTable; +import org.eclipse.n4js.transpiler.im.SymbolTableEntry; +import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal; +import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.xtext.EcoreUtil2; + +/** + * Some utilities for transpiler debugging, mainly dumping a {@link TranspilerState} to {@code stdout}, etc. + */ +public class TranspilerDebugUtils { + + /** + * Perform some consistency checks on the transpiler state. For example, this asserts that no node in the + * intermediate model has a direct cross-reference to the original AST or an original TModule element. + */ + public void validateState(TranspilerState state, boolean allowDanglingSecondaryReferencesInSTEs) + throws AssertionError { + // IM should not contain entities from n4js.xcore / TypeRefs.xcore for which a replacement in IM.xcore exists + List replacedEClasses = List.of( + N4JSPackage.eINSTANCE.getParameterizedPropertyAccessExpression(), + N4JSPackage.eINSTANCE.getIdentifierRef(), + TypeRefsPackage.eINSTANCE.getParameterizedTypeRef()); + EObject badObject = findFirst(state.im.eAllContents(), elem -> replacedEClasses.contains(elem.eClass())); + assertNull("intermediate model should not contain objects of type " + + (badObject != null + ? badObject.eClass() != null ? badObject.eClass().getName() : "null" + : "null"), + badObject); + // no cross reference in the IM to an element outside the IM (except in SymbolTableEntry) + assertFalse( + "intermediate model should not have a cross-reference to an element outside the intermediate model" + + " (except for SymbolTableEntry)", + exists(filter(state.im.eAllContents(), elem -> !(allowedCrossRefToOutside(elem))), + elem -> hasCrossRefToOutsideOf(elem, state))); + + // symbol table should exist + SymbolTable st = state.im.getSymbolTable(); + assertNotNull("intermediate model should have a symbol table", st); + // check entries in symbol table + for (SymbolTableEntry ste : st.getEntries()) { + if (ste instanceof SymbolTableEntryOriginal) { + assertNotNull("originalTarget should not be null", + ((SymbolTableEntryOriginal) ste).getOriginalTarget()); + assertFalse("originalTarget should not be an element in the intermediate model", + isElementInIntermediateModelOf(((SymbolTableEntryOriginal) ste).getOriginalTarget(), state)); + } + if (allowDanglingSecondaryReferencesInSTEs) { + // we allow dangling references in this case for the time being to make replacements in IM faster + // -> no checks here + // TODO consider disallowing dangling secondary references in symbol table entries + } else { + assertTrue("all elementsOfThisName should be elements in the intermediate model", + forall(ste.getElementsOfThisName(), elem -> isElementInIntermediateModelOf(elem, state))); + assertTrue("all referencingElements should be elements in the intermediate model", + forall(ste.getReferencingElements(), elem -> isElementInIntermediateModelOf(elem, state))); + if (ste instanceof SymbolTableEntryOriginal) { + if (((SymbolTableEntryOriginal) ste).getImportSpecifier() != null) { + assertTrue("importSpecifier should be an element in the intermediate model", + isElementInIntermediateModelOf(((SymbolTableEntryOriginal) ste).getImportSpecifier(), + state)); + } + } + } + } + } + + private boolean allowedCrossRefToOutside(EObject eobj) { + if (eobj instanceof SymbolTableEntry) { + return true; + } + return false; + } + + private static boolean hasCrossRefToOutsideOf(EObject elementInIntermediateModel, TranspilerState state) { + return exists(elementInIntermediateModel.eCrossReferences(), + cref -> !isElementInIntermediateModelOf(cref, state)); + } + + private static boolean isElementInIntermediateModelOf(EObject eobj, TranspilerState state) { + return EcoreUtil2.getContainerOfType(eobj, Script_IM.class) == state.im; + } + + /** Asserts {@code value} to be true and throws an {@link AssertionError} otherwise. */ + public static void assertTrue(String message, boolean value) throws AssertionError { + if (!value) + assertionFailure(message); + } + + /** Asserts {@code value} to be false and throws an {@link AssertionError} otherwise. */ + public static void assertFalse(String message, boolean value) throws AssertionError { + if (value) + assertionFailure(message); + } + + /** Asserts {@code value} to be null and throws an {@link AssertionError} otherwise. */ + public static void assertNull(String message, Object value) throws AssertionError { + if (value != null) + assertionFailure(message); + } + + /** Asserts {@code value} to be non-null and throws an {@link AssertionError} otherwise. */ + public static void assertNotNull(String message, Object value) throws AssertionError { + if (value == null) + assertionFailure(message); + } + + private static void assertionFailure(String message) throws AssertionError { + AssertionError ex = new AssertionError(message); + ex.printStackTrace(); // make sure we see this on the console even if exceptions are eaten up by someone + throw ex; + } + + /***/ + public static void dump(TranspilerState state) { + System.out.println(dumpToString(state)); + } + + /***/ + public static String dumpToString(TranspilerState state) { + StringWriter w = new StringWriter(); + dump(state, w); + return w.toString(); + } + + /** + * Dumps the transpiler state to the given writer. + */ + public static void dump(TranspilerState state, Writer out) { + PrintWriter w = new PrintWriter(out); + dump(w, state.im, 0); + } + + private static void dump(PrintWriter w, EObject obj, int indentLevel) { + indent(w, indentLevel); + printObj(w, obj, true, indentLevel); + w.println(); + + for (EObject child : obj.eContents()) { + dump(w, child, indentLevel + 1); + } + } + + private static void printObj(PrintWriter w, EObject obj, boolean includeCrossRefs, int indentLevel) { + w.print(obj.eClass().getName()); + if (obj instanceof IdentifiableElement || obj instanceof NamedElement || obj instanceof VariableDeclaration + || obj instanceof ImportSpecifier || obj instanceof SymbolTableEntry) { + w.print(" @" + Integer.toHexString(obj.hashCode())); + } + String objStr = obj.toString(); + int idx = objStr.indexOf("("); + if (idx >= 0) { + w.print(" " + objStr.substring(idx)); + } + if (includeCrossRefs) { + List crossRefs = toList(filter(obj.eClass().getEAllReferences(), ref -> !ref.isContainment())); + if (!crossRefs.isEmpty()) { + for (EReference ref : crossRefs) { + w.println(); + indent(w, indentLevel); + w.print("--" + ref.getName() + "--> "); + if (ref.isMany()) { + w.print("["); + @SuppressWarnings("unchecked") + EList targets = (EList) obj.eGet(ref); + Iterator iter = targets.iterator(); + while (iter.hasNext()) { + EObject currTarget = iter.next(); + if (currTarget != null) { + printObj(w, currTarget, false, indentLevel); + } else { + w.print("null"); + } + if (iter.hasNext()) { + w.print(", "); + } + } + w.print("]"); + } else { + EObject target = (EObject) obj.eGet(ref); + if (target != null) { + printObj(w, target, false, indentLevel); + } else { + w.print("null"); + } + } + } + } + } + } + + private static void indent(PrintWriter w, int indentLevel) { + for (int i = 0; i < indentLevel; i++) { + w.print("\t"); + } + } +} diff --git a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/utils/TranspilerDebugUtils.xtend b/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/utils/TranspilerDebugUtils.xtend deleted file mode 100644 index 114ef77aab..0000000000 --- a/plugins/org.eclipse.n4js.transpiler/src/org/eclipse/n4js/transpiler/utils/TranspilerDebugUtils.xtend +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.transpiler.utils - -import java.io.PrintWriter -import java.io.StringWriter -import java.io.Writer -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedElement -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.transpiler.InformationRegistry -import org.eclipse.n4js.transpiler.TranspilerState -import org.eclipse.n4js.transpiler.im.Script_IM -import org.eclipse.n4js.transpiler.im.SymbolTableEntry -import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal -import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.xtext.EcoreUtil2 - -/** - * Some utilities for transpiler debugging, mainly dumping a {@link TranspilerState} to {@code stdout}, etc. - */ -class TranspilerDebugUtils { - - /** - * Perform some consistency checks on the transpiler state. For example, this asserts that no node in the - * intermediate model has a direct cross-reference to the original AST or an original TModule element. - */ - def public void validateState(TranspilerState state, boolean allowDanglingSecondaryReferencesInSTEs) throws AssertionError { - // IM should not contain entities from n4js.xcore / TypeRefs.xcore for which a replacement in IM.xcore exists - val replacedEClasses = #[ - N4JSPackage.eINSTANCE.parameterizedPropertyAccessExpression, - N4JSPackage.eINSTANCE.identifierRef, - TypeRefsPackage.eINSTANCE.parameterizedTypeRef - ]; - val badObject = state.im.eAllContents.findFirst[replacedEClasses.contains(it.eClass)]; - assertNull("intermediate model should not contain objects of type " + badObject?.eClass?.name, badObject); - // no cross reference in the IM to an element outside the IM (except in SymbolTableEntry) - assertFalse( - "intermediate model should not have a cross-reference to an element outside the intermediate model" - + " (except for SymbolTableEntry)", - state.im.eAllContents.filter[!(allowedCrossRefToOutside(state.info))].exists[hasCrossRefToOutsideOf(state)]); - // symbol table should exist - val st = state.im.symbolTable; - assertNotNull("intermediate model should have a symbol table", st); - // check entries in symbol table - for(ste : st.entries) { - if(ste instanceof SymbolTableEntryOriginal) { - assertNotNull("originalTarget should not be null", ste.originalTarget); - assertFalse("originalTarget should not be an element in the intermediate model", - ste.originalTarget.isElementInIntermediateModelOf(state)); - } - if(allowDanglingSecondaryReferencesInSTEs) { - // we allow dangling references in this case for the time being to make replacements in IM faster - // -> no checks here - // TODO consider disallowing dangling secondary references in symbol table entries - } else { - assertTrue("all elementsOfThisName should be elements in the intermediate model", - ste.elementsOfThisName.forall[isElementInIntermediateModelOf(state)]); - assertTrue("all referencingElements should be elements in the intermediate model", - ste.referencingElements.forall[isElementInIntermediateModelOf(state)]); - if(ste instanceof SymbolTableEntryOriginal) { - if(ste.importSpecifier!==null) { - assertTrue("importSpecifier should be an element in the intermediate model", - ste.importSpecifier.isElementInIntermediateModelOf(state)); - } - } - } - } - } - - def private allowedCrossRefToOutside(EObject eobj, InformationRegistry info) { - switch eobj { - SymbolTableEntry: true - default: false - } - } - - def private static boolean hasCrossRefToOutsideOf(EObject elementInIntermediateModel, TranspilerState state) { - return elementInIntermediateModel.eCrossReferences.exists[ - if(!isElementInIntermediateModelOf(state)) { - return true; - } - return false; - ]; - } - - def private static boolean isElementInIntermediateModelOf(EObject eobj, TranspilerState state) { - return EcoreUtil2.getContainerOfType(eobj,Script_IM)===state.im - } - - /** Asserts {@code value} to be true and throws an {@link AssertionError} otherwise. */ - def public static void assertTrue(String message, boolean value) throws AssertionError { - if (!value) assertionFailure(message); - } - - /** Asserts {@code value} to be false and throws an {@link AssertionError} otherwise. */ - def public static void assertFalse(String message, boolean value) throws AssertionError { - if (value) assertionFailure(message); - } - - /** Asserts {@code value} to be null and throws an {@link AssertionError} otherwise. */ - def public static void assertNull(String message, Object value) throws AssertionError { - if (value!==null) assertionFailure(message); - } - - /** Asserts {@code value} to be non-null and throws an {@link AssertionError} otherwise. */ - def public static void assertNotNull(String message, Object value) throws AssertionError { - if (value===null) assertionFailure(message); - } - - def private static void assertionFailure(String message) throws AssertionError { - val ex = new AssertionError(message); - ex.printStackTrace; // make sure we see this on the console even if exceptions are eaten up by someone - throw ex; - } - - def public static void dump(TranspilerState state) { - println(dumpToString(state)); - } - def public static String dumpToString(TranspilerState state) { - val w = new StringWriter(); - dump(state, w); - return w.toString; - } - /** - * Dumps the transpiler state to the given writer. - */ - def public static void dump(TranspilerState state, Writer out) { - val w = new PrintWriter(out); - w.dump(state.im, 0); - } - - def private static void dump(PrintWriter w, EObject obj, int indentLevel) { - w.indent(indentLevel); - w.printObj(obj, true, indentLevel); - w.println(); - - for(child : obj.eContents) { - w.dump(child, indentLevel+1); - } - } - - def private static void printObj(PrintWriter w, EObject obj, boolean includeCrossRefs, int indentLevel) { - w.print(obj.eClass.name); - if(obj instanceof IdentifiableElement || obj instanceof NamedElement || obj instanceof VariableDeclaration - || obj instanceof ImportSpecifier || obj instanceof SymbolTableEntry) { - w.print(' @'+Integer.toHexString(obj.hashCode)); - } - val objStr = obj.toString; - val idx = objStr.indexOf('('); - if(idx>=0) { - w.print(' ' + objStr.substring(idx)); - } - if(includeCrossRefs) { - val crossRefs = obj.eClass.getEAllReferences.filter[!containment]; - if(!crossRefs.empty) { - for(ref : crossRefs) { - w.println(); - w.indent(indentLevel); - w.print('--'+ref.name+'--> '); - if(ref.many) { - w.print('['); - val targets = obj.eGet(ref) as EList; - val iter = targets.iterator(); - while(iter.hasNext) { - val currTarget = iter.next; - if(currTarget!==null) { - w.printObj(currTarget, false, indentLevel); - } else { - w.print('null'); - } - if(iter.hasNext) { - w.print(', '); - } - } - w.print(']'); - } else { - val target = obj.eGet(ref) as EObject; - if(target!==null) { - w.printObj(target, false, indentLevel); - } else { - w.print('null'); - } - } - } - } - } - } - - def private static void indent(PrintWriter w, int indentLevel) { - for(i : 0 ..< indentLevel) { - w.print('\t'); - } - } -} diff --git a/plugins/org.eclipse.n4js.transpiler/xtend-gen/.gitignore b/plugins/org.eclipse.n4js.transpiler/xtend-gen/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/plugins/org.eclipse.n4js.transpiler/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/FindReferenceHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/FindReferenceHelper.java index 60828d33cd..c8dc10033e 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/FindReferenceHelper.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/FindReferenceHelper.java @@ -104,14 +104,14 @@ public URI getMemberRefURIInDestructuring(EObject instanceOrProxy) { public TMember getMemberInDestructuring(EObject instanceOrProxy) { // If the EObject is a variable in a destructuring, we add that variable DestructNode destructNode = DestructureUtils.getCorrespondingDestructNode(instanceOrProxy); - if (destructNode != null && destructNode.getAssignedElem() != null) { - TypableElement assignedElem = destructNode.getAssignedElem(); + if (destructNode != null && destructNode.assignedElem != null) { + TypableElement assignedElem = destructNode.assignedElem; TypeRef type = typeSystem.type(RuleEnvironmentExtensions.newRuleEnvironment(assignedElem), assignedElem); MemberCollector memberCollector = containerTypesHelper.fromContext(assignedElem); Type declaredType = type.getDeclaredType(); if (declaredType instanceof ContainerType) { - String name = destructNode.getPropName(); + String name = destructNode.propName; TMember member = memberCollector.findMember((ContainerType) declaredType, name, true, false); if (member == null) { member = memberCollector.findMember((ContainerType) declaredType, name, false, false); diff --git a/testhelpers/org.eclipse.n4js.ide.tests.helper/src/org/eclipse/n4js/ide/tests/helper/server/TestWorkspaceManager.java b/testhelpers/org.eclipse.n4js.ide.tests.helper/src/org/eclipse/n4js/ide/tests/helper/server/TestWorkspaceManager.java index 8944490d9c..27371cf37e 100644 --- a/testhelpers/org.eclipse.n4js.ide.tests.helper/src/org/eclipse/n4js/ide/tests/helper/server/TestWorkspaceManager.java +++ b/testhelpers/org.eclipse.n4js.ide.tests.helper/src/org/eclipse/n4js/ide/tests/helper/server/TestWorkspaceManager.java @@ -492,7 +492,11 @@ public Project createTestOnDisk(Path destination, Workspace workspace) { } destination.toFile().mkdirs(); - workspace.create(destination); + try { + workspace.create(destination); + } catch (IOException e) { + throw new IllegalStateException(e); + } createdProject = workspace.getProjects().get(0); // TODO return createdProject; diff --git a/testhelpers/org.eclipse.n4js.tests.helper/.classpath b/testhelpers/org.eclipse.n4js.tests.helper/.classpath index 8334a30eac..3628e33687 100644 --- a/testhelpers/org.eclipse.n4js.tests.helper/.classpath +++ b/testhelpers/org.eclipse.n4js.tests.helper/.classpath @@ -1,6 +1,5 @@ - diff --git a/testhelpers/org.eclipse.n4js.tests.helper/build.properties b/testhelpers/org.eclipse.n4js.tests.helper/build.properties index aaedd5b241..17daa5b49c 100644 --- a/testhelpers/org.eclipse.n4js.tests.helper/build.properties +++ b/testhelpers/org.eclipse.n4js.tests.helper/build.properties @@ -1,5 +1,4 @@ -source.. = src/,\ - xtend-gen/ +source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff --git a/testhelpers/org.eclipse.n4js.tests.helper/pom.xml b/testhelpers/org.eclipse.n4js.tests.helper/pom.xml index 5479f36127..984e0e897b 100644 --- a/testhelpers/org.eclipse.n4js.tests.helper/pom.xml +++ b/testhelpers/org.eclipse.n4js.tests.helper/pom.xml @@ -30,10 +30,6 @@ Contributors: org.apache.maven.plugins maven-clean-plugin - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Class.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Class.java new file mode 100644 index 0000000000..fdc4242ae2 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Class.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import java.util.LinkedList; +import java.util.List; + +import com.google.common.base.Strings; + +/** + * Generates the code for a class. + */ +public class Class extends Classifier { + String superClass; + List implementedInterfaces = new LinkedList<>(); + + /** + * Creates a new instance with the given parameters. + * + * @param name + * the name of the class + */ + public Class(String name) { + super(name); + } + + /** + * Sets the super class. + * + * @param superClass + * the super class or interface. + */ + public Class setSuperClass(Class superClass) { + return setSuperClass(superClass.getName()); + } + + /** + * Sets the super class. + * + * @param superClass + * the name of the super class or interface. + */ + public Class setSuperClass(String superClass) { + this.superClass = superClass; + return this; + } + + /** + * Adds an interface implemented by the class to be built. + * + * @param implementedInterface + * the name of the interface to implement + * + * @return this builder + */ + public Class addInterface(Interface implementedInterface) { + return addInterface(implementedInterface.getName()); + } + + /** + * Adds an interface implemented by the class to be built. + * + * @param implementedInterface + * the interface to implement + */ + public Class addInterface(String implementedInterface) { + implementedInterfaces.add(implementedInterface); + return this; + } + + @Override + protected String generateType() { + return "class "; + } + + @Override + protected String generateTypeRelations() { + return generateSuperClass() + generateImplementedInterfaces(); + } + + private String generateSuperClass() { + if (!Strings.isNullOrEmpty(superClass)) { + return " extends " + superClass; + } + return ""; + } + + private String generateImplementedInterfaces() { + String result = ""; + if (!implementedInterfaces.isEmpty()) { + result += " implements "; + boolean isFirst = true; + for (String inf : implementedInterfaces) { + if (!isFirst) { + result += ", "; + } + result += inf; + isFirst = false; + } + } + return result; + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Class.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Class.xtend deleted file mode 100644 index c11d7d8327..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Class.xtend +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.util.List - -/** - * Generates the code for a class. - */ -class Class extends Classifier { - String superClass; - List implementedInterfaces; - - /** - * Creates a new instance with the given parameters. - * - * @param name the name of the class - */ - public new(String name) { - super(name) - } - - /** - * Sets the super class. - * - * @param superClass the super class or interface. - */ - public def Class setSuperClass(Class superClass) { - return setSuperClass(superClass.name) - } - - /** - * Sets the super class. - * - * @param superClass the name of the super class or interface. - */ - public def Class setSuperClass(String superClass) { - this.superClass = superClass; - return this; - } - - /** - * Adds an interface implemented by the class to be built. - * - * @param implementedInterface the name of the interface to implement - * - * @return this builder - */ - public def Class addInterface(Interface implementedInterface) { - return addInterface(implementedInterface.name) - } - - /** - * Adds an interface implemented by the class to be built. - * - * @param implementedInterface the interface to implement - */ - public def Class addInterface(String implementedInterface) { - if (implementedInterfaces === null) - implementedInterfaces = newLinkedList(); - implementedInterfaces.add(implementedInterface); - return this; - } - - override protected def generateType() '''class ''' - - override protected def CharSequence generateTypeRelations() '''«generateSuperClass()»«generateImplementedInterfaces()»''' - - private def CharSequence generateSuperClass() '''«IF !superClass.nullOrEmpty» extends «superClass»«ENDIF»''' - - private def CharSequence generateImplementedInterfaces() '''«IF !implementedInterfaces.nullOrEmpty»«FOR i : implementedInterfaces BEFORE ' implements ' SEPARATOR ', '»«i»«ENDFOR»«ENDIF»''' -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Classifier.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Classifier.java new file mode 100644 index 0000000000..d616f3ce52 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Classifier.java @@ -0,0 +1,212 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.n4js.utils.Strings; + +/** + * Abstract base class for classifiers. + */ +abstract public class Classifier> extends Fragment { + /** + * Possible visibility modifiers for classifiers. + */ + public static enum Visibility { + PRIVATE, PROJECT, PUBLIC_INTERNAL, PUBLIC + } + + /** + * Extension methods for the {@link Visibility} enumeration. + */ + public static class VisibilityExtensions { + /** + * Builds a classifier name from the given name and visibility by appending an appropriate string to the given + * name. + * + * @param visibility + * the visibility value + * @param classifierName + * classifier name prefix + * + * @return the newly created classifier name + */ + static String makeName(Visibility visibility, String classifierName) { + return classifierName + getNameExtension(visibility); + } + + /** + * Returns an appropriate classifier name extension depending on the given visibility. + * + * @param visibility + * the visibility value + * + * @return the name extension + */ + static String getNameExtension(Visibility visibility) { + switch (visibility) { + case PRIVATE: + return "_private"; + case PROJECT: + return "_project"; + case PUBLIC_INTERNAL: + return "_public_internal"; + case PUBLIC: + return "_public"; + } + return ""; + } + + /** + * Builds an appropriate code fragment for the given classifier visibility. + * + * @param visibility + * the visibility value + * + * @return the code fragment + */ + static String generate(Visibility visibility) { + switch (visibility) { + case PRIVATE: + return "/* private */"; + case PROJECT: + return "export project"; + case PUBLIC_INTERNAL: + return "export @Internal public"; + case PUBLIC: + return "export public"; + } + return ""; + } + } + + Visibility visibility = Visibility.PRIVATE; + String name; + List> members = new LinkedList<>(); + + /** + * Creates a new classifier instance with the given name. + * + * @param name + * the name of the new classifier + */ + protected Classifier(String name) { + this.name = Objects.requireNonNull(name); + } + + /** + * Returns the name of this classifier. + * + * @return the name of this classifier + */ + public String getName() { + return name; + } + + /** + * Set the visibility to project. + */ + public T makeProjectVisible() { + return setVisibility(Visibility.PROJECT); + } + + /** + * Set the visibility to public @Internal. + */ + public T makePublicInternal() { + return setVisibility(Visibility.PUBLIC_INTERNAL); + } + + /** + * Set the visibility to public. + */ + public T makePublic() { + return setVisibility(Visibility.PUBLIC); + } + + /** + * Set the visibility. + * + * @param visibility + * the visibility to set + */ + @SuppressWarnings("unchecked") + public T setVisibility(Visibility visibility) { + this.visibility = visibility; + return (T) this; + } + + /** + * Add the given member to this builder. + * + * @param member + * the member to add + */ + @SuppressWarnings("unchecked") + public T addMember(Member member) { + members.add(Objects.requireNonNull(member)); + return (T) this; + } + + @Override + public String generate() { + String result = generateVisibility() + generateAbstract() + generateType() + name + generateTypeRelations(); + if (hasMembers()) { + result += " {\n\t" + generateMembers() + "\n}"; + } else { + result += " {}"; + } + return result; + } + + /** + * Generate an appropriate code fragment for this classifier's visibility. + * + * @return the generated visibility code fragment + */ + protected String generateVisibility() { + return VisibilityExtensions.generate(visibility) + " "; + } + + /** + * Generate the code fragments for each of this classifier's members. + * + * @return the generated member code fragment + */ + protected String generateMembers() { + return Strings.join("\n\t", m -> m.generate(), members); + } + + /** + * Generates a code fragment for the actual type of this classifier. + * + * @return the generated code fragment + */ + protected abstract CharSequence generateType(); + + /** + * Generates a code fragment for the type relations of this classifier, e.g. its base types or implemented + * interfaces. + */ + protected abstract CharSequence generateTypeRelations(); + + private boolean hasMembers() { + return members != null && !members.isEmpty(); + } + + @Override + public String toString() { + return generate().toString(); + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Classifier.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Classifier.xtend deleted file mode 100644 index 04f0bdc0a3..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Classifier.xtend +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.util.List -import java.util.Objects - -/** - * Abstract base class for classifiers. - */ -abstract class Classifier> extends Fragment { - /** - * Possible visibility modifiers for classifiers. - */ - public static enum Visibility { - PRIVATE, - PROJECT, - PUBLIC_INTERNAL, - PUBLIC - } - - /** - * Extension methods for the {@link Visibility} enumeration. - */ - public static class VisibilityExtensions { - /** - * Builds a classifier name from the given name and visibility by appending an appropriate string - * to the given name. - * - * @param visibility the visibility value - * @param the classifier name prefix - * - * @return the newly created classifier name - */ - static def String makeName(Visibility visibility, String classifierName) { - classifierName + visibility.nameExtension - } - - /** - * Returns an appropriate classifier name extension depending on the given visibility. - * - * @param visibility the visibility value - * - * @return the name extension - */ - static def String getNameExtension(Visibility visibility) { - switch visibility { - case PRIVATE: "_private" - case PROJECT: "_project" - case PUBLIC_INTERNAL: "_public_internal" - case PUBLIC: "_public" - } - } - - /** - * Builds an appropriate code fragment for the given classifier visibility. - * - * @param visibility the visibility value - * - * @return the code fragment - */ - static def String generate(Visibility visibility) { - switch visibility { - case PRIVATE: "/* private */" - case PROJECT: "export project" - case PUBLIC_INTERNAL: "export @Internal public" - case PUBLIC: "export public" - } - } - } - - Visibility visibility = Visibility.PRIVATE; - String name; - List> members; - - /** - * Creates a new classifier instance with the given name. - * - * @param name the name of the new classifier - */ - protected new(String name) { - this.name = Objects.requireNonNull(name); - } - - /** - * Returns the name of this classifier. - * - * @return the name of this classifier - */ - public def String getName() { - return name; - } - - /** - * Set the visibility to project. - */ - public def T makeProjectVisible() { - return setVisibility(Visibility.PROJECT); - } - - /** - * Set the visibility to public @Internal. - */ - public def T makePublicInternal() { - return setVisibility(Visibility.PUBLIC_INTERNAL); - } - - /** - * Set the visibility to public. - */ - public def T makePublic() { - return setVisibility(Visibility.PUBLIC); - } - - /** - * Set the visibility. - * - * @param visibility the visibility to set - */ - public def T setVisibility(Visibility visibility) { - this.visibility = visibility; - return this as T; - } - - /** - * Add the given member to this builder. - * - * @param member the member to add - */ - public def T addMember(Member member) { - if (members === null) - members = newLinkedList(); - members.add(Objects.requireNonNull(member)); - return this as T; - } - - override def generate() ''' - «generateVisibility()»«generateAbstract()»«generateType()»«name»«generateTypeRelations()» «IF !hasMembers»{}«ELSE»{ - «generateMembers()» - } - «ENDIF» - ''' - - /** - * Generate an appropriate code fragment for this classifier's visibility. - * - * @return the generated visibility code fragment - */ - protected def generateVisibility() '''«VisibilityExtensions.generate(visibility)» ''' - - /** - * Generate the code fragments for each of this classifier's members. - * - * @return the generated member code fragment - */ - protected def generateMembers() ''' - «FOR m : members» - «m.generate()» - «ENDFOR» - ''' - - /** - * Generates a code fragment for the actual type of this classifier. - * - * @return the generated code fragment - */ - protected abstract def CharSequence generateType() - - /** - * Generates a code fragment for the type relations of this classifier, e.g. - * its base types or implemented interfaces. - */ - protected abstract def CharSequence generateTypeRelations() - - private def boolean hasMembers() { - members !== null && !members.empty - } - - override public def String toString() { - return generate().toString(); - } -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Field.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Field.java new file mode 100644 index 0000000000..beba72450e --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Field.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import com.google.common.base.Strings; + +/** + * Generates code for a field of a {@link Classifier}. + */ +public class Field extends Member { + String fieldType; + String defaultValue; + + /** + * Creates a new field with the given values. + * + * @param name + * the name of this field + */ + public Field(String name) { + super(name); + } + + /** + * Sets the field type. + * + * @param fieldType + * the field type + */ + public Field setFieldType(String fieldType) { + this.fieldType = fieldType; + return this; + } + + /** + * Sets the default value or expression. + * + * @param defaultValue + * the default value + */ + public Field setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + @Override + protected String generateMember() { + String result = ""; + if (!Strings.isNullOrEmpty(fieldType)) { + result += fieldType + " "; + } + result += name; + if (!Strings.isNullOrEmpty(defaultValue)) { + result += " = " + defaultValue; + } + result += ";\n"; + return result; + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Field.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Field.xtend deleted file mode 100644 index 76e03d88c8..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Field.xtend +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -/** - * Generates code for a field of a {@link Classifier}. - */ -class Field extends Member { - String fieldType - String defaultValue - - /** - * Creates a new field with the given values. - * - * @param name the name of this field - */ - public new(String name) { - super(name) - } - - /** - * Sets the field type. - * - * @param fieldType the field type - */ - public def Field setFieldType(String fieldType) { - this.fieldType = fieldType; - return this; - } - - /** - * Sets the default value or expression. - * - * @param defaultValue the default value - */ - public def Field setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue - return this; - } - - override protected generateMember() ''' - «IF !fieldType.nullOrEmpty»«fieldType» «ENDIF»«name»«IF !defaultValue.nullOrEmpty» = «defaultValue»«ENDIF»; - ''' -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Folder.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Folder.java similarity index 52% rename from testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Folder.xtend rename to testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Folder.java index eccff9fa2a..5f04d1929b 100644 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Folder.xtend +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Folder.java @@ -4,109 +4,121 @@ * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html - * + * * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.tests.codegen +package org.eclipse.n4js.tests.codegen; -import java.io.File -import java.io.IOException -import java.util.List -import java.util.Objects +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; /** * Represents a source folder that has a name and contains modules. */ public class Folder { final public String name; - final public List modules = newLinkedList(); - final public List files = newLinkedList(); + final public List modules = new LinkedList<>(); + final public List files = new LinkedList<>(); final public boolean isSourceFolder; /** * Creates a new instance with the given parameters. - * - * @param name the name of this source folder + * + * @param name + * the name of this source folder */ - public new(String name) { + public Folder(String name) { this(name, true); } /** * Creates a new instance with the given parameters. * - * @param name the name of this source folder - * @param isSourceFolder iff true it will be listed as a source folder in the package.json + * @param name + * the name of this source folder + * @param isSourceFolder + * iff true it will be listed as a source folder in the package.json */ - public new(String name, boolean isSourceFolder) { + public Folder(String name, boolean isSourceFolder) { this.name = Objects.requireNonNull(name); this.isSourceFolder = isSourceFolder; } /** * Adds the given module to the source folder to created. - * - * @param module the module to add - * + * + * @param module + * the module to add + * * @return this source folder */ - public def addModule(Module module) { + public Folder addModule(Module module) { modules.add(Objects.requireNonNull(module)); return this; } /** * Returns a list of all modules of this source folder. - * + * * @return list of all modules of this source folder */ - public def List getModules() { + public List getModules() { return modules; } /** * Returns a list of all non-modules of this folder. - * + * * @return list of all non-modules of this folder */ - public def List getOtherFiles() { + public List getOtherFiles() { return files; } /** * Creates this source folder within the given parent directory, which must exist. - * - * This method first creates a new folder within the given parent directory, and then - * it creates all of its modules within that folder by calling their {@link Module#create(File)} - * function with the newly created folder as the parameter. - * - * @param parentDirectory a file representing the parent directory of this source folder + * + * This method first creates a new folder within the given parent directory, and then it creates all of its modules + * within that folder by calling their {@link Module#create(File)} function with the newly created folder as the + * parameter. + * + * @param parentDirectory + * a file representing the parent directory of this source folder */ - public def create(File parentDirectory) { + public void create(File parentDirectory) throws IOException { Objects.requireNonNull(parentDirectory); - if (!parentDirectory.exists) + if (!parentDirectory.exists()) { throw new IOException("Directory '" + parentDirectory + "' does not exist"); - if (!parentDirectory.directory) + } + if (!parentDirectory.isDirectory()) { throw new IOException("'" + parentDirectory + "' is not a directory"); + } - val File folder = new File(parentDirectory, name); + File folder = new File(parentDirectory, name); folder.mkdirs(); - for (module : modules) - module.create(folder) - for (file : files) - file.create(folder) + for (Module module : modules) { + module.create(folder); + } + for (OtherFile file : files) { + file.create(folder); + } } - override equals(Object o) { + @Override + public boolean equals(Object o) { if (o instanceof Folder) { - Objects.equals(name, o.name); + Objects.equals(name, ((Folder) o).name); } return false; } - override hashCode() { + @Override + public int hashCode() { return name.hashCode(); } } diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Fragment.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Fragment.java similarity index 54% rename from testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Fragment.xtend rename to testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Fragment.java index 6b42493568..da2283c257 100644 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Fragment.xtend +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Fragment.java @@ -8,18 +8,17 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.tests.codegen +package org.eclipse.n4js.tests.codegen; /** * Abstract base class for constructs that can be abstract or concrete. */ -abstract class Fragment> { +abstract public class Fragment> { /** * Possible values for abstractness. */ public static enum Abstract { - YES, - NO + YES, NO } /** @@ -27,46 +26,55 @@ public static enum Abstract { */ public static abstract class AbstractExtensions { /** - * Return a name for the given classifier name and abstractness. The created name - * has the classifier name as a prefix. If the given value indicates an abstract - * construct, then the prefix is followed by an underscore and the word 'abstract'. - * Otherwise, there is no suffix. + * Return a name for the given classifier name and abstractness. The created name has the classifier name as a + * prefix. If the given value indicates an abstract construct, then the prefix is followed by an underscore and + * the word 'abstract'. Otherwise, there is no suffix. * - * @param abstract_ whether or not the construct of interest is abstract - * @param classifierName the classifier name prefix + * @param abstract_ + * whether or not the construct of interest is abstract + * @param classifierName + * the classifier name prefix * * @return the generated name */ - static def String makeName(Abstract abstract_, String classifierName) { - classifierName + abstract_.classifierExtension + static String makeName(Abstract abstract_, String classifierName) { + return classifierName + getClassifierExtension(abstract_); } /** * Return the appropriate extension for a classifier name depending on the given value. * - * @param abstract_ whether or not the construct of interest is abstract + * @param abstract_ + * whether or not the construct of interest is abstract * * @return the name extension */ - static def String getClassifierExtension(Abstract abstract_) { - switch abstract_ { - case YES: "_abstract" - case NO: "" + static String getClassifierExtension(Abstract abstract_) { + switch (abstract_) { + case YES: + return "_abstract"; + case NO: + return ""; } + return ""; } /** * Returns the generated string for the given value. * - * @param abstract_ whether or not the construct of interest is abstract + * @param abstract_ + * whether or not the construct of interest is abstract * * @return the generated string */ - static def String generate(Abstract abstract_) { - switch abstract_ { - case YES: "abstract " - case NO: "" + static String generate(Abstract abstract_) { + switch (abstract_) { + case YES: + return "abstract "; + case NO: + return ""; } + return ""; } } @@ -75,14 +83,16 @@ static def String generate(Abstract abstract_) { /** * Creates a new instance. */ - protected new() {} + protected Fragment() { + } /** * Specifies that the this fragment should be abstract. */ - public def T makeAbstract() { + @SuppressWarnings("unchecked") + public T makeAbstract() { abstract_ = Abstract.YES; - return this as T; + return (T) this; } /** @@ -90,8 +100,8 @@ public def T makeAbstract() { * * @return true if this construct is abstract and false otherwise */ - protected def boolean isAbstract() { - abstract_ == Abstract.YES + protected boolean isAbstract() { + return abstract_ == Abstract.YES; } /** @@ -99,14 +109,14 @@ protected def boolean isAbstract() { * * @return the generated code */ - abstract def CharSequence generate() + abstract CharSequence generate(); /** * Generates the appropriate keyword for this fragment, depending on whether or not it is abstract. * * @return the generated keyword, followed by a blank */ - protected def generateAbstract() { - AbstractExtensions.generate(abstract_) + protected String generateAbstract() { + return AbstractExtensions.generate(abstract_); } } diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Getter.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Getter.java new file mode 100644 index 0000000000..9f3b8c3916 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Getter.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import com.google.common.base.Strings; + +/** + * Generates code for a getter method of a {@link Classifier}. + */ +public class Getter extends Member { + String fieldType; + String defaultValue; + + /** + * Creates a new getter with the given parameters. + * + * @param name + * the getter's name + */ + public Getter(String name) { + super(name); + } + + /** + * Sets the field type. + * + * @param fieldType + * the field type + */ + public Getter setFieldType(String fieldType) { + this.fieldType = fieldType; + return this; + } + + /** + * Sets the default value or expression. + * + * @param defaultValue + * the default value + */ + public Getter setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + @Override + protected String generateMember() { + String result = generateAbstract(); + result += "get " + name + "()"; + if (!Strings.isNullOrEmpty(fieldType)) { + result += ": " + fieldType; + } + if (isAbstract()) { + result += ":"; + } else { + result += " { return "; + if (!Strings.isNullOrEmpty(defaultValue)) { + result += defaultValue; + } else { + result += "null"; + } + result += "; }"; + } + return result; + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Getter.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Getter.xtend deleted file mode 100644 index 4dce2f0cb6..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Getter.xtend +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -/** - * Generates code for a getter method of a {@link Classifier}. - */ -class Getter extends Member { - String fieldType - String defaultValue; - - /** - * Creates a new getter with the given parameters. - * - * @param name the getter's name - */ - public new(String name) { - super(name) - } - - /** - * Sets the field type. - * - * @param fieldType the field type - */ - public def Getter setFieldType(String fieldType) { - this.fieldType = fieldType; - return this; - } - - /** - * Sets the default value or expression. - * - * @param defaultValue the default value - */ - public def Getter setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue - return this; - } - - override protected generateMember() ''' - «generateAbstract()»get «name»()«IF !fieldType.nullOrEmpty»: «fieldType»«ENDIF»«IF abstract»;«ELSE» { return «IF defaultValue.nullOrEmpty»null«ELSE»defaultValue«ENDIF»; }«ENDIF» - ''' -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Interface.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Interface.java new file mode 100644 index 0000000000..8bc2c81826 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Interface.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import java.util.LinkedList; +import java.util.List; + +/** + * Generates the code for an interface. + */ +public class Interface extends Classifier { + List extendedInterfaces = new LinkedList<>(); + + /** + * Creates a new instance with the given parameters. + * + * @param name + * the name of the interface + */ + public Interface(String name) { + super(name); + } + + /** + * Adds a super interface to this interface + * + * @param implementedInterface + * the interface to add + * + * @return this builder + */ + public Interface addSuperInterface(Interface implementedInterface) { + return addSuperInterface(implementedInterface.name); + } + + /** + * Adds a super interface to this interface + * + * @param implementedInterface + * the name of the interface to add + */ + public Interface addSuperInterface(String implementedInterface) { + extendedInterfaces.add(implementedInterface); + return this; + } + + @Override + protected String generateType() { + return "interface "; + } + + @Override + protected String generateTypeRelations() { + if (!extendedInterfaces.isEmpty()) { + String result = " extends "; + boolean isFirst = true; + for (String inf : extendedInterfaces) { + if (!isFirst) { + result += ", "; + } + result += inf; + isFirst = false; + } + return result; + } + return ""; + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Interface.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Interface.xtend deleted file mode 100644 index 89b40d933f..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Interface.xtend +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.util.List - -/** - * Generates the code for an interface. - */ -class Interface extends Classifier { - List extendedInterfaces; - - /** - * Creates a new instance with the given parameters. - * - * @param name the name of the interface - */ - public new(String name) { - super(name) - } - - /** - * Adds a super interface to this interface - * - * @param implementedInterface the interface to add - * - * @return this builder - */ - public def Interface addSuperInterface(Interface implementedInterface) { - return addSuperInterface(implementedInterface.name) - } - - /** - * Adds a super interface to this interface - * - * @param implementedInterface the name of the interface to add - */ - public def Interface addSuperInterface(String implementedInterface) { - if (extendedInterfaces === null) - extendedInterfaces = newLinkedList(); - extendedInterfaces.add(implementedInterface); - return this; - } - - - override protected def generateType() '''interface ''' - - override protected def CharSequence generateTypeRelations() ''' - «IF extendedInterfaces !== null»«FOR i : extendedInterfaces BEFORE ' extends ' SEPARATOR ', '»«i»«ENDFOR»«ENDIF» - ''' -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Member.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Member.java similarity index 52% rename from testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Member.xtend rename to testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Member.java index 68e93d978c..d6a29d92ac 100644 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Member.xtend +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Member.java @@ -8,12 +8,12 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.tests.codegen +package org.eclipse.n4js.tests.codegen; /** * Abstract base class for code generators that generate code for members of a {@link Classifier}. */ -abstract class Member> extends Fragment { +abstract public class Member> extends Fragment { /** * Possible visibilities for members. */ @@ -49,52 +49,69 @@ public static enum Visibility { */ public static class VisibilityExtensions { /** - * Builds a member name from the given name and visibility by appending an appropriate string - * to the given name. + * Builds a member name from the given name and visibility by appending an appropriate string to the given name. * - * @param visibility the visibility value - * @param the member name prefix + * @param visibility + * the visibility value + * @param memberName + * member name prefix * * @return the newly created member name */ - static def String makeName(Visibility visibility, String memberName) { - memberName + visibility.nameExtension + static String makeName(Visibility visibility, String memberName) { + return memberName + getNameExtension(visibility); } /** * Returns an appropriate member name extension depending on the given visibility. * - * @param visibility the visibility value + * @param visibility + * the visibility value * * @return the name extension */ - static def String getNameExtension(Visibility visibility) { - switch visibility { - case PRIVATE: "_private" - case PROJECT: "_project" - case PROTECTED_INTERNAL: "_protected_internal" - case PROTECTED: "_protected" - case PUBLIC_INTERNAL: "_public_internal" - case PUBLIC: "_public" + static String getNameExtension(Visibility visibility) { + switch (visibility) { + case PRIVATE: + return "_private"; + case PROJECT: + return "_project"; + case PROTECTED_INTERNAL: + return "_protected_internal"; + case PROTECTED: + return "_protected"; + case PUBLIC_INTERNAL: + return "_public_internal"; + case PUBLIC: + return "_public"; } + return ""; } /** * Builds an appropriate code fragment for the given member visibility. * - * @param visibility the visibility value + * @param visibility + * the visibility value * * @return the code fragment */ - static def String generate(Visibility visibility) { - switch visibility { - case PRIVATE: "private" - case PROJECT: "project" - case PROTECTED_INTERNAL: "@Internal protected" - case PROTECTED: "protected" - case PUBLIC_INTERNAL: "@Internal public" - case PUBLIC: "public" + static String generate(Visibility visibility) { + switch (visibility) { + case PRIVATE: + return "private"; + case PROJECT: + return "project"; + case PROTECTED_INTERNAL: + return "@Internal protected"; + case PROTECTED: + return "protected"; + case PUBLIC_INTERNAL: + return "@Internal public"; + case PUBLIC: + return "public"; } + return ""; } } @@ -115,9 +132,8 @@ public enum Static { /** * Possible values for whether or not a member overrides an inherited member. */ - static enum Override { - YES, - NO + static enum HasOverride { + YES, NO } /** @@ -125,106 +141,119 @@ static enum Override { */ public static class StaticExtensions { /** - * Builds a member name from the given name and static specifier by appending an appropriate string - * to the given name. + * Builds a member name from the given name and static specifier by appending an appropriate string to the given + * name. * - * @param static_ whether or not the member is static - * @param the member name prefix + * @param static_ + * whether or not the member is static + * @param classifierName + * name prefix * * @return the newly created member name */ - static def String makeName(Static static_, String classifierName) { - classifierName + static_.nameExtension + static String makeName(Static static_, String classifierName) { + return classifierName + getNameExtension(static_); } /** * Returns an appropriate member name extension depending on the given static specification. * - * @param static_ whether or not the member is static + * @param static_ + * whether or not the member is static * * @return the name extension */ - static def String getNameExtension(Static static_) { - switch static_ { - case YES: "_static" - case NO: "" + static String getNameExtension(Static static_) { + switch (static_) { + case YES: + return "_static"; + case NO: + return ""; } + return ""; } /** * Returns an appropriate code fragment depending on the given static specification. * - * @param static_ whether or not the member is static + * @param static_ + * whether or not the member is static * * @return the code fragment */ - static def String generate(Static static_) { - switch static_ { - case YES: "static " - case NO: "" + static String generate(Static static_) { + switch (static_) { + case YES: + return "static "; + case NO: + return ""; } + return ""; } } protected Visibility visibility = Visibility.PUBLIC; protected Static static_ = Static.NO; protected String name; - protected Override override_ = Override.NO; + protected HasOverride override_ = HasOverride.NO; /** * Creates a new member with the given parameters. * - * @param name the name of the member + * @param name + * the name of the member */ - protected new(String name) { - this.name = name + protected Member(String name) { + this.name = name; } /** * Sets visibility to project visible. */ - public def T makeProjectVisible() { + public T makeProjectVisible() { return setVisibility(Visibility.PROJECT); } /** * Sets visibility to internal protected. */ - public def T makeProtectedInternal() { + public T makeProtectedInternal() { return setVisibility(Visibility.PROTECTED_INTERNAL); } /** * Sets visibility to protected. */ - public def T makeProtected() { + public T makeProtected() { return setVisibility(Visibility.PROTECTED); } /** * Sets visibility to internal public. */ - public def T makePublicInternal() { + public T makePublicInternal() { return setVisibility(Visibility.PUBLIC_INTERNAL); } /** * Sets visibility to public. */ - public def T makePublic() { + public T makePublic() { return setVisibility(Visibility.PUBLIC); } /** * Set the visibility. * - * @param visibility the visibility to set + * @param visibility + * the visibility to set * * @return this builder */ - public def T setVisibility(Visibility visibility) { + @SuppressWarnings("unchecked") + public T setVisibility(Visibility visibility) { this.visibility = visibility; - return this as T; + return (T) this; } /** @@ -232,18 +261,20 @@ public def T setVisibility(Visibility visibility) { * * @return this builder */ - public def T makeStatic() { + public T makeStatic() { return setStatic(Static.YES); } /** * Specify whether the member is static. * - * @param static_ whether or not the member is static + * @param static_ + * whether or not the member is static */ - public def T setStatic(Static static_) { + @SuppressWarnings("unchecked") + public T setStatic(Static static_) { this.static_ = static_; - return this as T; + return (T) this; } /** @@ -251,25 +282,27 @@ public def T setStatic(Static static_) { * * @return true if this member is static and false otherwise */ - public def boolean isStatic() { - static_ == Static.YES + public boolean isStatic() { + return static_ == Static.YES; } /** * Set the member to override. */ - public def T makeOverride() { - return setOverride(Override.YES); + public T makeOverride() { + return setOverride(HasOverride.YES); } /** * Set the override status of the member. * - * @param override_ the override status + * @param override_ + * the override status */ - public def T setOverride(Override override_) { - this.override_ = override_ - return this as T; + @SuppressWarnings("unchecked") + public T setOverride(HasOverride override_) { + this.override_ = override_; + return (T) this; } /** @@ -277,30 +310,38 @@ public def T setOverride(Override override_) { * * @return true if this member overrides */ - public def boolean isOverride() { - return override_== Override.YES; + public boolean isOverride() { + return override_ == HasOverride.YES; } - override def generate() ''' - «generateOverride()»«generateVisibility()»«generateStatic()»«generateMember()» - ''' + @Override + public String generate() { + return generateOverride() + generateVisibility() + generateStatic() + generateMember(); + } - private def generateOverride() '''«IF override»@Override «ENDIF»''' + private String generateOverride() { + if (isOverride()) { + return "@Override "; + } + return ""; + } /** * Generates a code fragment for the visibility of this member. * * @return the code fragment */ - private def generateVisibility() '''«VisibilityExtensions.generate(visibility)» ''' + private String generateVisibility() { + return VisibilityExtensions.generate(visibility) + " "; + } /** * Generates a code fragment according to whether this member is static or not. * * @return the code fragment */ - private def generateStatic() { - StaticExtensions.generate(static_) + private String generateStatic() { + return StaticExtensions.generate(static_); } /** @@ -308,9 +349,10 @@ private def generateStatic() { * * @return the generated code fragment */ - protected abstract def CharSequence generateMember() + protected abstract CharSequence generateMember(); - override public def String toString() { + @Override + public String toString() { return generate().toString(); } } diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Method.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Method.java new file mode 100644 index 0000000000..52684268b7 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Method.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import com.google.common.base.Strings; + +/** + * Code generator for member methods of a {@link Classifier}. + */ +public class Method extends Member { + /** + * A method parameter specification. + */ + static class Param { + String type; + String name; + + /** + * Creates a new instance with the given type and name. + * + * @param type + * the parameter type + * @param name + * the parameter name + */ + Param(String type, String name) { + this.type = type; + this.name = name; + } + } + + String returnType; + List params = new LinkedList<>(); + String body; + + /** + * Creates a new instance with the given parameters. + * + * @param name + * the method name + */ + public Method(String name) { + super(name); + } + + /** + * Sets the return type of this method. + * + * @param returnType + * the return type + */ + public Method setReturnType(String returnType) { + this.returnType = returnType; + return this; + } + + /** + * Adds a parameter to this method. + * + * @param param + * the parameter to add + */ + public Method addParameter(Param param) { + this.params.add(Objects.requireNonNull(param)); + return this; + } + + /** + * Sets the body of this method. + * + * @param body + * the body to set + */ + public Method setBody(String body) { + this.body = body; + return this; + } + + @Override + protected String generateMember() { + String result = generateAbstract(); + result += name + "("; + boolean isFirst = true; + for (Param p : params) { + if (!isFirst) { + result += ", "; + } + result += p.name + ": " + p.type; + isFirst = false; + } + result += ")"; + if (!Strings.isNullOrEmpty(returnType)) { + result += ": " + returnType; + } + if (isAbstract()) { + result += ";"; + } else { + if (hasBody()) { + result += " { "; + if (!Strings.isNullOrEmpty(body)) { + result += body; + } else if (!Strings.isNullOrEmpty(returnType)) { + result += "return new " + returnType + "();"; + } + result += " }"; + } else { + result += " {}"; + } + } + + return result; + } + + private boolean hasBody() { + return !Strings.isNullOrEmpty(body) || !Strings.isNullOrEmpty(returnType); + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Method.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Method.xtend deleted file mode 100644 index a4f633ed52..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Method.xtend +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.util.List -import java.util.Objects - -/** - * Code generator for member methods of a {@link Classifier}. - */ -class Method extends Member { - /** - * A method parameter specification. - */ - static class Param { - String type; - String name; - - /** - * Creates a new instance with the given type and name. - * - * @param type the parameter type - * @param name the parameter name - */ - new(String type, String name) { - this.type = type; - this.name = name; - } - } - - String returnType; - List params; - String body; - - /** - * Creates a new instance with the given parameters. - * - * @param name the method name - */ - public new(String name) { - super(name); - } - - /** - * Sets the return type of this method. - * - * @param returnType the return type - */ - public def Method setReturnType(String returnType) { - this.returnType = returnType; - return this; - } - - /** - * Adds a parameter to this method. - * - * @param param the parameter to add - */ - public def Method addParameter(Param param) { - if (this.params === null) - this.params = newLinkedList(); - this.params.add(Objects.requireNonNull(param)); - return this; - } - - /** - * Sets the body of this method. - * - * @param body the body to set - */ - public def Method setBody(String body) { - this.body = body; - return this; - } - - override protected generateMember() ''' - «generateAbstract()»«name»(«IF params !== null»«FOR p : params»«p.name»: «p.type»«ENDFOR»«ENDIF»)«IF !returnType.nullOrEmpty»: «returnType»«ENDIF»«IF abstract»;«ELSE» «IF !hasBody»{}«ELSE»{ - «IF !body.nullOrEmpty» - «body» - «ELSEIF !returnType.nullOrEmpty» - return new «returnType»() - «ENDIF» - }«ENDIF» - «ENDIF» - ''' - - private def boolean hasBody() { - !body.nullOrEmpty || !returnType.nullOrEmpty - } -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Module.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Module.java new file mode 100644 index 0000000000..9221207323 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Module.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.utils.Strings; + +/** + * Generates code for a module containing imports and either given classifiers or contents. + */ +public class Module extends OtherFile { + List> classifiers = new LinkedList<>(); + Map> imports = new HashMap<>(); + + /** + * Creates a new instance with the given parameters. + * + * @param name + * the module name without extension + */ + public Module(String name) { + super(name, N4JSGlobals.N4JS_FILE_EXTENSION, null); + } + + /** + * Creates a new instance with the given parameters. + * + * @param name + * the module name without extension + */ + public Module(String name, String fExtension, String fromFileName) { + super(name, fExtension, fromFileName); + } + + /** + * Adds the given classifier to the module built by this builder. Note that the classifiers are ignored iff the + * contents are set. + * + * @param classifier + * the classifier to add + */ + public Module addClassifier(Classifier classifier) { + classifiers.add(Objects.requireNonNull(classifier)); + return this; + } + + /** + * Adds an import to the module built by this builder. + * + * @param importedType + * the name of the type to be imported + * @param sourceModule + * the module containing the imported type + */ + public Module addImport(String importedType, Module sourceModule) { + return addImport(importedType, sourceModule.name); + } + + /** + * Adds an import to the module built by this builder. + * + * @param importedType + * the classifier representing the type to be imported + * @param sourceModule + * the module containing the imported type + */ + public Module addImport(Classifier importedType, Module sourceModule) { + return addImport(importedType.name, sourceModule); + } + + /** + * Adds an import to the module built by this builder. + * + * @param importedType + * the name of the type to be imported + * @param sourceModule + * the name of the module containing the imported type + */ + public Module addImport(String importedType, String sourceModule) { + List importedTypesForModule = imports.get(Objects.requireNonNull(sourceModule)); + if (importedTypesForModule == null) { + importedTypesForModule = new LinkedList<>(); + imports.put(sourceModule, importedTypesForModule); + } + + importedTypesForModule.add(Objects.requireNonNull(importedType)); + return this; + } + + /** + * Generates the N4JS code for this module. + */ + @Override + public String generate() { + String result = ""; + if (hasImports()) { + result += generateImports(); + } + if (hasContents()) { + result += content; + } + if (hasClassifiers()) { + result += generateClassifiers(); + } + return result; + } + + private String generateImports() { + String result = ""; + for (Map.Entry> entry : imports.entrySet()) { + result += "import { "; + boolean isFirst = true; + for (String type : entry.getValue()) { + if (!isFirst) { + result += ", "; + } + result += type; + isFirst = false; + } + result += " } from \"" + entry.getKey() + "\";"; + } + return result; + } + + private String generateClassifiers() { + return Strings.join("", c -> c.generate(), classifiers); + } + + private boolean hasClassifiers() { + return !classifiers.isEmpty(); + } + + private boolean hasImports() { + return !imports.isEmpty(); + } + + private boolean hasContents() { + return !isNullOrEmpty(content); + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Module.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Module.xtend deleted file mode 100644 index 0ca8b8242e..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Module.xtend +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.util.List -import java.util.Map -import java.util.Objects -import org.eclipse.n4js.N4JSGlobals - -/** - * Generates code for a module containing imports and either given classifiers or contents. - */ -class Module extends OtherFile { - List> classifiers; - Map> imports; - - /** - * Creates a new instance with the given parameters. - * - * @param name the module name without extension - */ - public new(String name) { - super(name, N4JSGlobals.N4JS_FILE_EXTENSION, null); - } - - /** - * Creates a new instance with the given parameters. - * - * @param name the module name without extension - */ - public new(String name, String fExtension, String fromFileName) { - super(name, fExtension, fromFileName); - } - - /** - * Adds the given classifier to the module built by this builder. - * Note that the classifiers are ignored iff the contents are set. - * - * @param classifier the classifier to add - */ - public def Module addClassifier(Classifier classifier) { - if (classifiers === null) - classifiers = newLinkedList(); - classifiers.add(Objects.requireNonNull(classifier)); - return this; - } - - /** - * Adds an import to the module built by this builder. - * - * @param importedType the name of the type to be imported - * @param sourceModule the module containing the imported type - */ - public def Module addImport(String importedType, Module sourceModule) { - return addImport(importedType, sourceModule.name) - } - - /** - * Adds an import to the module built by this builder. - * - * @param importedType the classifier representing the type to be imported - * @param sourceModule the module containing the imported type - */ - public def Module addImport(Classifier importedType, Module sourceModule) { - return addImport(importedType.name, sourceModule) - } - - /** - * Adds an import to the module built by this builder. - * - * @param importedType the name of the type to be imported - * @param sourceModule the name of the module containing the imported type - */ - public def Module addImport(String importedType, String sourceModule) { - if (imports === null) - imports = newHashMap(); - - var List importedTypesForModule = imports.get(Objects.requireNonNull(sourceModule)); - if (importedTypesForModule === null) { - importedTypesForModule = newLinkedList(); - imports.put(sourceModule, importedTypesForModule); - } - - importedTypesForModule.add(Objects.requireNonNull(importedType)) - return this; - } - - /** - * Generates the N4JS code for this module. - */ - public override generate() ''' - «IF hasImports» - «generateImports()» - «ENDIF» - «IF hasContents» - «content» - «ELSEIF hasClassifiers» - «generateClassifiers()» - «ENDIF» - ''' - - private def generateImports() ''' - «FOR entry : imports.entrySet()» - import { «FOR type: entry.value SEPARATOR ', '»«type»«ENDFOR» } from "«entry.key»"; - «ENDFOR» - ''' - - private def generateClassifiers() ''' - «FOR classifier : classifiers» - «classifier.generate()» - «ENDFOR» - ''' - - private def boolean hasClassifiers() { - return classifiers !== null && !classifiers.empty; - } - - private def boolean hasImports() { - return imports !== null && !imports.empty - } - - private def boolean hasContents() { - return content !== null && !content.empty - } -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/OtherFile.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/OtherFile.java similarity index 60% rename from testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/OtherFile.xtend rename to testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/OtherFile.java index 1d849c4727..7cf537916d 100644 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/OtherFile.xtend +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/OtherFile.java @@ -4,27 +4,28 @@ * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html - * + * * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.tests.codegen +package org.eclipse.n4js.tests.codegen; -import java.io.File -import java.io.FileWriter -import java.io.IOException -import java.util.Objects -import org.eclipse.n4js.N4JSGlobals +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Objects; + +import org.eclipse.n4js.N4JSGlobals; /** * Generates code for a module containing imports and either given classifiers or contents. */ -class OtherFile { +public class OtherFile { /** The file name without extension. */ final protected String name; /** - * The file extension. Will be null for files without extension and empty string for files - * with a name ending in ".". + * The file extension. Will be null for files without extension and empty string for files with a name + * ending in ".". */ final protected String fExtension; /** File name provided using the {@code from=""} syntax or null if not provided. */ @@ -33,19 +34,21 @@ class OtherFile { /** * Creates a new instance with the given parameters. - * - * @param name the module name without extension + * + * @param name + * the module name without extension */ - public new(String name) { + public OtherFile(String name) { this(name, N4JSGlobals.N4JS_FILE_EXTENSION, null); } /** * Creates a new instance with the given parameters. - * - * @param name the module name without extension + * + * @param name + * the module name without extension */ - public new(String name, String fExtension, String fromFileName) { + public OtherFile(String name, String fExtension, String fromFileName) { this.name = Objects.requireNonNull(name); this.fExtension = fExtension; this.fromFileName = fromFileName; @@ -53,82 +56,87 @@ public new(String name, String fExtension, String fromFileName) { /** * Returns the name of this module. - * + * * @return the name of this module */ - public def String getName() { + public String getName() { return name; } /** - * Returns the file extension of this module. Will be null for files without extension - * and the empty string for files with a name ending in ".". - * + * Returns the file extension of this module. Will be null for files without extension and the empty + * string for files with a name ending in ".". + * * @return the file extension of this module or null. */ - public def String getExtension() { + public String getExtension() { return fExtension; } /** @return filename with extension (if any). */ - public def String getNameWithExtension() { - return fExtension !== null ? name + "." + fExtension : name; + public String getNameWithExtension() { + return fExtension != null ? name + "." + fExtension : name; } /** File name provided using the {@code from=""} syntax or null if not provided. */ - public def String getFromFileName() { + public String getFromFileName() { return fromFileName; } /** - * Sets the given string of contents to the module built by this builder. - * This will cause the classifiers to be ignored. - * - * @param contents the contents to add + * Sets the given string of contents to the module built by this builder. This will cause the classifiers to be + * ignored. + * + * @param contents + * the contents to add */ - public def OtherFile setContents(String contents) { + public OtherFile setContents(String contents) { this.content = contents; return this; } /** * Returns the contents of this module. - * + * * @return the contents of this module */ - public def String getContents() { + public String getContents() { return content; } /** * Creates this module as a file in the given parent directory, which must already exist. - * - * @param parentDirectory a file representing the parent directory + * + * @param parentDirectory + * a file representing the parent directory */ - public def create(File parentDirectory) { + public void create(File parentDirectory) throws IOException { Objects.requireNonNull(parentDirectory); - if (!parentDirectory.exists) + if (!parentDirectory.exists()) { throw new IOException("Directory '" + parentDirectory + "' does not exist"); - if (!parentDirectory.directory) + } + if (!parentDirectory.isDirectory()) { throw new IOException("'" + parentDirectory + "' is not a directory"); + } - val File filePath = new File(parentDirectory, this.getNameWithExtension().replace('/', File.separatorChar)); - filePath.parentFile.mkdirs(); + File filePath = new File(parentDirectory, this.getNameWithExtension().replace('/', File.separatorChar)); + filePath.getParentFile().mkdirs(); - var FileWriter out = null; + FileWriter out = null; try { out = new FileWriter(filePath); out.write(generate().toString()); } finally { - if (out !== null) + if (out != null) { out.close(); + } } } /** * Generates the N4JS code for this module. */ - public def generate() { + public String generate() { return content; } } diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Project.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Project.java new file mode 100644 index 0000000000..3f61fc6516 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Project.java @@ -0,0 +1,462 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import static org.eclipse.n4js.utils.Strings.join; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.packagejson.projectDescription.ProjectType; +import org.eclipse.n4js.utils.io.FileDeleter; + +import com.google.common.base.Strings; + +/** + * Generates the code for a project. + */ +public class Project { + final String projectName; + final String vendorId; + final String vendorName; + final LinkedHashSet folders = new LinkedHashSet<>(); + final Set projectDependencies = new LinkedHashSet<>(); + final Map scripts = new LinkedHashMap<>(); + final Map nodeModuleProjects = new HashMap<>(); + ProjectType projectType; + String projectVersion = "1.0.0"; + String mainModule = null; + String outputFolder = "src-gen"; + String projectDescriptionContent = null; + boolean generateDts = false; + + /** + * Same as {@link #Project(String, String, String, ProjectType)}, but with a default project type of + * {@link ProjectType#LIBRARY LIBRARY}. + */ + public Project(String projectName, String vendorId, String vendorName) { + this(projectName, vendorId, vendorName, ProjectType.LIBRARY); + } + + /** + * Creates a new instance with the given parameters. + * + * @param projectName + * the project ID + * @param vendorId + * the vendor ID + * @param vendorName + * the vendor name + * @param projectType + * the project type + */ + public Project(String projectName, String vendorId, String vendorName, ProjectType projectType) { + this.projectName = Objects.requireNonNull(projectName); + this.vendorId = Objects.requireNonNull(vendorId); + this.vendorName = Objects.requireNonNull(vendorName); + this.projectType = Objects.requireNonNull(projectType); + } + + /** + * Returns the project name. + * + * @return the project name. + */ + public String getName() { + return projectName; + } + + /** + * Returns the project type. + * + * @return the project type. + */ + public ProjectType getType() { + return projectType; + } + + /** + * Sets the project type. + * + * @param projectType + * the project type to set + */ + public Project setType(ProjectType projectType) { + this.projectType = projectType; + return this; + } + + /** + * Gets the project version. + * + * @return projectVersion the project + */ + public String getVersion() { + return this.projectVersion; + } + + /** + * Sets the project version. + * + * @param projectVersion + * the project version + */ + public Project setVersion(String projectVersion) { + this.projectVersion = projectVersion; + return this; + } + + /** + * Gets the project's main module. + * + * @return main module of the project. + */ + public String getMainModule() { + return this.mainModule; + } + + /** + * Sets the project's main module. + * + * @param mainModule + * the main module. + */ + public Project setMainModule(String mainModule) { + this.mainModule = mainModule; + return this; + } + + /** + * Returns the output folder. + * + * @return the output folder. + */ + public String getOutputFolder() { + return outputFolder; + } + + /** + * Sets the output folder. + * + * @param outputFolder + * the output folder to set + */ + public Project setOutputFolder(String outputFolder) { + this.outputFolder = outputFolder; + return this; + } + + /** + * @return true iff "generator"/"d.ts" is set to true. + */ + public boolean isGenerateDts() { + return generateDts; + } + + /** + * Turns on/off generation of .d.ts files. + */ + public Project setGenerateDts(boolean generateDts) { + this.generateDts = generateDts; + return this; + } + + /** + * Sets the content of the project description file 'package.json' + * + * @param projectDescriptionContent + * content of package.json + */ + public Project setProjectDescriptionContent(String projectDescriptionContent) { + this.projectDescriptionContent = projectDescriptionContent; + return this; + } + + /** + * Returns content of package.json. + * + * @return content of package.json. + */ + public String getProjectDescriptionContent() { + return projectDescriptionContent; + } + + /** + * Creates a folder with the given name to this project. + * + * @param name + * the name of the source folder to add + * + * @return the added source folder + */ + public Folder createFolder(String name) { + Folder result = new Folder(name, false); + addSourceFolder(result); + return result; + } + + /** + * Creates a source folder with the given name to this project. + * + * @param name + * the name of the source folder to add + * + * @return the added source folder + */ + public Folder createSourceFolder(String name) { + Folder result = new Folder(name, true); + addSourceFolder(result); + return result; + } + + /** + * Adds a source folder to this project. + * + * @param sourceFolder + * the source folder to add + */ + public Project addSourceFolder(Folder sourceFolder) { + folders.add(Objects.requireNonNull(sourceFolder)); + return this; + } + + /** + * Returns a list of all source folders of this project. + * + * @return list of all source folders of this project. + */ + public LinkedHashSet getSourceFolders() { + return folders; + } + + /** + * Adds a project dependency to this project. + * + * @param projectDependency + * the name of the project to add to the list of dependencies. + */ + public Project addProjectDependency(String projectDependency) { + projectDependencies.add(Objects.requireNonNull(projectDependency)); + return this; + } + + public void addScript(String name, String script) { + scripts.put(name, script); + } + + /** + * Returns a list of project dependencies of this project. + * + * @return projectDependencies the project + */ + public Set getProjectDependencies() { + return this.projectDependencies; + } + + public void addNodeModuleProject(Project project) { + this.nodeModuleProjects.put(project.projectName, project); + } + + public Project getNodeModuleProject(String _projectName) { + return this.nodeModuleProjects.get(_projectName); + } + + public Collection getNodeModuleProjects() { + List projects = new ArrayList<>(); + projects.addAll(nodeModuleProjects.values()); + return projects; + } + + /** + * Generates the {@link N4JSGlobals#PACKAGE_JSON} for this project. + */ + public String generate() { + if (!Strings.isNullOrEmpty(projectDescriptionContent)) { + return projectDescriptionContent; + } + + String scriptsStr = ""; + if (!scriptsStr.isEmpty()) { + scriptsStr = join(",\n\t", e -> "\"%s\": \"%s\"".formatted(e.getKey(), e.getValue()), scripts.entrySet()); + } + + String vendorIdStr = "\"vendorId\": \"%s\"".formatted(vendorId); + String vendorNameStr = "\"vendorName\": \"%s\"".formatted(vendorName); + String projectTypeStr = "\"projectType\": \"%s\"".formatted(projectTypeToString(projectType)); + String mainModuleStr = mainModule == null ? "" : "\"mainModule\": \"%s\"".formatted(mainModule); + String outputFolderStr = outputFolder == null ? "" : "\"output\": \"%s\"".formatted(outputFolder); + String generateDtsStr = generateDts ? "\"generator\": { \"d.ts\": true }".formatted(outputFolder) : ""; + String sourcesStr = ""; + if (!folders.isEmpty()) { + sourcesStr += "\"sources\": {\n\t\t\t\"source\": ["; + boolean isFirst = true; + for (Folder sourceFolder : filter(folders, f -> f.isSourceFolder)) { + if (!isFirst) { + sourcesStr += ", "; + } + sourcesStr += "\"%s\"".formatted(sourceFolder.name); + isFirst = false; + } + sourcesStr += "]\n\t\t}"; + } + + String n4jsProps = ""; + boolean isFirst = true; + for (String prop : List.of(vendorIdStr, vendorNameStr, projectTypeStr, mainModuleStr, outputFolderStr, + generateDtsStr, sourcesStr)) { + + if (!Strings.isNullOrEmpty(prop)) { + if (!isFirst) { + n4jsProps += ",\n\t\t"; + } + n4jsProps += prop; + isFirst = false; + } + } + + String deps = ""; + if (!projectDependencies.isEmpty()) { + deps = join(",\n\t", d -> "\"%s\": \"\"".formatted(d), projectDependencies); + } + + String result = """ + { + "name": "%s", + "version": "%s", + "type": "module", + "scripts": {%s}, + "n4js": { + %s + }, + "dependencies": {%s} + } + """.formatted( + projectName, + projectVersion, + scriptsStr, + n4jsProps, + deps); + + return result; + } + + private static String projectTypeToString(ProjectType type) { + switch (type) { + case API: + return "api"; + case APPLICATION: + return "application"; + case LIBRARY: + return "library"; + case PROCESSOR: + return "processor"; + case RUNTIME_ENVIRONMENT: + return "runtimeEnvironment"; + case RUNTIME_LIBRARY: + return "runtimeLibrary"; + case TEST: + return "test"; + case PLAINJS: + return "plainjs"; + case VALIDATION: + return "validation"; + case DEFINITION: + return "definition"; + } + return ""; + } + + /** + * Creates this project in the given parent directory, which must exist. + * + * This method first creates a directory with the same name as the {@link #projectName} within the given parent + * directory. If there already exists a file or directory with that name within the given parent directory, that + * file or directory will be (recursively) deleted. + * + * Afterward, the package.json file and the source folders are created within the newly created project directory. + * + * @param parentDirectoryPath + * the path to the parent directory + * + * @return the project directory + */ + public File create(Path parentDirectoryPath) throws IOException { + File parentDirectory = Objects.requireNonNull(parentDirectoryPath).toFile(); + if (!parentDirectory.exists()) { + throw new IOException("'" + parentDirectory + "' does not exist"); + } + if (!parentDirectory.isDirectory()) { + throw new IOException("'" + parentDirectory + "' is not a directory"); + } + + File projectDirectory = new File(parentDirectory, projectName); + rmkdirs(projectDirectory); + + createProjectDescriptionFile(projectDirectory); + createModules(projectDirectory); + + if (!nodeModuleProjects.isEmpty()) { + File nodeModulesDirectory = new File(projectDirectory, N4JSGlobals.NODE_MODULES); + if (nodeModulesDirectory.exists()) { + FileDeleter.delete(nodeModulesDirectory); + } + nodeModulesDirectory.mkdir(); + createNodeModuleProjects(nodeModulesDirectory); + } + + return projectDirectory; + } + + private void createProjectDescriptionFile(File parentDirectory) throws IOException { + File filePath = new File(parentDirectory, N4JSGlobals.PACKAGE_JSON); + FileWriter out = null; + try { + out = new FileWriter(filePath); + out.write(generate().toString()); + } finally { + if (out != null) { + out.close(); + } + } + } + + private void createModules(File parentDirectory) throws IOException { + for (Folder sourceFolder : folders) { + sourceFolder.create(parentDirectory); + } + } + + private void createNodeModuleProjects(File parentDirectory) throws IOException { + for (Project nodeModuleProject : nodeModuleProjects.values()) { + nodeModuleProject.create(parentDirectory.toPath()); + } + } + + void rmkdirs(File file) throws IOException { + if (file.exists()) { + FileDeleter.delete(file); + } + file.mkdirs(); + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Project.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Project.xtend deleted file mode 100644 index 5d195e3481..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Project.xtend +++ /dev/null @@ -1,402 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.io.File -import java.io.FileWriter -import java.io.IOException -import java.nio.file.Path -import java.util.Collection -import java.util.LinkedHashSet -import java.util.Map -import java.util.Objects -import java.util.Set -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.packagejson.projectDescription.ProjectType -import org.eclipse.n4js.utils.io.FileDeleter - -/** - * Generates the code for a project. - */ -public class Project { - final String projectName; - final String vendorId; - final String vendorName; - final LinkedHashSet folders = newLinkedHashSet(); - final Set projectDependencies = newLinkedHashSet(); - final Map scripts = newLinkedHashMap(); - final Map nodeModuleProjects = newHashMap(); - ProjectType projectType; - String projectVersion = "1.0.0"; - String mainModule = null; - String outputFolder = "src-gen"; - String projectDescriptionContent = null; - boolean generateDts = false; - - /** - * Same as {@link #Project(String, String, String, ProjectType)}, but with - * a default project type of {@link ProjectType#LIBRARY LIBRARY}. - */ - public new(String projectName, String vendorId, String vendorName) { - this(projectName, vendorId, vendorName, ProjectType.LIBRARY); - } - - /** - * Creates a new instance with the given parameters. - * - * @param projectName the project ID - * @param vendorId the vendor ID - * @param vendorName the vendor name - * @param projectType the project type - */ - public new(String projectName, String vendorId, String vendorName, ProjectType projectType) { - this.projectName = Objects.requireNonNull(projectName); - this.vendorId = Objects.requireNonNull(vendorId); - this.vendorName = Objects.requireNonNull(vendorName); - this.projectType = Objects.requireNonNull(projectType); - } - - /** - * Returns the project name. - * - * @return the project name. - */ - public def String getName() { - return projectName; - } - - /** - * Returns the project type. - * - * @return the project type. - */ - public def ProjectType getType() { - return projectType; - } - - /** - * Sets the project type. - * - * @param projectType the project type to set - */ - public def Project setType(ProjectType projectType) { - this.projectType = projectType; - return this; - } - - /** - * Gets the project version. - * - * @return projectVersion the project - */ - public def String getVersion() { - return this.projectVersion; - } - - /** - * Sets the project version. - * - * @param projectVersion the project version - */ - public def Project setVersion(String projectVersion) { - this.projectVersion = projectVersion; - return this; - } - - /** - * Gets the project's main module. - * - * @return main module of the project. - */ - public def String getMainModule() { - return this.mainModule; - } - - /** - * Sets the project's main module. - * - * @param mainModule the main module. - */ - public def Project setMainModule(String mainModule) { - this.mainModule = mainModule; - return this; - } - - /** - * Returns the output folder. - * - * @return the output folder. - */ - public def String getOutputFolder() { - return outputFolder; - } - - /** - * Sets the output folder. - * - * @param outputFolder the output folder to set - */ - public def Project setOutputFolder(String outputFolder) { - this.outputFolder = outputFolder; - return this; - } - - /** - * @return true iff "generator"/"d.ts" is set to true. - */ - public def boolean isGenerateDts() { - return generateDts; - } - - /** - * Turns on/off generation of .d.ts files. - */ - public def Project setGenerateDts(boolean generateDts) { - this.generateDts = generateDts; - return this; - } - - /** - * Sets the content of the project description file 'package.json' - * - * @param projectDescriptionContent content of package.json - */ - public def Project setProjectDescriptionContent(String projectDescriptionContent) { - this.projectDescriptionContent = projectDescriptionContent; - return this; - } - - /** - * Returns content of package.json. - * - * @return content of package.json. - */ - public def String getProjectDescriptionContent() { - return projectDescriptionContent; - } - - /** - * Creates a folder with the given name to this project. - * - * @param name the name of the source folder to add - * - * @return the added source folder - */ - public def Folder createFolder(String name) { - val Folder result = new Folder(name, false); - addSourceFolder(result); - return result; - } - - /** - * Creates a source folder with the given name to this project. - * - * @param name the name of the source folder to add - * - * @return the added source folder - */ - public def Folder createSourceFolder(String name) { - val Folder result = new Folder(name, true); - addSourceFolder(result); - return result; - } - - /** - * Adds a source folder to this project. - * - * @param sourceFolder the source folder to add - */ - public def Project addSourceFolder(Folder sourceFolder) { - folders.add(Objects.requireNonNull(sourceFolder)); - return this; - } - - /** - * Returns a list of all source folders of this project. - * - * @return list of all source folders of this project. - */ - public def LinkedHashSet getSourceFolders() { - return folders; - } - - /** - * Adds a project dependency to this project. - * - * @param projectDependency the name of the project to add to the list of dependencies. - */ - public def Project addProjectDependency(String projectDependency) { - projectDependencies.add(Objects.requireNonNull(projectDependency)); - return this; - } - - public def void addScript(String name, String script) { - scripts.put(name, script); - } - - /** - * Returns a list of project dependencies of this project. - * - * @return projectDependencies the project - */ - public def Set getProjectDependencies() { - return this.projectDependencies; - } - - public def void addNodeModuleProject(Project project) { - this.nodeModuleProjects.put(project.projectName, project); - } - - public def Project getNodeModuleProject(String projectName) { - return this.nodeModuleProjects.get(projectName); - } - - public def Collection getNodeModuleProjects() { - val projects = newArrayList(); - projects.addAll(nodeModuleProjects.values); - return projects; - } - - /** - * Generates the {@link N4JSGlobals#PACKAGE_JSON} for this project. - */ - public def String generate() ''' - «IF !projectDescriptionContent.nullOrEmpty»« - projectDescriptionContent» - «ELSE» - { - "name": "«projectName»", - "version": "«projectVersion»", - "type": "module", - «IF !scripts.empty» - "scripts": { - «FOR script : scripts.entrySet SEPARATOR ','» - "«script.key»": "«script.value»" - «ENDFOR» - }, - «ENDIF» - "n4js": { - "vendorId": "«vendorId»", - "vendorName": "«vendorName»", - "projectType": "«projectType.projectTypeToString»" - «IF mainModule !== null - »,"mainModule": "«mainModule»" - «ENDIF» - «IF !outputFolder.nullOrEmpty - »,"output": "«outputFolder»" - «ENDIF» - «IF generateDts» - ,"generator": { - "d.ts": true - } - «ENDIF» - «IF !folders.nullOrEmpty - »,"sources": { - "source": [ - «FOR sourceFolder : folders.filter[isSourceFolder] SEPARATOR ','» - "«sourceFolder.name»" - «ENDFOR» - ] - } - «ENDIF» - }, - "dependencies": { - «IF !projectDependencies.nullOrEmpty» - «FOR dep : projectDependencies SEPARATOR ','» - "«dep»": "" - «ENDFOR» - «ENDIF» - } - } - «ENDIF» - ''' - - private static def String projectTypeToString(ProjectType type) { - return switch (type) { - case API: "api" - case APPLICATION: "application" - case LIBRARY: "library" - case PROCESSOR: "processor" - case RUNTIME_ENVIRONMENT: "runtimeEnvironment" - case RUNTIME_LIBRARY: "runtimeLibrary" - case TEST: "test" - case PLAINJS: "plainjs" - case VALIDATION: "validation" - case DEFINITION: "definition" - }; - } - - /** - * Creates this project in the given parent directory, which must exist. - * - * This method first creates a directory with the same name as the {@link #projectName} within - * the given parent directory. If there already exists a file or directory with that name - * within the given parent directory, that file or directory will be (recursively) deleted. - * - * Afterward, the package.json file and the source folders are created within the newly created - * project directory. - * - * @param parentDirectoryPath the path to the parent directory - * - * @return the project directory - */ - public def File create(Path parentDirectoryPath) { - var File parentDirectory = Objects.requireNonNull(parentDirectoryPath).toFile - if (!parentDirectory.exists) - throw new IOException("'" + parentDirectory + "' does not exist") - if (!parentDirectory.directory) - throw new IOException("'" + parentDirectory + "' is not a directory"); - - val File projectDirectory = new File(parentDirectory, projectName); - rmkdirs(projectDirectory); - - createProjectDescriptionFile(projectDirectory); - createModules(projectDirectory); - - if (!nodeModuleProjects.isEmpty()) { - val File nodeModulesDirectory = new File(projectDirectory, N4JSGlobals.NODE_MODULES); - if (nodeModulesDirectory.exists) - FileDeleter.delete(nodeModulesDirectory); - nodeModulesDirectory.mkdir(); - createNodeModuleProjects(nodeModulesDirectory); - } - - return projectDirectory; - } - - private def void createProjectDescriptionFile(File parentDirectory) { - val File filePath = new File(parentDirectory, N4JSGlobals.PACKAGE_JSON); - var FileWriter out = null; - try { - out = new FileWriter(filePath); - out.write(generate().toString()); - } finally { - if (out !== null) - out.close(); - } - } - - private def void createModules(File parentDirectory) { - for (sourceFolder : folders) - sourceFolder.create(parentDirectory); - } - - private def void createNodeModuleProjects(File parentDirectory) { - for (nodeModuleProject : nodeModuleProjects.values) - nodeModuleProject.create(parentDirectory.toPath()); - } - - def void rmkdirs(File file) { - if (file.exists) - FileDeleter.delete(file); - file.mkdirs(); - } -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Setter.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Setter.java new file mode 100644 index 0000000000..5d3d64841f --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Setter.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import com.google.common.base.Strings; + +/** + * Generates code for a setter method of a {@link Classifier}. + */ +public class Setter extends Member { + protected String fieldType; + + /** + * Creates a new setter with the given parameters. + * + * @param name + * the setter's name + */ + public Setter(String name) { + super(name); + } + + /** + * Sets the field type of this setter. + * + * @param fieldType + * the field type + */ + public Setter setFieldType(String fieldType) { + this.fieldType = fieldType; + return this; + } + + @Override + protected String generateMember() { + String result = generateAbstract(); + result += "set " + name + "(value"; + if (Strings.isNullOrEmpty(fieldType)) { + result += ": " + fieldType; + } + result += ")"; + if (isAbstract()) { + result += ";"; + } else { + result += "{}"; + } + return result; + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Setter.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Setter.xtend deleted file mode 100644 index 702817ac37..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Setter.xtend +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -/** - * Generates code for a setter method of a {@link Classifier}. - */ -class Setter extends Member { - protected String fieldType - - /** - * Creates a new setter with the given parameters. - * - * @param name the setter's name - */ - public new(String name) { - super(name) - } - - /** - * Sets the field type of this setter. - * - * @param fieldType the field type - */ - public def Setter setFieldType(String fieldType) { - this.fieldType = fieldType; - return this; - } - - override protected generateMember() ''' - «generateAbstract()»set «name»(value«IF !fieldType.nullOrEmpty»: «fieldType»«ENDIF»)«IF abstract»;«ELSE» {}«ENDIF» - ''' -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Workspace.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Workspace.java new file mode 100644 index 0000000000..c47cc70348 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Workspace.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.n4js.utils.io.FileDeleter; + +/** + * Generates code for a workspace. + */ +public class Workspace { + final List projects = new ArrayList<>(); + + public void addProject(Project project) { + this.projects.add(project); + } + + public void clearProjects() { + this.projects.clear(); + } + + public List getProjects() { + return this.projects; + } + + /** + * Similar to {@link #getProjects()}, but also includes the member projects of {@link YarnWorkspaceProject}s. + */ + public List getAllProjects() { + List allProjects = new ArrayList<>(); + for (Project p : projects) { + allProjects.add(p); + if (p instanceof YarnWorkspaceProject) { + YarnWorkspaceProject ywp = (YarnWorkspaceProject) p; + allProjects.addAll(ywp.getMemberProjects()); + allProjects.addAll(ywp.getNodeModuleProjects()); + } + } + return allProjects; + } + + public void simplifyIfPossible() { + if (isYarnToSingleProjectConvertable()) { + YarnWorkspaceProject yarnWorkspaceProject = (YarnWorkspaceProject) getProjects().get(0); + Project project = yarnWorkspaceProject.getMemberProjects().iterator().next(); + clearProjects(); + addProject(project); + } + } + + public boolean isYarnToSingleProjectConvertable() { + return getProjects().size() == 1 + && getAllProjects().size() == 2 // this includes the yarn project and the single project in packages + && getProjects().get(0) instanceof YarnWorkspaceProject + && ((YarnWorkspaceProject) getProjects().get(0)).getMemberProjects().size() == 1; + } + + public File create(Path parentDirectoryPath) throws IOException { + File wsDirectory = Objects.requireNonNull(parentDirectoryPath).toFile(); + if (!wsDirectory.exists()) { + throw new IOException("'" + wsDirectory + "' does not exist"); + } + if (!wsDirectory.isDirectory()) { + throw new IOException("'" + wsDirectory + "' is not a directory"); + } + + if (wsDirectory.exists()) { + FileDeleter.delete(wsDirectory); + } + wsDirectory.mkdirs(); + + for (Project project : projects) { + project.create(wsDirectory.toPath()); + } + + return wsDirectory; + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Workspace.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Workspace.xtend deleted file mode 100644 index 77af8bf177..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/Workspace.xtend +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.io.File -import java.io.IOException -import java.nio.file.Path -import java.util.List -import java.util.Objects -import org.eclipse.n4js.utils.io.FileDeleter - -/** - * Generates code for a workspace. - */ -class Workspace { - final List projects = newArrayList(); - - def addProject(Project project) { - this.projects.add(project); - } - - def clearProjects() { - this.projects.clear(); - } - - def List getProjects() { - return this.projects; - } - - /** - * Similar to {@link #getProjects()}, but also includes the member projects of {@link YarnWorkspaceProject}s. - */ - def Iterable getAllProjects() { - return this.projects.flatMap[p| - if (p instanceof YarnWorkspaceProject) - #[p] + p.memberProjects + p.nodeModuleProjects - else - #[p] - ]; - } - - def void simplifyIfPossible() { - if (isYarnToSingleProjectConvertable()) { - val YarnWorkspaceProject yarnWorkspaceProject = getProjects().get(0) as YarnWorkspaceProject; - val Project project = yarnWorkspaceProject.getMemberProjects().iterator().next(); - clearProjects(); - addProject(project); - } - } - - def boolean isYarnToSingleProjectConvertable() { - return getProjects().size() == 1 - && getAllProjects().size() == 2 // this includes the yarn project and the single project in packages - && getProjects().get(0) instanceof YarnWorkspaceProject - && (getProjects().get(0) as YarnWorkspaceProject).getMemberProjects().size() == 1; - } - - public def File create(Path parentDirectoryPath) { - var File wsDirectory = Objects.requireNonNull(parentDirectoryPath).toFile - if (!wsDirectory.exists) - throw new IOException("'" + wsDirectory + "' does not exist") - if (!wsDirectory.directory) - throw new IOException("'" + wsDirectory + "' is not a directory"); - - if (wsDirectory.exists) - FileDeleter.delete(wsDirectory); - wsDirectory.mkdirs(); - - for (project : projects) - project.create(wsDirectory.toPath()); - - return wsDirectory; - } -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/YarnWorkspaceProject.java b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/YarnWorkspaceProject.java new file mode 100644 index 0000000000..28795ee0d0 --- /dev/null +++ b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/YarnWorkspaceProject.java @@ -0,0 +1,159 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tests.codegen; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.packagejson.projectDescription.ProjectType; +import org.eclipse.n4js.utils.Strings; + +/** + * Generates the code for a yarn workspace project. + */ +public class YarnWorkspaceProject extends Project { + + /** Name of the 'packages' folder, i.e. the folder containing the actual projects. */ + public static final String PACKAGES = "packages"; + + final Map> memberProjects = new LinkedHashMap<>(); + + /** + * Same as {@link Project#Project(String, String, String, ProjectType)}, but with a default project type of + * {@link ProjectType#PLAINJS PLAINJS}. + */ + public YarnWorkspaceProject(String projectName, String vendorId, String vendorName) { + this(projectName, vendorId, vendorName, PACKAGES); + } + + /** + * Same as {@link Project#Project(String, String, String, ProjectType)}, but with a default project type of + * {@link ProjectType#PLAINJS PLAINJS}. + */ + public YarnWorkspaceProject(String projectName, String vendorId, String vendorName, String workspacesFolderName) { + super(projectName, vendorId, vendorName, ProjectType.PLAINJS); + this.memberProjects.put(workspacesFolderName, new HashMap<>()); + } + + public void addWorkspaceName(String workspacesFolderName) { + this.memberProjects.put(workspacesFolderName, new HashMap<>()); + } + + public void addMemberProject(Project project) { + addMemberProject(memberProjects.keySet().iterator().next(), project); + } + + public void addMemberProject(String workspaceFolderName, Project project) { + this.memberProjects.putIfAbsent(workspaceFolderName, new HashMap<>()); + this.memberProjects.get(workspaceFolderName).put(project.getName(), project); + } + + public Collection getMemberProjects() { + Collection projects = new ArrayList<>(); + for (Map name2prj : memberProjects.values()) { + projects.addAll(name2prj.values()); + } + return projects; + } + + public Project getMemberProject(String _projectName) { + return getMemberProject(memberProjects.keySet().iterator().next(), _projectName); + } + + public Project getMemberProject(String workspaceFolderName, String _projectName) { + return this.memberProjects.get(workspaceFolderName).get(_projectName); + } + + /** + * Generates the {@link N4JSGlobals#PACKAGE_JSON} for this project. + */ + @Override + public String generate() { + if (!com.google.common.base.Strings.isNullOrEmpty(projectDescriptionContent)) { + return projectDescriptionContent; + } + + String workspaces = Strings.join(", ", ws -> "\"%s/*\"".formatted(ws), memberProjects.keySet()); + String deps = Strings.join(",\n", d -> "\"%s\": \"*\"".formatted(d), projectDependencies); + + return """ + { + "name": "%s", + "version": "%s", + "private": true, + "workspaces": [ + %s + ], + "dependencies": { + %s + } + } + """.formatted( + getName(), + getVersion(), + workspaces, + deps); + } + + /** + * Creates this project in the given parent directory, which must exist. + * + * This method first creates a directory with the same name as the {@link #projectName} within the given parent + * directory. If there already exists a file or directory with that name within the given parent directory, that + * file or directory will be (recursively) deleted. + * + * Afterward, the package.json file and the source folders are created within the newly created project directory. + * + * @param parentDirectoryPath + * the path to the parent directory + * + * @return the project directory + */ + @Override + public File create(Path parentDirectoryPath) throws IOException { + super.create(parentDirectoryPath); + + File parentDirectory = Objects.requireNonNull(parentDirectoryPath).toFile(); + File projectDirectory = new File(parentDirectory, getName()); + File nodeModulesDirectory = new File(projectDirectory, N4JSGlobals.NODE_MODULES); + nodeModulesDirectory.mkdirs(); + + for (String workspacesFolderName : memberProjects.keySet()) { + File workspacesDirectory = new File(new File(parentDirectory, getName()), workspacesFolderName); + rmkdirs(workspacesDirectory); + + createWorkspaceProjects(memberProjects.get(workspacesFolderName), nodeModulesDirectory, + workspacesDirectory); + } + + return projectDirectory; + } + + private void createWorkspaceProjects(Map name2projects, File nodeModulesDirectory, + File parentDirectory) throws IOException { + + for (Project project : name2projects.values()) { + File projectDir = project.create(parentDirectory.toPath()); + Path symProjectDirectory = new File(nodeModulesDirectory, project.getName()).toPath(); + symProjectDirectory.getParent().toFile().mkdir(); + Files.createSymbolicLink(symProjectDirectory, projectDir.toPath()); + } + } +} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/YarnWorkspaceProject.xtend b/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/YarnWorkspaceProject.xtend deleted file mode 100644 index a457efa27a..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/src/org/eclipse/n4js/tests/codegen/YarnWorkspaceProject.xtend +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tests.codegen - -import java.io.File -import java.nio.file.Files -import java.nio.file.Path -import java.util.Collection -import java.util.Map -import java.util.Objects -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.packagejson.projectDescription.ProjectType -import org.eclipse.n4js.utils.Strings - -/** - * Generates the code for a yarn workspace project. - */ -public class YarnWorkspaceProject extends Project { - - /** Name of the 'packages' folder, i.e. the folder containing the actual projects. */ - public static final String PACKAGES = "packages"; - - final Map> memberProjects = newLinkedHashMap(); - - /** - * Same as {@link #Project(String, String, String, ProjectType)}, but with - * a default project type of {@link ProjectType#PLAINJS PLAINJS}. - */ - public new(String projectName, String vendorId, String vendorName) { - this(projectName, vendorId, vendorName, PACKAGES); - } - - /** - * Same as {@link #Project(String, String, String, ProjectType)}, but with - * a default project type of {@link ProjectType#PLAINJS PLAINJS}. - */ - public new(String projectName, String vendorId, String vendorName, String workspacesFolderName) { - super(projectName, vendorId, vendorName, ProjectType.PLAINJS); - this.memberProjects.put(workspacesFolderName, newHashMap()); - } - - public def void addWorkspaceName(String workspacesFolderName) { - this.memberProjects.put(workspacesFolderName, newHashMap()); - } - - public def void addMemberProject(Project project) { - addMemberProject(memberProjects.keySet.iterator.next, project); - } - - public def void addMemberProject(String workspaceFolderName, Project project) { - this.memberProjects.putIfAbsent(workspaceFolderName, newHashMap()); - this.memberProjects.get(workspaceFolderName).put(project.name, project); - } - - public def Collection getMemberProjects() { - val projects = newArrayList(); - for (name2prj : memberProjects.values) - projects.addAll(name2prj.values); - return projects; - } - - public def Project getMemberProject(String projectName) { - getMemberProject(memberProjects.keySet.iterator.next, projectName); - } - - public def Project getMemberProject(String workspaceFolderName, String projectName) { - return this.memberProjects.get(workspaceFolderName).get(projectName); - } - - - /** - * Generates the {@link N4JSGlobals#PACKAGE_JSON} for this project. - */ - public override String generate() ''' - «IF !projectDescriptionContent.nullOrEmpty»« - projectDescriptionContent» - «ELSE» - { - "name": "«name»", - "version": "«version»", - "private": true, - "workspaces": [ - «Strings.join(", ", [wsName | '''"«wsName»/*"'''], memberProjects.keySet())» - ], - "dependencies": { - «IF !projectDependencies.nullOrEmpty» - «FOR dep : projectDependencies SEPARATOR ','» - "«dep»": "*" - «ENDFOR» - «ENDIF» - } - } - «ENDIF» - ''' - - - /** - * Creates this project in the given parent directory, which must exist. - * - * This method first creates a directory with the same name as the {@link #projectName} within - * the given parent directory. If there already exists a file or directory with that name - * within the given parent directory, that file or directory will be (recursively) deleted. - * - * Afterward, the package.json file and the source folders are created within the newly created - * project directory. - * - * @param parentDirectoryPath the path to the parent directory - * - * @return the project directory - */ - public override File create(Path parentDirectoryPath) { - super.create(parentDirectoryPath); - - var File parentDirectory = Objects.requireNonNull(parentDirectoryPath).toFile - val File projectDirectory = new File(parentDirectory, name); - val File nodeModulesDirectory = new File(projectDirectory, N4JSGlobals.NODE_MODULES); - nodeModulesDirectory.mkdirs(); - - for (workspacesFolderName : memberProjects.keySet()) { - val File workspacesDirectory = new File(new File(parentDirectory, name), workspacesFolderName); - rmkdirs(workspacesDirectory); - - createWorkspaceProjects(memberProjects.get(workspacesFolderName), nodeModulesDirectory, workspacesDirectory); - } - - return projectDirectory; - } - - private def void createWorkspaceProjects(Map name2projects, File nodeModulesDirectory, File parentDirectory) { - for (project: name2projects.values()) { - val projectDir = project.create(parentDirectory.toPath); - val Path symProjectDirectory = new File(nodeModulesDirectory, project.name).toPath(); - symProjectDirectory.parent.toFile.mkdir; - Files.createSymbolicLink(symProjectDirectory, projectDir.toPath()); - } - } -} diff --git a/testhelpers/org.eclipse.n4js.tests.helper/xtend-gen/.gitignore b/testhelpers/org.eclipse.n4js.tests.helper/xtend-gen/.gitignore deleted file mode 100644 index c96a04f008..0000000000 --- a/testhelpers/org.eclipse.n4js.tests.helper/xtend-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/org.eclipse.n4js.accesscontrol.tests/src/org/eclipse/n4js/accesscontrol/tests/ScenarioGenerator.java b/tests/org.eclipse.n4js.accesscontrol.tests/src/org/eclipse/n4js/accesscontrol/tests/ScenarioGenerator.java index 8f32511363..afe67dd6e2 100644 --- a/tests/org.eclipse.n4js.accesscontrol.tests/src/org/eclipse/n4js/accesscontrol/tests/ScenarioGenerator.java +++ b/tests/org.eclipse.n4js.accesscontrol.tests/src/org/eclipse/n4js/accesscontrol/tests/ScenarioGenerator.java @@ -11,6 +11,7 @@ package org.eclipse.n4js.accesscontrol.tests; import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.util.LinkedList; import java.util.List; @@ -62,7 +63,7 @@ class ScenarioGenerator { * the path to generate the scenario in * @return a list files representing the root directories of the created projects */ - public List generateScenario(Path destination) { + public List generateScenario(Path destination) throws IOException { List result = new LinkedList<>(); // Create the required classifiers for the scenario. diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/bugreports/pending/GH_2004_ReviewImportedNamesComputationTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/bugreports/pending/GH_2004_ReviewImportedNamesComputationTest.java index 28bef84c5f..1c3bd2ac11 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/bugreports/pending/GH_2004_ReviewImportedNamesComputationTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/bugreports/pending/GH_2004_ReviewImportedNamesComputationTest.java @@ -57,7 +57,7 @@ public m() {} new FileEvent(otherFileURI.toString(), FileChangeType.Deleted)))); joinServerRequests(); assertIsAffectedBug("ProjectOther/" + PACKAGE_JSON, - "(Error, [8:17 - 8:34], Main module specifier some/path/Other does not exist.)"); + "(Error, [9:16 - 9:33], Main module specifier some/path/Other does not exist.)"); } @Test diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderNoRebuildTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderNoRebuildTest.java index b03e6f4045..78204d0b3c 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderNoRebuildTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderNoRebuildTest.java @@ -54,18 +54,18 @@ public void testStableCycleNoRebuild() { assertIssues2( Pair.of("P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)")), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)")), Pair.of("P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); executeCommand(N4JSCommandService.N4JS_REFRESH); joinServerRequests(); assertIssues2( Pair.of("P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)")), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)")), Pair.of("P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); } @Override @@ -73,15 +73,13 @@ protected Optional> getOverridingModule() { return Optional.of(Test_Module.class); } - public static final class Test_Module extends AbstractGenericModule { - + public Class bindXWorkspaceManager() { return Test_N4JSWorkspaceManager.class; } } - @Singleton public static class Test_N4JSWorkspaceManager extends N4JSWorkspaceManager { @Override diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderTest.java index 59566bef79..a7a2cdc39b 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/CyclicDependenciesBuilderTest.java @@ -105,9 +105,9 @@ public void testAddCycle() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:23 - 16:27], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:38 - 14:42], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); } @Test @@ -118,9 +118,9 @@ public void testRemoveCycle() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); openFile("P1/package.json"); @@ -139,9 +139,9 @@ public void testOnlyDirtyStateBuildInsideCycle() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); openFile("M1"); @@ -149,27 +149,27 @@ public void testOnlyDirtyStateBuildInsideCycle() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "M1", List.of("(Error, [0:25 - 1:0], extraneous input '#\\n' expecting '}')"))); saveOpenedFile("M1"); assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "M1", List.of("(Error, [0:25 - 1:0], extraneous input '#\\n' expecting '}')"))); closeFile("M1"); assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); } @Test @@ -180,9 +180,9 @@ public void testBuildDirtyAfterRemoveCycle1() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); openFile("M1"); @@ -190,27 +190,27 @@ public void testBuildDirtyAfterRemoveCycle1() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "M1", List.of("(Error, [0:25 - 1:0], extraneous input '#\\n' expecting '}')"))); saveOpenedFile("M1"); assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "M1", List.of("(Error, [0:25 - 1:0], extraneous input '#\\n' expecting '}')"))); closeFile("M1"); assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); // remove cycle openFile("P1/package.json"); @@ -229,9 +229,9 @@ public void testBuildDirtyAfterRemoveCycle2() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"))); openFile("M1"); @@ -239,18 +239,18 @@ public void testBuildDirtyAfterRemoveCycle2() { assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "M1", List.of("(Error, [0:25 - 1:0], extraneous input '#\\n' expecting '}')"))); saveOpenedFile("M1"); assertIssues2(Map.of( "P1/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "P2/package.json", List.of( - "(Error, [16:3 - 16:7], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), + "(Error, [14:18 - 14:22], Dependency cycle of the projects: yarn-test-project/packages/P1, yarn-test-project/packages/P2.)"), "M1", List.of("(Error, [0:25 - 1:0], extraneous input '#\\n' expecting '}')"))); // remove cycle diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderWorkspaceChangesTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderWorkspaceChangesTest.java index 6c246c317f..14e0950488 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderWorkspaceChangesTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderWorkspaceChangesTest.java @@ -55,7 +55,7 @@ public m() {} "(Error, [0:26 - 0:33], Cannot resolve plain module specifier (without project name as first segment): no matching module found.)", "(Error, [1:5 - 1:15], Couldn't resolve reference to IdentifiableElement 'OtherClass'.)"), "MainProject/" + PACKAGE_JSON, List.of( - "(Error, [16:3 - 16:21], Project does not exist with project ID: OtherProject.)")); + "(Error, [14:18 - 14:36], Project does not exist with project ID: OtherProject.)")); @Test public void testCreateProject() throws IOException { @@ -286,7 +286,7 @@ public m() {} "OtherProject/package.json", List.of( " (Warning, [1:9 - 1:25], As a convention the package name 'RenamedProject' should match the name of the project folder 'OtherProject' on the file system.)"), "MainProject/package.json", List.of( - " (Error, [16:3 - 16:21], Project does not exist with project ID: OtherProject.)"), + " (Error, [14:18 - 14:36], Project does not exist with project ID: OtherProject.)"), "Main.n4js", List.of( " (Error, [0:26 - 0:33], Cannot resolve plain module specifier (without project name as first segment): no matching module found.)", " (Error, [1:5 - 1:15], Couldn't resolve reference to IdentifiableElement 'OtherClass'.)"))); @@ -419,10 +419,10 @@ private void doTestAddRemoveDependency(Pair dependency, boolean // unfortunately we have an additional error in the open, non-saved package.json file when a dependency to a // plain-JS-project is added // (due to the optimization in ProjectDiscoveryHelper of hiding all unnecessary PLAINJS projects) - int tpnLength = 29 + targetProjectName.length(); + int tpnLength = 44 + targetProjectName.length(); Map> errorsBeforeSaving = new HashMap<>(originalErrors); errorsBeforeSaving.put(sourceProjectName + "/" + PACKAGE_JSON, List.of( - "(Error, [16:23 - 16:" + tpnLength + "], Project does not exist with project ID: " + "(Error, [14:38 - 14:" + tpnLength + "], Project does not exist with project ID: " + targetProjectName + ".)")); assertIssues2(errorsBeforeSaving); // changes in package.json not saved yet, so still the original errors + // 1 error in the unsaved package.json editor diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/InitialBuildTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/InitialBuildTest.java index 38199da176..ebd318bede 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/InitialBuildTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/InitialBuildTest.java @@ -219,7 +219,7 @@ public void testAddRemoveProjectBetweenServerSessions() throws IOException { "(Error, [0:25 - 0:37], Cannot resolve plain module specifier (without project name as first segment): no matching module found.)", "(Error, [1:10 - 1:19], Couldn't resolve reference to Type 'SomeClass'.)")), Pair.of("ClientProject/" + PACKAGE_JSON, List.of( - "(Error, [16:3 - 16:24], Project does not exist with project ID: ProviderProject.)")) }; + "(Error, [14:18 - 14:39], Project does not exist with project ID: ProviderProject.)")) }; assertIssues2(errorsWithProviderProjectMissing); shutdownLspServer(); diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/RebuildFindsNewNpmPackageTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/RebuildFindsNewNpmPackageTest.java index 8d89cdae6e..986d8568ba 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/RebuildFindsNewNpmPackageTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/RebuildFindsNewNpmPackageTest.java @@ -75,7 +75,7 @@ private void performTest(File rootProjectFolder, File mainProjectFolder) throws getFileURIFromModuleName("Main"), List.of( "(Error, [0:21 - 0:32], Cannot resolve plain module specifier (without project name as first segment): no matching module found.)"), packageJsonFileURI, List.of( - "(Error, [16:3 - 16:12], Project does not exist with project ID: lib.)")); + "(Error, [14:18 - 14:27], Project does not exist with project ID: lib.)")); assertIssues(errorsWhenNpmPackageMissing); // create the missing npm package (without notifications to the server) diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/spec/ImportsUnresolvedTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/spec/ImportsUnresolvedTest.java index 580339502d..751b2b6ebf 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/spec/ImportsUnresolvedTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/spec/ImportsUnresolvedTest.java @@ -127,6 +127,6 @@ public void testProjectImport_mainModuleDefinedButDoesNotExist() { "Main", List.of( "(Error, [0:16 - 0:30], Cannot resolve project import: no matching module found.)"), "OtherProject/" + PACKAGE_JSON, List.of( - "(Error, [8:17 - 8:24], Main module specifier Other does not exist.)"))); + "(Error, [9:16 - 9:23], Main module specifier Other does not exist.)"))); } } diff --git a/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/MultiProjectIdeTest.java b/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/MultiProjectIdeTest.java index df127412d9..08b9aa5b5d 100644 --- a/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/MultiProjectIdeTest.java +++ b/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/MultiProjectIdeTest.java @@ -138,7 +138,7 @@ class C extends D {} "(Error, [0:18 - 0:21], Cannot resolve plain module specifier (without project name as first segment): no matching module found.)", "(Error, [1:16 - 1:17], Couldn't resolve reference to Type 'D'.)")), Pair.of("multiProjectTest.first/package.json", List.of( - "(Error, [17:8 - 17:27], Project does not exist with project ID: thirdProject.)"))); + "(Error, [18:8 - 18:27], Project does not exist with project ID: thirdProject.)"))); FileURI fileURI = toFileURI(getProjectLocation().toPath().resolve("thirdProject")); fileURI.appendSegment("src").toFile().mkdirs(); @@ -199,7 +199,7 @@ export public class D {} joinServerRequests(); assertIssues2( Pair.of("multiProjectTest.first/package.json", List.of( - "(Error, [16:3 - 16:32], Project does not exist with project ID: multiProjectTest.second.)")), + "(Error, [14:18 - 14:47], Project does not exist with project ID: multiProjectTest.second.)")), Pair.of("C", List.of( "(Error, [0:18 - 0:21], Cannot resolve plain module specifier (without project name as first segment): no matching module found.)", "(Error, [1:16 - 1:17], Couldn't resolve reference to Type 'D'.)"))); @@ -266,21 +266,21 @@ public void testChangeProjectTypeWithoutOpenedEditors() { joinServerRequests(); assertIssues2( Pair.of(PROJECT1_NAME + "/package.json", List.of( - "(Warning, [7:58 - 7:83], Project multiProjectTest.second of type library cannot be declared among the required runtime libraries.)"))); + "(Warning, [8:58 - 8:83], Project multiProjectTest.second of type library cannot be declared among the required runtime libraries.)"))); changeNonOpenedFile(toFileURI(getPackageJsonFile(PROJECT2_NAME)), Pair.of("\"projectType\": \"library\"", "\"projectType\": \"runtimeLibrary\"")); joinServerRequests(); assertIssues2( Pair.of(PROJECT2_NAME + "/package.json", List.of( - "(Warning, [16:3 - 16:21], Project n4js-runtime of type runtime environment cannot be declared among the dependencies or devDependencies.)"))); + "(Warning, [14:18 - 14:36], Project n4js-runtime of type runtime environment cannot be declared among the dependencies or devDependencies.)"))); changeNonOpenedFile(toFileURI(getPackageJsonFile(PROJECT2_NAME)), Pair.of("\"projectType\": \"runtimeLibrary\"", "\"projectType\": \"library\"")); joinServerRequests(); assertIssues2( Pair.of(PROJECT1_NAME + "/package.json", List.of( - "(Warning, [7:58 - 7:83], Project multiProjectTest.second of type library cannot be declared among the required runtime libraries.)"))); + "(Warning, [8:58 - 8:83], Project multiProjectTest.second of type library cannot be declared among the required runtime libraries.)"))); } @Test @@ -299,7 +299,7 @@ class C {} joinServerRequests(); assertIssues2( Pair.of(PROJECT1_NAME + "/package.json", List.of( - "(Warning, [9:30 - 9:35], Source container path ext does not exist.)"))); + "(Warning, [10:29 - 10:34], Source container path ext does not exist.)"))); File extFolder = getProjectRoot(PROJECT1_NAME).toPath().resolve("ext").toFile(); Assert.assertTrue("External folder 'ext' should be missing", !extFolder.exists()); @@ -320,7 +320,7 @@ class C {} cleanBuildAndWait(); assertIssues2( Pair.of(PROJECT1_NAME + "/package.json", List.of( - "(Warning, [9:30 - 9:35], Source container path ext does not exist.)"))); + "(Warning, [10:29 - 10:34], Source container path ext does not exist.)"))); } private void addSecondProjectToDependencies() throws IOException { diff --git a/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/NoValidationIdeTest.java b/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/NoValidationIdeTest.java index 27ae81a8c0..8f3a44ed68 100644 --- a/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/NoValidationIdeTest.java +++ b/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/NoValidationIdeTest.java @@ -147,7 +147,7 @@ export public class Shadow {} assertIssues2( Pair.of(DEFAULT_PROJECT_NAME + "/package.json", List.of( - "(Error, [7:69 - 7:79], Module filters of type noValidate must not match N4JS modules/files.)"))); + "(Error, [8:69 - 8:79], Module filters of type noValidate must not match N4JS modules/files.)"))); assertEquals("file package.json should have 1 marker since the module filter is invalid", 1, getIssuesInFile(packageJsonFileURI).size()); assertEquals("file src/js/Shadowed.js should have 0 markers", 0, getIssuesInFile(fileImpl).size()); diff --git a/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/SingleProjectIdeTest.java b/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/SingleProjectIdeTest.java index dc58d12221..0c13d003b6 100644 --- a/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/SingleProjectIdeTest.java +++ b/tests/org.eclipse.n4js.ui.tests/src/org/eclipse/n4js/tests/project/SingleProjectIdeTest.java @@ -249,7 +249,7 @@ class C extends D {} assertIssues2( Pair.of(DEFAULT_PROJECT_NAME + "/package.json", List.of( - "(Warning, [12:16 - 12:22], Source container path src3 does not exist.)")), + "(Warning, [13:16 - 13:22], Source container path src3 does not exist.)")), Pair.of("C", List.of( "(Error, [0:18 - 0:21], Cannot resolve plain module specifier (without project name as first segment): no matching module found.)", "(Error, [1:16 - 1:17], Couldn't resolve reference to Type 'D'.)")));