diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.java
new file mode 100644
index 0000000000..bc4edaec4d
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.java
@@ -0,0 +1,521 @@
+/**
+ * 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.compileTime;
+
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.symbolObjectType;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.map;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.n4js.compileTime.CompileTimeValue.ValueBoolean;
+import org.eclipse.n4js.compileTime.CompileTimeValue.ValueInvalid;
+import org.eclipse.n4js.compileTime.CompileTimeValue.ValueNumber;
+import org.eclipse.n4js.n4JS.AdditiveExpression;
+import org.eclipse.n4js.n4JS.BinaryLogicalExpression;
+import org.eclipse.n4js.n4JS.BooleanLiteral;
+import org.eclipse.n4js.n4JS.ConditionalExpression;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.IdentifierRef;
+import org.eclipse.n4js.n4JS.MultiplicativeExpression;
+import org.eclipse.n4js.n4JS.N4FieldDeclaration;
+import org.eclipse.n4js.n4JS.N4JSPackage;
+import org.eclipse.n4js.n4JS.NullLiteral;
+import org.eclipse.n4js.n4JS.NumericLiteral;
+import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression;
+import org.eclipse.n4js.n4JS.ParenExpression;
+import org.eclipse.n4js.n4JS.Script;
+import org.eclipse.n4js.n4JS.StringLiteral;
+import org.eclipse.n4js.n4JS.TemplateLiteral;
+import org.eclipse.n4js.n4JS.TemplateSegment;
+import org.eclipse.n4js.n4JS.UnaryExpression;
+import org.eclipse.n4js.n4JS.VariableDeclaration;
+import org.eclipse.n4js.postprocessing.ASTMetaInfoCache;
+import org.eclipse.n4js.postprocessing.ASTMetaInfoUtils;
+import org.eclipse.n4js.postprocessing.ASTProcessor;
+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.TConstableElement;
+import org.eclipse.n4js.ts.types.TEnum;
+import org.eclipse.n4js.ts.types.TEnumLiteral;
+import org.eclipse.n4js.ts.types.TField;
+import org.eclipse.n4js.ts.types.TMember;
+import org.eclipse.n4js.ts.types.TypesPackage;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.utils.ContainerTypesHelper;
+import org.eclipse.n4js.utils.N4JSLanguageUtils;
+import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind;
+import org.eclipse.n4js.utils.RecursionGuard;
+import org.eclipse.n4js.utils.Strings;
+import org.eclipse.n4js.validation.N4JSElementKeywordProvider;
+import org.eclipse.n4js.validation.validators.N4JSExpressionValidator;
+import org.eclipse.xtext.xbase.lib.IterableExtensions;
+
+import com.google.inject.Inject;
+
+/**
+ * Helper class to evaluate compile-time expressions.
+ *
+ * IMPORTANT IMPLEMENTATION NOTES:
+ *
+ * - It is a design decision1 to handle compile-time evaluation and computed property names as a separate,
+ * up-front phase during post-processing before the main AST traversal begins (for details about the phases of
+ * post-processing, see method {@link ASTProcessor#processAST(RuleEnvironment, Script, ASTMetaInfoCache)}).
+ *
- To achieve this, we must avoid using type information during compile-time evaluation, because typing is
+ * part of main AST traversal, so typing an AST node would inevitably start the main AST traversal.
+ *
- the only place where this limitation becomes tricky is the evaluation of property access expressions, see
+ * {@link #eval(RuleEnvironment, ParameterizedPropertyAccessExpression, RecursionGuard)}.
+ *
+ *
+ * 1 main rationale for this decision was to keep the handling of computed property names from complicating
+ * the scoping and main AST traversal. Without resolving computed property names beforehand, all the code in scoping,
+ * AST traversal, type system, and helper classes such as {@link ContainerTypesHelper} would have to cope with
+ * unresolved property names, i.e. {@code #getName()} on a property or member would return null
or trigger
+ * some potentially complex computation in the background that might confuse AST traversal.
+ */
+public class CompileTimeEvaluator {
+
+ @Inject
+ private N4JSElementKeywordProvider keywordProvider;
+
+ /**
+ * IMPORTANT: CLIENT CODE SHOULD NOT CALL THIS METHOD!
+ * Instead, read compile-time values from the cache using method
+ * {@link ASTMetaInfoUtils#getCompileTimeValue(Expression)}.
+ * If the evaluation result of the expression you are interested in is not being cached, add your use case to method
+ * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}. Only expressions for which this method
+ * returns true
will be evaluated and cached during post-processing.
+ *
+ * Computes and returns the value of the given expression as a {@link CompileTimeValue}. If the given expression is
+ * not a valid compile-time expression, the returned value will be {@link CompileTimeValue#isValid() invalid}. Never
+ * returns null
.
+ */
+ public CompileTimeValue evaluateCompileTimeExpression(RuleEnvironment G, Expression expr) {
+ return eval(G, expr, new RecursionGuard<>());
+ }
+
+ // ---------------------------------------------------------------------------------------------------------------
+
+ // catch-all case for expression not handled by any of the more specific dispatch methods
+ private CompileTimeValue eval(RuleEnvironment G, Expression expr, RecursionGuard guard) {
+ if (expr instanceof ParenExpression) {
+ Expression expression = ((ParenExpression) expr).getExpression();
+ if (expression == null) {
+ return CompileTimeValue.error();
+ }
+ return eval(G, expression, guard);
+ }
+ if (expr instanceof NullLiteral) {
+ return CompileTimeValue.NULL;
+ }
+ if (expr instanceof BooleanLiteral) {
+ return CompileTimeValue.of(((BooleanLiteral) expr).isTrue());
+ }
+ if (expr instanceof NumericLiteral) {
+ return CompileTimeValue.of(((NumericLiteral) expr).getValue());
+ }
+ if (expr instanceof StringLiteral) {
+ return CompileTimeValue.of(((StringLiteral) expr).getValue());
+ }
+ if (expr instanceof TemplateSegment) {
+ return CompileTimeValue.of(((TemplateSegment) expr).getValue());
+ }
+ if (expr instanceof TemplateLiteral) {
+ return eval(G, (TemplateLiteral) expr, guard);
+ }
+ if (expr instanceof UnaryExpression) {
+ return eval(G, (UnaryExpression) expr, guard);
+ }
+ if (expr instanceof AdditiveExpression) {
+ return eval(G, (AdditiveExpression) expr, guard);
+ }
+ if (expr instanceof MultiplicativeExpression) {
+ return eval(G, (MultiplicativeExpression) expr, guard);
+ }
+ if (expr instanceof BinaryLogicalExpression) {
+ return eval(G, (BinaryLogicalExpression) expr, guard);
+ }
+ if (expr instanceof ConditionalExpression) {
+ return eval(G, (ConditionalExpression) expr, guard);
+ }
+ if (expr instanceof IdentifierRef) {
+ return eval(G, (IdentifierRef) expr, guard);
+ }
+ if (expr instanceof ParameterizedPropertyAccessExpression) {
+ return eval(G, (ParameterizedPropertyAccessExpression) expr, guard);
+ }
+
+ return CompileTimeValue.error(keywordProvider.keywordWithIndefiniteArticle(expr)
+ + " is never a compile-time expression", expr);
+ }
+
+ private CompileTimeValue eval(RuleEnvironment G, TemplateLiteral expr, RecursionGuard guard) {
+ StringBuilder buff = new StringBuilder();
+ List invalidValues = new ArrayList<>();
+ for (Expression seg : expr.getSegments()) {
+ CompileTimeValue segValue = eval(G, seg, guard);
+ if (segValue.isValid()) {
+ buff.append(segValue.toString());
+ } else {
+ invalidValues.add(segValue);
+ }
+ }
+ if (!invalidValues.isEmpty()) {
+ return CompileTimeValue.combineErrors(invalidValues.toArray(new CompileTimeValue[0]));
+ }
+ return CompileTimeValue.of(buff.toString());
+ }
+
+ private CompileTimeValue eval(RuleEnvironment G, UnaryExpression expr, RecursionGuard guard) {
+ CompileTimeValue value = (expr.getExpression() != null) ? eval(G, expr.getExpression(), guard) : null;
+ switch (expr.getOp()) {
+ case NOT:
+ return CompileTimeValue.invert(value, expr.getExpression());
+ case POS: {
+ ValueInvalid tmpV = CompileTimeValue.requireValueType(value, ValueNumber.class, "operand must be a number",
+ expr.getExpression());
+ return tmpV != null ? tmpV : value;
+ }
+ case NEG:
+ return CompileTimeValue.negate(value, expr.getExpression());
+ case VOID:
+ return CompileTimeValue.UNDEFINED;
+ default:
+ return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr);
+ }
+ }
+
+ private CompileTimeValue eval(RuleEnvironment G, AdditiveExpression expr, RecursionGuard guard) {
+ Expression lhs = expr.getLhs();
+ Expression rhs = expr.getRhs();
+ CompileTimeValue leftValue = (lhs != null) ? eval(G, lhs, guard) : null;
+ CompileTimeValue rightValue = (rhs != null) ? eval(G, rhs, guard) : null;
+ switch (expr.getOp()) {
+ case ADD:
+ return CompileTimeValue.add(leftValue, rightValue, expr);
+ case SUB:
+ return CompileTimeValue.subtract(leftValue, rightValue, lhs, rhs);
+ default:
+ return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr);
+ }
+ }
+
+ private CompileTimeValue eval(RuleEnvironment G, MultiplicativeExpression expr, RecursionGuard guard) {
+ Expression lhs = expr.getLhs();
+ Expression rhs = expr.getRhs();
+ CompileTimeValue leftValue = (lhs != null) ? eval(G, lhs, guard) : null;
+ CompileTimeValue rightValue = (rhs != null) ? eval(G, rhs, guard) : null;
+ switch (expr.getOp()) {
+ case TIMES:
+ return CompileTimeValue.multiply(leftValue, rightValue, lhs, rhs);
+ case DIV:
+ return CompileTimeValue.divide(leftValue, rightValue, lhs, rhs);
+ case MOD:
+ return CompileTimeValue.remainder(leftValue, rightValue, lhs, rhs);
+ default:
+ return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr);
+ }
+ }
+
+ private CompileTimeValue eval(RuleEnvironment G, BinaryLogicalExpression expr, RecursionGuard guard) {
+ Expression lhs = expr.getLhs();
+ Expression rhs = expr.getRhs();
+ CompileTimeValue leftValue = (lhs != null) ? eval(G, lhs, guard) : null;
+ CompileTimeValue rightValue = (rhs != null) ? eval(G, rhs, guard) : null;
+ switch (expr.getOp()) {
+ case AND:
+ return CompileTimeValue.and(leftValue, rightValue, lhs, rhs);
+ case OR:
+ return CompileTimeValue.or(leftValue, rightValue, lhs, rhs);
+ default:
+ return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr);
+ }
+ }
+
+ private CompileTimeValue eval(RuleEnvironment G, ConditionalExpression expr, RecursionGuard guard) {
+ Expression condition = expr.getExpression();
+ Expression trueExpr = expr.getTrueExpression();
+ Expression falseExpr = expr.getFalseExpression();
+ CompileTimeValue conditionValue = (condition != null) ? eval(G, condition, guard) : null;
+ CompileTimeValue trueValue = (trueExpr != null) ? eval(G, trueExpr, guard) : null;
+ CompileTimeValue falseValue = (falseExpr != null) ? eval(G, falseExpr, guard) : null;
+ ValueInvalid requireValueType = CompileTimeValue.requireValueType(conditionValue, ValueBoolean.class,
+ "condition must be a boolean",
+ expr.getExpression());
+ ValueInvalid error = CompileTimeValue.combineErrors(requireValueType, trueValue, falseValue);
+ if (error != null) {
+ return error;
+ }
+ return (conditionValue != null && ((ValueBoolean) conditionValue).getValue()) ? trueValue : falseValue;
+ }
+
+ private CompileTimeValue eval(RuleEnvironment G, IdentifierRef expr, RecursionGuard guard) {
+ if (N4JSLanguageUtils.isUndefinedLiteral(G, expr)) {
+ return CompileTimeValue.UNDEFINED;
+ }
+ IdentifiableElement id = expr.getId();
+ // ^^ triggers scoping; this is unproblematic, because the scoping of IdentifierRefs does not
+ // require type information and will thus not interfere with our goal of handling compile-time expressions and
+ // computed property names as an up-front preparatory step before main AST traversal.
+ if (id != null && !id.eIsProxy()) {
+ return obtainValueIfConstFieldOrVariable(G, id, expr, guard);
+ }
+ return CompileTimeValue.error();
+ }
+
+ /**
+ * Handles compile-time evaluation of property access expressions.
+ *
+ * IMPORTANT IMPLEMENTATION NOTES:
+ *
+ * - We must not make use of type information during compile-time evaluation (see {@link CompileTimeEvaluator} for
+ * details why this rule exists).
+ *
- Since scoping of property access requires type information, we cannot use this form of scoping.
+ *
- Since this scoping would be triggered when invoking {@code #getProperty()} on the given property access
+ * expression, we cannot make use of that property in this method.
+ *
- APPROACH: avoid using (ordinary) scoping but instead implement custom member lookup for the very limited
+ * cases supported by compile-time expressions.
+ *
+ * YES, this approach introduces an unfortunate duplication of logic, but greatly simplifies other parts of the
+ * system, i.e. (ordinary) scoping, AST traversal, type system.
+ */
+ private CompileTimeValue eval(RuleEnvironment G, ParameterizedPropertyAccessExpression expr,
+ RecursionGuard guard) {
+ Expression targetExpr = expr.getTarget();
+ String propName = expr.getPropertyAsText(); // IMPORTANT: don't invoke expr.getProperty()!!
+ EObject targetElem = null;
+ if (targetExpr instanceof IdentifierRef) {
+ if (targetExpr.eIsProxy()) {
+ return CompileTimeValue.error("Expression is a proxy '" + propName + "'", expr);
+ }
+ targetElem = ((IdentifierRef) targetExpr).getId();
+ }
+ EObject sym = symbolObjectType(G);
+ if (targetElem == sym) {
+ // A) Is 'expr' an access to a built-in symbol, e.g. Symbol.iterator?
+ // IMPORTANT: pass in 'false', to disallow proxy resolution (which would trigger scoping, type inference,
+ // etc.)
+ TMember memberInSym = N4JSLanguageUtils.getAccessedBuiltInSymbol(G, expr, false);
+ if (memberInSym != null) {
+ // yes, it is!
+ return CompileTimeValue.of(memberInSym);
+ } else {
+ return CompileTimeValue.error("Unknown Symbol property '" + propName + "'", expr);
+ }
+ } else if (targetElem instanceof TEnum) {
+ // B) Is 'expr' an access to the literal of a @NumberBased or @StringBased enum?
+ EnumKind enumKind = N4JSLanguageUtils.getEnumKind((TEnum) targetElem);
+ if (enumKind != EnumKind.Normal) {
+ // custom scoping logic!
+ TEnumLiteral litInEnum = IterableExtensions.findFirst(((TEnum) targetElem).getLiterals(),
+ l -> Objects.equals(l.getName(), propName));
+ if (litInEnum != null) {
+ // yes, it is!
+ switch (enumKind) {
+ case Normal:
+ throw new IllegalStateException("cannot happen");
+ case NumberBased:
+ return CompileTimeValue.of(litInEnum.getValueNumber());
+ case StringBased:
+ return CompileTimeValue.of(litInEnum.getValueString());
+ }
+ }
+ }
+ } else if (targetElem instanceof TClassifier) {
+ // C) Is 'expr' an access to a const field initialized by a compile-time expression?
+ // custom scoping logic!
+ TMember member = findFirst(filterNull(((TClassifier) targetElem).getOwnedMembers()),
+ m -> Objects.equals(m.getName(), propName) && m.isReadable() && m.isStatic());
+ // IMPORTANT: don't use "targetElem.findOwnedMember(memberName, false, true)" in previous line, because
+ // #findOwnedMember() will create and cache a MemberByNameAndAccessMap, which will be incomplete if the
+ // TClassifier contains members with unresolved computed property names!
+ if (member instanceof TField && !((TField) member).isHasComputedName()) {
+ // yes, it is!
+ return obtainValueIfConstFieldOrVariable(G, member, expr, guard);
+ } else {
+ // we get here in two cases:
+ //
+ // 1) member not found
+ // -> there are a number of possible reasons:
+ // 1.a) member has a computed property name which was not yet evaluated,
+ // 1.b) member is inherited, consumed, polyfilled, etc.,
+ // 1.c) member does not exist at all.
+ // At this point, i.e. before computed names are processed, we cannot distinguish between these
+ // cases. So we create a dummy error here that will be improved later.
+ //
+ // 2) member was found but it has a (resolved) computed property name (since processing of computed
+ // property names for the current resource has not started yet, this happens only if the member is
+ // located in another file an its full type information, including its name, was found in the index)
+ // -> for consistency with 1.a above, we have to raise an error also in this case
+ return CompileTimeValue.error(new UnresolvedPropertyAccessError(expr));
+ }
+ }
+ // D) all other cases:
+ if (targetElem != sym && !(targetElem instanceof TClassifier || targetElem instanceof TEnum)) {
+ return CompileTimeValue.error(
+ "target of a property access must be a direct reference to a class, interface, or enum", expr,
+ N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET);
+ }
+ return CompileTimeValue.error(
+ "property access must point to const fields, literals of @NumberBased/@StringBased enums, or built-in symbols",
+ expr);
+ }
+
+ // ---------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Iff the given element is a const field or variable with a valid compile-time expression as initializer, then this
+ * method returns its compile-time value; otherwise, an invalid compile-time value with an appropriate error message
+ * is returned. Never returns null
.
+ *
+ * This method only handles infinite recursion; main logic in
+ * {@link #obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment, IdentifiableElement, EObject, RecursionGuard)}.
+ */
+ private CompileTimeValue obtainValueIfConstFieldOrVariable(RuleEnvironment G, IdentifiableElement targetElem,
+ EObject astNodeForErrorMessage, RecursionGuard guard) {
+
+ if (guard.tryNext(targetElem)) {
+ try {
+ return obtainValueIfConstFieldOrVariableUnguarded(G, targetElem, astNodeForErrorMessage, guard);
+ } finally {
+ guard.done(targetElem);
+ }
+ } else {
+ return CompileTimeValue.error("cyclic definition of compile-time expression", astNodeForErrorMessage);
+ }
+ }
+
+ private CompileTimeValue obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment G,
+ IdentifiableElement targetElem, EObject astNodeForErrorMessage, RecursionGuard guard) {
+
+ boolean targetElemIsConst = false;
+ if (targetElem instanceof TConstableElement) {
+ targetElemIsConst = ((TConstableElement) targetElem).isConst();
+ }
+ if (targetElem instanceof N4FieldDeclaration) {
+ targetElemIsConst = ((N4FieldDeclaration) targetElem).isConst();
+ }
+ if (targetElem instanceof VariableDeclaration) {
+ targetElemIsConst = ((VariableDeclaration) targetElem).isConst();
+ }
+ if (!targetElemIsConst) {
+ return CompileTimeValue.error(
+ keywordProvider.keyword(targetElem) + " " + targetElem.getName() + " is not const",
+ astNodeForErrorMessage);
+ }
+
+ CompileTimeValue valueOfTargetElem = obtainCompileTimeValueOfTargetElement(G,
+ astNodeForErrorMessage.eResource(), targetElem, guard);
+ if (valueOfTargetElem != null) {
+ if (valueOfTargetElem instanceof ValueInvalid) {
+ String baseMsg = keywordProvider.keyword(targetElem) + " " + targetElem.getName() +
+ " is const but does not have a compile-time expression as initializer";
+ String msg = combineErrorMessageWithNestedErrors(baseMsg,
+ ((ValueInvalid) valueOfTargetElem).getErrors().toArray(new CompileTimeEvaluationError[0]));
+ EReference feature = null;
+ if (astNodeForErrorMessage instanceof ParameterizedPropertyAccessExpression) {
+ feature = N4JSPackage.eINSTANCE.getParameterizedPropertyAccessExpression_Property();
+ }
+ return CompileTimeValue.error(msg, astNodeForErrorMessage, feature);
+ }
+ return valueOfTargetElem;
+ }
+ return CompileTimeValue.error(
+ "only references to const variables with a compile-time expression as initializer are allowed",
+ astNodeForErrorMessage);
+ }
+
+ private CompileTimeValue obtainCompileTimeValueOfTargetElement(RuleEnvironment G, Resource currentResource,
+ IdentifiableElement targetElem, RecursionGuard guard) {
+
+ if (targetElem.eResource() == currentResource || hasLoadedASTElement(targetElem)) {
+ // 'targetElem' is in same resource OR is in a different resource that already has a fully-loaded AST
+ // -> compute value from the initializer expression of 'targetElem'
+ EObject astNodeOfTargetElem = (targetElem instanceof SyntaxRelatedTElement)
+ ? ((SyntaxRelatedTElement) targetElem).getAstElement()
+ // NOTE: this will never trigger demand-loading of an AST, because above we
+ // ensured that we are still in 'currentResource' OR method #hasLoadedASTElement() has returned true
+ : targetElem // here we simply assume that elem is already an AST node
+ ;
+ Expression expressionOfTargetElem = null;
+ if (astNodeOfTargetElem instanceof N4FieldDeclaration) {
+ expressionOfTargetElem = ((N4FieldDeclaration) astNodeOfTargetElem).getExpression();
+ }
+ if (astNodeOfTargetElem instanceof VariableDeclaration) {
+ expressionOfTargetElem = ((VariableDeclaration) astNodeOfTargetElem).getExpression();
+ }
+ if (expressionOfTargetElem != null) {
+ return eval(G, expressionOfTargetElem, guard);
+ }
+ } else {
+ // 'targetElem' is in another resource with an AST proxy
+ // -> read value from TModule to avoid demand-loading of AST
+ if (targetElem instanceof TConstableElement) {
+ return CompileTimeValue.deserialize(((TConstableElement) targetElem).getCompileTimeValue());
+ }
+ }
+ return null; // no value found
+ }
+
+ /**
+ * Tells if given element has an AST element in an already loaded AST (i.e. it is safe to invoke method
+ * {@code #getASTElement()} without triggering a demand-load of the AST).
+ */
+ private static boolean hasLoadedASTElement(IdentifiableElement elem) {
+ EObject astElemNonResolved = null;
+ if (elem instanceof SyntaxRelatedTElement) {
+ astElemNonResolved = (EObject) elem.eGet(TypesPackage.eINSTANCE.getSyntaxRelatedTElement_AstElement(),
+ false);
+ }
+ return astElemNonResolved != null && !astElemNonResolved.eIsProxy();
+ }
+
+ private static String combineErrorMessageWithNestedErrors(String mainMessage,
+ CompileTimeEvaluationError... nestedErrors) {
+
+ if (nestedErrors.length == 0) {
+ return mainMessage;
+ } else if (nestedErrors.length == 1) {
+ return mainMessage + ": " + nestedErrors[0].getMessageWithLocation();
+ } else {
+ return mainMessage + ":\n- "
+ + Strings.join("\n- ", map(List.of(nestedErrors), e -> e.getMessageWithLocation()));
+ }
+ }
+
+ // ---------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Special kind of {@link CompileTimeEvaluationError} used to denote a particular case in which the
+ * {@link CompileTimeEvaluator} cannot come up with the correct error message and thus delegates finding a proper
+ * message to the validation, i.e. to class {@link N4JSExpressionValidator}.
+ */
+ public static final class UnresolvedPropertyAccessError extends CompileTimeEvaluationError {
+
+ /***/
+ public UnresolvedPropertyAccessError(ParameterizedPropertyAccessExpression astNode) {
+ super("*** UnresolvedPropertyAccessError ***", astNode,
+ N4JSPackage.eINSTANCE.getParameterizedPropertyAccessExpression_Property());
+ }
+
+ /***/
+ public ParameterizedPropertyAccessExpression getAstNodeCasted() {
+ return (ParameterizedPropertyAccessExpression) astNode;
+ }
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.xtend
deleted file mode 100644
index 0f4ebefe9d..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.xtend
+++ /dev/null
@@ -1,447 +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.compileTime
-
-import com.google.inject.Inject
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.emf.ecore.resource.Resource
-import org.eclipse.n4js.compileTime.CompileTimeValue.ValueBoolean
-import org.eclipse.n4js.compileTime.CompileTimeValue.ValueInvalid
-import org.eclipse.n4js.compileTime.CompileTimeValue.ValueNumber
-import org.eclipse.n4js.n4JS.AdditiveExpression
-import org.eclipse.n4js.n4JS.BinaryLogicalExpression
-import org.eclipse.n4js.n4JS.BooleanLiteral
-import org.eclipse.n4js.n4JS.ConditionalExpression
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.IdentifierRef
-import org.eclipse.n4js.n4JS.MultiplicativeExpression
-import org.eclipse.n4js.n4JS.N4FieldDeclaration
-import org.eclipse.n4js.n4JS.N4JSPackage
-import org.eclipse.n4js.n4JS.NullLiteral
-import org.eclipse.n4js.n4JS.NumericLiteral
-import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression
-import org.eclipse.n4js.n4JS.ParenExpression
-import org.eclipse.n4js.n4JS.StringLiteral
-import org.eclipse.n4js.n4JS.TemplateLiteral
-import org.eclipse.n4js.n4JS.TemplateSegment
-import org.eclipse.n4js.n4JS.UnaryExpression
-import org.eclipse.n4js.n4JS.VariableDeclaration
-import org.eclipse.n4js.postprocessing.ASTMetaInfoUtils
-import org.eclipse.n4js.postprocessing.ASTProcessor
-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.TConstableElement
-import org.eclipse.n4js.ts.types.TEnum
-import org.eclipse.n4js.ts.types.TField
-import org.eclipse.n4js.ts.types.TypesPackage
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.utils.ContainerTypesHelper
-import org.eclipse.n4js.utils.N4JSLanguageUtils
-import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind
-import org.eclipse.n4js.utils.RecursionGuard
-import org.eclipse.n4js.validation.N4JSElementKeywordProvider
-import org.eclipse.n4js.validation.validators.N4JSExpressionValidator
-
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-
-/**
- * Helper class to evaluate compile-time expressions.
- *
- * IMPORTANT IMPLEMENTATION NOTES:
- *
- * - It is a design decision1 to handle compile-time evaluation and computed property names as a separate,
- * up-front phase during post-processing before the main AST traversal begins (for details about the phases of
- * post-processing, see method {@link ASTProcessor#processAST(RuleEnvironment, Script, ASTMetaInfoCache)}).
- *
- To achieve this, we must avoid using type information during compile-time evaluation, because typing
- * is part of main AST traversal, so typing an AST node would inevitably start the main AST traversal.
- *
- the only place where this limitation becomes tricky is the evaluation of property access expressions, see
- * {@link #eval(RuleEnvironment, ParameterizedPropertyAccessExpression, RecursionGuard)}.
- *
- *
- * 1 main rationale for this decision was to keep the handling of computed property names from complicating
- * the scoping and main AST traversal. Without resolving computed property names beforehand, all the code in scoping,
- * AST traversal, type system, and helper classes such as {@link ContainerTypesHelper} would have to cope with
- * unresolved property names, i.e. {@code #getName()} on a property or member would return null
or trigger
- * some potentially complex computation in the background that might confuse AST traversal.
- */
-class CompileTimeEvaluator {
-
- @Inject
- private N4JSElementKeywordProvider keywordProvider;
-
-
- /**
- *
- * IMPORTANT: CLIENT CODE SHOULD NOT CALL THIS METHOD!
- * Instead, read compile-time values from the cache using method {@link ASTMetaInfoUtils#getCompileTimeValue(Expression)}.
- * If the evaluation result of the expression you are interested in is not being cached, add your use case to method
- * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}. Only expressions for which this method
- * returns true
will be evaluated and cached during post-processing.
- *
- *
- * Computes and returns the value of the given expression as a {@link CompileTimeValue}. If the given expression is
- * not a valid compile-time expression, the returned value will be {@link CompileTimeValue#isValid() invalid}. Never
- * returns null
.
- */
- def public CompileTimeValue evaluateCompileTimeExpression(RuleEnvironment G, Expression expr) {
- return eval(G, expr, new RecursionGuard());
- }
-
-
- // ---------------------------------------------------------------------------------------------------------------
-
-
- // catch-all case for expression not handled by any of the more specific dispatch methods
- def private dispatch CompileTimeValue eval(RuleEnvironment G, Expression expr, RecursionGuard guard) {
- return CompileTimeValue.error(keywordProvider.keywordWithIndefiniteArticle(expr)
- + " is never a compile-time expression", expr);
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, ParenExpression expr, RecursionGuard guard) {
- if (expr.expression === null)
- return CompileTimeValue.error();
- return eval(G, expr.expression, guard);
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, NullLiteral expr, RecursionGuard guard) {
- return CompileTimeValue.NULL;
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, BooleanLiteral expr, RecursionGuard guard) {
- return CompileTimeValue.of(expr.isTrue);
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, NumericLiteral expr, RecursionGuard guard) {
- return CompileTimeValue.of(expr.value);
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, StringLiteral expr, RecursionGuard guard) {
- return CompileTimeValue.of(expr.value);
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, TemplateSegment expr, RecursionGuard guard) {
- return CompileTimeValue.of(expr.value);
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, TemplateLiteral expr, RecursionGuard guard) {
- val buff = new StringBuilder;
- val invalidValues = newArrayList;
- for (seg : expr.segments) {
- val segValue = eval(G, seg, guard);
- if (segValue.valid) {
- buff.append(segValue.toString);
- } else {
- invalidValues += segValue;
- }
- }
- if (!invalidValues.empty) {
- return CompileTimeValue.combineErrors(invalidValues);
- }
- return CompileTimeValue.of(buff.toString);
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, UnaryExpression expr, RecursionGuard guard) {
- val value = if (expr.expression !== null) eval(G, expr.expression, guard);
- return switch (expr.op) {
- case NOT: CompileTimeValue.invert(value, expr.expression)
- case POS: CompileTimeValue.requireValueType(value, ValueNumber, "operand must be a number", expr.expression) ?: value
- case NEG: CompileTimeValue.negate(value, expr.expression)
- case VOID: CompileTimeValue.UNDEFINED
- default: CompileTimeValue.error("invalid operator: " + expr.op, expr)
- };
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, AdditiveExpression expr, RecursionGuard guard) {
- val lhs = expr.lhs;
- val rhs = expr.rhs;
- val leftValue = if (lhs !== null) eval(G, lhs, guard);
- val rightValue = if (rhs !== null) eval(G, rhs, guard);
- return switch (expr.op) {
- case ADD: CompileTimeValue.add(leftValue, rightValue, expr)
- case SUB: CompileTimeValue.subtract(leftValue, rightValue, lhs, rhs)
- default: CompileTimeValue.error("invalid operator: " + expr.op, expr)
- };
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, MultiplicativeExpression expr, RecursionGuard guard) {
- val lhs = expr.lhs;
- val rhs = expr.rhs;
- val leftValue = if (lhs !== null) eval(G, lhs, guard);
- val rightValue = if (rhs !== null) eval(G, rhs, guard);
- return switch (expr.op) {
- case TIMES: CompileTimeValue.multiply(leftValue, rightValue, lhs, rhs)
- case DIV: CompileTimeValue.divide(leftValue, rightValue, lhs, rhs)
- case MOD: CompileTimeValue.remainder(leftValue, rightValue, lhs, rhs)
- default: CompileTimeValue.error("invalid operator: " + expr.op, expr)
- };
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, BinaryLogicalExpression expr, RecursionGuard guard) {
- val lhs = expr.lhs;
- val rhs = expr.rhs;
- val leftValue = if (lhs !== null) eval(G, lhs, guard);
- val rightValue = if (rhs !== null) eval(G, rhs, guard);
- return switch (expr.op) {
- case AND: CompileTimeValue.and(leftValue, rightValue, lhs, rhs)
- case OR: CompileTimeValue.or(leftValue, rightValue, lhs, rhs)
- default: CompileTimeValue.error("invalid operator: " + expr.op, expr)
- };
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, ConditionalExpression expr, RecursionGuard guard) {
- val condition = expr.expression;
- val trueExpr = expr.trueExpression;
- val falseExpr = expr.falseExpression;
- val conditionValue = if (condition !== null) eval(G, condition, guard);
- val trueValue = if (trueExpr !== null) eval(G, trueExpr, guard);
- val falseValue = if (falseExpr !== null) eval(G, falseExpr, guard);
- val error = CompileTimeValue.combineErrors(
- CompileTimeValue.requireValueType(conditionValue, ValueBoolean, "condition must be a boolean",
- expr.expression), trueValue, falseValue);
- if (error !== null) {
- return error;
- }
- return if ((conditionValue as ValueBoolean).getValue()) trueValue else falseValue;
- }
-
- def private dispatch CompileTimeValue eval(RuleEnvironment G, IdentifierRef expr, RecursionGuard guard) {
- if (N4JSLanguageUtils.isUndefinedLiteral(G, expr)) {
- return CompileTimeValue.UNDEFINED;
- }
- val id = expr.id; // <-- triggers scoping; this is unproblematic, because the scoping of IdentifierRefs does not
- // require type information and will thus not interfere with our goal of handling compile-time expressions and
- // computed property names as an up-front preparatory step before main AST traversal.
- if (id !== null && !id.eIsProxy) {
- return obtainValueIfConstFieldOrVariable(G, id, expr, guard);
- }
- return CompileTimeValue.error();
- }
-
- /**
- * Handles compile-time evaluation of property access expressions.
- *
- * IMPORTANT IMPLEMENTATION NOTES:
- *
- * - We must not make use of type information during compile-time evaluation (see {@link CompileTimeEvaluator}
- * for details why this rule exists).
- *
- Since scoping of property access requires type information, we cannot use this form of scoping.
- *
- Since this scoping would be triggered when invoking {@code #getProperty()} on the given property access
- * expression, we cannot make use of that property in this method.
- *
- APPROACH: avoid using (ordinary) scoping but instead implement custom member lookup for the very limited cases
- * supported by compile-time expressions.
- *
- * YES, this approach introduces an unfortunate duplication of logic, but greatly simplifies other parts of the
- * system, i.e. (ordinary) scoping, AST traversal, type system.
- */
- def private dispatch CompileTimeValue eval(RuleEnvironment G, ParameterizedPropertyAccessExpression expr, RecursionGuard guard) {
- val targetExpr = expr.target;
- val propName = expr.propertyAsText; // IMPORTANT: don't invoke expr.getProperty()!!
- val targetElem = if (targetExpr instanceof IdentifierRef) {
- if (targetExpr.eIsProxy) {
- return CompileTimeValue.error("Expression is a proxy '"+ propName +"'", expr);
- }
- targetExpr.id;
- }
- val sym = G.symbolObjectType;
- if (targetElem === sym) {
- // A) Is 'expr' an access to a built-in symbol, e.g. Symbol.iterator?
- val memberInSym = N4JSLanguageUtils.getAccessedBuiltInSymbol(G, expr, false); // IMPORTANT: pass in 'false', to disallow proxy resolution (which would trigger scoping, type inference, etc.)
- if (memberInSym !== null) {
- // yes, it is!
- return CompileTimeValue.of(memberInSym);
- } else {
- return CompileTimeValue.error("Unknown Symbol property '"+ propName +"'", expr);
- }
- } else if (targetElem instanceof TEnum) {
- // B) Is 'expr' an access to the literal of a @NumberBased or @StringBased enum?
- val enumKind = N4JSLanguageUtils.getEnumKind(targetElem);
- if (enumKind !== EnumKind.Normal) {
- val litInEnum = targetElem.literals.findFirst[name == propName]; // custom scoping logic!
- if (litInEnum !== null) {
- // yes, it is!
- return switch (enumKind) {
- case Normal:
- throw new IllegalStateException("cannot happen")
- case NumberBased:
- CompileTimeValue.of(litInEnum.valueNumber)
- case StringBased:
- CompileTimeValue.of(litInEnum.valueString)
- };
- }
- }
- } else if (targetElem instanceof TClassifier) {
- // C) Is 'expr' an access to a const field initialized by a compile-time expression?
- val member = targetElem.ownedMembers.filterNull.findFirst[name == propName && readable && static]; // custom scoping logic!
- // IMPORTANT: don't use "targetElem.findOwnedMember(memberName, false, true)" in previous line, because
- // #findOwnedMember() will create and cache a MemberByNameAndAccessMap, which will be incomplete if the
- // TClassifier contains members with unresolved computed property names!
- if (member instanceof TField && !(member as TField).hasComputedName) {
- // yes, it is!
- return obtainValueIfConstFieldOrVariable(G, member, expr, guard);
- } else {
- // we get here in two cases:
- //
- // 1) member not found
- // -> there are a number of possible reasons:
- // 1.a) member has a computed property name which was not yet evaluated,
- // 1.b) member is inherited, consumed, polyfilled, etc.,
- // 1.c) member does not exist at all.
- // At this point, i.e. before computed names are processed, we cannot distinguish between these
- // cases. So we create a dummy error here that will be improved later.
- //
- // 2) member was found but it has a (resolved) computed property name (since processing of computed
- // property names for the current resource has not started yet, this happens only if the member is
- // located in another file an its full type information, including its name, was found in the index)
- // -> for consistency with 1.a above, we have to raise an error also in this case
- return CompileTimeValue.error(new UnresolvedPropertyAccessError(expr));
- }
- }
- // D) all other cases:
- if (targetElem !== sym && !(targetElem instanceof TClassifier || targetElem instanceof TEnum)) {
- return CompileTimeValue.error(
- "target of a property access must be a direct reference to a class, interface, or enum", expr,
- N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET);
- }
- return CompileTimeValue.error("property access must point to const fields, literals of @NumberBased/@StringBased enums, or built-in symbols", expr);
- }
-
-
- // ---------------------------------------------------------------------------------------------------------------
-
-
- /**
- * Iff the given element is a const field or variable with a valid compile-time expression as initializer, then this
- * method returns its compile-time value; otherwise, an invalid compile-time value with an appropriate error message
- * is returned. Never returns null
.
- *
- * This method only handles infinite recursion; main logic in
- * {@link #obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment, IdentifiableElement, EObject, RecursionGuard)}.
- */
- def private CompileTimeValue obtainValueIfConstFieldOrVariable(RuleEnvironment G, IdentifiableElement targetElem,
- EObject astNodeForErrorMessage, RecursionGuard guard) {
-
- if (guard.tryNext(targetElem)) {
- try {
- return obtainValueIfConstFieldOrVariableUnguarded(G, targetElem, astNodeForErrorMessage, guard);
- } finally {
- guard.done(targetElem);
- }
- } else {
- return CompileTimeValue.error("cyclic definition of compile-time expression", astNodeForErrorMessage);
- }
- }
-
- def private CompileTimeValue obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment G,
- IdentifiableElement targetElem, EObject astNodeForErrorMessage, RecursionGuard guard) {
-
- val targetElemIsConst = switch (targetElem) {
- TConstableElement: targetElem.const
- N4FieldDeclaration: targetElem.const
- VariableDeclaration: targetElem.const
- };
- if (!targetElemIsConst) {
- return CompileTimeValue.error(
- keywordProvider.keyword(targetElem) + " " + targetElem.name + " is not const",
- astNodeForErrorMessage);
- }
-
- val valueOfTargetElem = obtainCompileTimeValueOfTargetElement(G, astNodeForErrorMessage.eResource, targetElem, guard);
- if (valueOfTargetElem !== null) {
- if (valueOfTargetElem instanceof ValueInvalid) {
- val baseMsg = keywordProvider.keyword(targetElem) + " " + targetElem.name +
- " is const but does not have a compile-time expression as initializer";
- val msg = combineErrorMessageWithNestedErrors(baseMsg, valueOfTargetElem.errors);
- val feature = if (astNodeForErrorMessage instanceof ParameterizedPropertyAccessExpression) {
- N4JSPackage.eINSTANCE.parameterizedPropertyAccessExpression_Property
- };
- return CompileTimeValue.error(msg, astNodeForErrorMessage, feature);
- }
- return valueOfTargetElem;
- }
- return CompileTimeValue.error(
- "only references to const variables with a compile-time expression as initializer are allowed",
- astNodeForErrorMessage);
- }
-
- def private CompileTimeValue obtainCompileTimeValueOfTargetElement(RuleEnvironment G, Resource currentResource,
- IdentifiableElement targetElem, RecursionGuard guard) {
-
- if (targetElem.eResource === currentResource || hasLoadedASTElement(targetElem)) {
- // 'targetElem' is in same resource OR is in a different resource that already has a fully-loaded AST
- // -> compute value from the initializer expression of 'targetElem'
- val astNodeOfTargetElem = if (targetElem instanceof SyntaxRelatedTElement) {
- targetElem.astElement // NOTE: this will never trigger demand-loading of an AST, because above we
- // ensured that we are still in 'currentResource' OR method #hasLoadedASTElement() has returned true
- } else {
- targetElem // here we simply assume that elem is already an AST node
- };
- val expressionOfTargetElem = switch (astNodeOfTargetElem) {
- N4FieldDeclaration: astNodeOfTargetElem.expression
- VariableDeclaration: astNodeOfTargetElem.expression
- };
- if (expressionOfTargetElem !== null) {
- return eval(G, expressionOfTargetElem, guard);
- }
- } else {
- // 'targetElem' is in another resource with an AST proxy
- // -> read value from TModule to avoid demand-loading of AST
- if (targetElem instanceof TConstableElement) {
- return CompileTimeValue.deserialize(targetElem.compileTimeValue);
- }
- }
- return null; // no value found
- }
-
- /**
- * Tells if given element has an AST element in an already loaded AST (i.e. it is safe to invoke method
- * {@code #getASTElement()} without triggering a demand-load of the AST).
- */
- def private static boolean hasLoadedASTElement(IdentifiableElement elem) {
- val astElemNonResolved = if (elem instanceof SyntaxRelatedTElement) {
- elem.eGet(TypesPackage.eINSTANCE.syntaxRelatedTElement_AstElement, false) as EObject
- };
- return astElemNonResolved !== null && !astElemNonResolved.eIsProxy;
- }
-
- def private static String combineErrorMessageWithNestedErrors(String mainMessage,
- CompileTimeEvaluationError... nestedErrors) {
-
- if (nestedErrors.length == 0) {
- return mainMessage;
- } else if (nestedErrors.length == 1) {
- return mainMessage + ": " + nestedErrors.get(0).messageWithLocation;
- } else {
- return mainMessage + ":\n- " + nestedErrors.map[messageWithLocation].join("\n- ");
- }
- }
-
-
- // ---------------------------------------------------------------------------------------------------------------
-
-
- /**
- * Special kind of {@link CompileTimeEvaluationError} used to denote a particular case in which the
- * {@link CompileTimeEvaluator} cannot come up with the correct error message and thus delegates finding a proper
- * message to the validation, i.e. to class {@link N4JSExpressionValidator}.
- */
- public static final class UnresolvedPropertyAccessError extends CompileTimeEvaluationError {
-
- public new(ParameterizedPropertyAccessExpression astNode) {
- super("*** UnresolvedPropertyAccessError ***", astNode,
- N4JSPackage.eINSTANCE.getParameterizedPropertyAccessExpression_Property());
- }
-
- def public ParameterizedPropertyAccessExpression getAstNodeCasted() {
- return astNode as ParameterizedPropertyAccessExpression;
- }
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.java
new file mode 100644
index 0000000000..b2dc12c9b1
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.java
@@ -0,0 +1,2379 @@
+/**
+ * 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.formatting2;
+
+import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR;
+import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_MAX_CONSECUTIVE_NEWLINES;
+import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_PARENTHESIS;
+import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE;
+import static org.eclipse.n4js.formatting2.N4JSGenericFormatter.PRIO_3;
+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.head;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.last;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.tail;
+
+import java.util.function.Function;
+
+import org.apache.log4j.Logger;
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.formatting2.N4JSGenericFormatter.IndentHandlingTextReplaceMerger;
+import org.eclipse.n4js.n4JS.AbstractAnnotationList;
+import org.eclipse.n4js.n4JS.AbstractCaseClause;
+import org.eclipse.n4js.n4JS.AdditiveExpression;
+import org.eclipse.n4js.n4JS.AnnotableExpression;
+import org.eclipse.n4js.n4JS.AnnotableN4MemberDeclaration;
+import org.eclipse.n4js.n4JS.AnnotablePropertyAssignment;
+import org.eclipse.n4js.n4JS.AnnotableScriptElement;
+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.ArrowFunction;
+import org.eclipse.n4js.n4JS.AssignmentExpression;
+import org.eclipse.n4js.n4JS.AwaitExpression;
+import org.eclipse.n4js.n4JS.BinaryBitwiseExpression;
+import org.eclipse.n4js.n4JS.BinaryLogicalExpression;
+import org.eclipse.n4js.n4JS.BindingPattern;
+import org.eclipse.n4js.n4JS.Block;
+import org.eclipse.n4js.n4JS.BooleanLiteral;
+import org.eclipse.n4js.n4JS.CastExpression;
+import org.eclipse.n4js.n4JS.CatchBlock;
+import org.eclipse.n4js.n4JS.CommaExpression;
+import org.eclipse.n4js.n4JS.ConditionalExpression;
+import org.eclipse.n4js.n4JS.EqualityExpression;
+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.FieldAccessor;
+import org.eclipse.n4js.n4JS.FinallyBlock;
+import org.eclipse.n4js.n4JS.ForStatement;
+import org.eclipse.n4js.n4JS.FormalParameter;
+import org.eclipse.n4js.n4JS.FunctionDeclaration;
+import org.eclipse.n4js.n4JS.FunctionDefinition;
+import org.eclipse.n4js.n4JS.FunctionExpression;
+import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor;
+import org.eclipse.n4js.n4JS.GenericDeclaration;
+import org.eclipse.n4js.n4JS.GetterDeclaration;
+import org.eclipse.n4js.n4JS.IdentifierRef;
+import org.eclipse.n4js.n4JS.IfStatement;
+import org.eclipse.n4js.n4JS.ImportDeclaration;
+import org.eclipse.n4js.n4JS.IndexedAccessExpression;
+import org.eclipse.n4js.n4JS.IntLiteral;
+import org.eclipse.n4js.n4JS.JSXElement;
+import org.eclipse.n4js.n4JS.MultiplicativeExpression;
+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.N4InterfaceDeclaration;
+import org.eclipse.n4js.n4JS.N4JSPackage;
+import org.eclipse.n4js.n4JS.N4MemberDeclaration;
+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.ObjectLiteral;
+import org.eclipse.n4js.n4JS.ParameterizedCallExpression;
+import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression;
+import org.eclipse.n4js.n4JS.ParenExpression;
+import org.eclipse.n4js.n4JS.PostfixExpression;
+import org.eclipse.n4js.n4JS.PromisifyExpression;
+import org.eclipse.n4js.n4JS.PropertyAssignment;
+import org.eclipse.n4js.n4JS.RegularExpressionLiteral;
+import org.eclipse.n4js.n4JS.RelationalExpression;
+import org.eclipse.n4js.n4JS.ReturnStatement;
+import org.eclipse.n4js.n4JS.Script;
+import org.eclipse.n4js.n4JS.ScriptElement;
+import org.eclipse.n4js.n4JS.ShiftExpression;
+import org.eclipse.n4js.n4JS.Statement;
+import org.eclipse.n4js.n4JS.StringLiteral;
+import org.eclipse.n4js.n4JS.SuperLiteral;
+import org.eclipse.n4js.n4JS.SwitchStatement;
+import org.eclipse.n4js.n4JS.TaggedTemplateString;
+import org.eclipse.n4js.n4JS.TemplateLiteral;
+import org.eclipse.n4js.n4JS.TemplateSegment;
+import org.eclipse.n4js.n4JS.ThisLiteral;
+import org.eclipse.n4js.n4JS.ThrowStatement;
+import org.eclipse.n4js.n4JS.TypeReferenceNode;
+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.YieldExpression;
+import org.eclipse.n4js.services.N4JSGrammarAccess;
+import org.eclipse.n4js.ts.typeRefs.IntersectionTypeExpression;
+import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef;
+import org.eclipse.n4js.ts.typeRefs.StaticBaseTypeRef;
+import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef;
+import org.eclipse.n4js.ts.typeRefs.ThisTypeRef;
+import org.eclipse.n4js.ts.typeRefs.ThisTypeRefStructural;
+import org.eclipse.n4js.ts.typeRefs.TypeArgument;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage;
+import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression;
+import org.eclipse.n4js.ts.types.TField;
+import org.eclipse.n4js.ts.types.TGetter;
+import org.eclipse.n4js.ts.types.TStructMember;
+import org.eclipse.n4js.ts.types.TypesPackage;
+import org.eclipse.xtext.AbstractRule;
+import org.eclipse.xtext.Keyword;
+import org.eclipse.xtext.formatting2.IAutowrapFormatter;
+import org.eclipse.xtext.formatting2.IFormattableDocument;
+import org.eclipse.xtext.formatting2.IHiddenRegionFormatter;
+import org.eclipse.xtext.formatting2.IHiddenRegionFormatting;
+import org.eclipse.xtext.formatting2.ITextReplacer;
+import org.eclipse.xtext.formatting2.internal.SinglelineCodeCommentReplacer;
+import org.eclipse.xtext.formatting2.internal.SinglelineDocCommentReplacer;
+import org.eclipse.xtext.formatting2.regionaccess.IComment;
+import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion;
+import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ILineRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.xbase.lib.Pair;
+import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
+import org.eclipse.xtext.xtext.generator.parser.antlr.splitting.simpleExpressions.NumberLiteral;
+
+import com.google.inject.Inject;
+
+/***/
+@SuppressWarnings("restriction")
+public class N4JSFormatter extends TypeExpressionsFormatter {
+ private final static Logger LOGGER = Logger.getLogger(TypeExpressionsFormatter.class);
+
+ /** Debug switch */
+ private static boolean debug = false;
+
+ @Inject
+ N4JSGrammarAccess grammarAccess;
+
+ /**
+ * PRIO_4 = -7 - still very low. Standard priorities in the formatter are:
+ *
+ * - lowPriority = -1; == PRIO_10
+ *
- normal priority = 0; == PRIO_11
+ *
- high priority = +1; == PRIO_12
+ *
+ */
+ static int PRIO_4 = PRIO_3 + 1;
+ static int PRIO_13 = IHiddenRegionFormatter.HIGH_PRIORITY + 1;
+
+ @Override
+ public void format(Object clazz, IFormattableDocument document) {
+ if (clazz instanceof N4ClassDeclaration) {
+ _format((N4ClassDeclaration) clazz, document);
+ return;
+ } else if (clazz instanceof N4InterfaceDeclaration) {
+ _format((N4InterfaceDeclaration) clazz, document);
+ return;
+ } else if (clazz instanceof ArrowFunction) {
+ _format((ArrowFunction) clazz, document);
+ return;
+ } else if (clazz instanceof N4EnumDeclaration) {
+ _format((N4EnumDeclaration) clazz, document);
+ return;
+ } else if (clazz instanceof TemplateSegment) {
+ _format((TemplateSegment) clazz, document);
+ return;
+ } else if (clazz instanceof IntersectionTypeExpression) {
+ _format((IntersectionTypeExpression) clazz, document);
+ return;
+ } else if (clazz instanceof ParameterizedTypeRef) {
+ _format((ParameterizedTypeRef) clazz, document);
+ return;
+ } else if (clazz instanceof ThisTypeRef) {
+ _format((ThisTypeRef) clazz, document);
+ return;
+ } else if (clazz instanceof UnionTypeExpression) {
+ _format((UnionTypeExpression) clazz, document);
+ return;
+ } else if (clazz instanceof TStructMember) {
+ _format((TStructMember) clazz, document);
+ return;
+ } else if (clazz instanceof ArrayLiteral) {
+ _format((ArrayLiteral) clazz, document);
+ return;
+ } else if (clazz instanceof ForStatement) {
+ _format((ForStatement) clazz, document);
+ return;
+ } else if (clazz instanceof FunctionExpression) {
+ _format((FunctionExpression) clazz, document);
+ return;
+ } else if (clazz instanceof IndexedAccessExpression) {
+ _format((IndexedAccessExpression) clazz, document);
+ return;
+ } else if (clazz instanceof N4FieldDeclaration) {
+ _format((N4FieldDeclaration) clazz, document);
+ return;
+ } else if (clazz instanceof ObjectLiteral) {
+ _format((ObjectLiteral) clazz, document);
+ return;
+ } else if (clazz instanceof ParameterizedCallExpression) {
+ _format((ParameterizedCallExpression) clazz, document);
+ return;
+ } else if (clazz instanceof ParameterizedPropertyAccessExpression) {
+ _format((ParameterizedPropertyAccessExpression) clazz, document);
+ return;
+ } else if (clazz instanceof ParenExpression) {
+ _format((ParenExpression) clazz, document);
+ return;
+ } else if (clazz instanceof TaggedTemplateString) {
+ _format((TaggedTemplateString) clazz, document);
+ return;
+ } else if (clazz instanceof TemplateLiteral) {
+ _format((TemplateLiteral) clazz, document);
+ return;
+ } else if (clazz instanceof VariableDeclaration) {
+ _format((VariableDeclaration) clazz, document);
+ return;
+ } else if (clazz instanceof VariableStatement) {
+ _format((VariableStatement) clazz, document);
+ return;
+ } else if (clazz instanceof AdditiveExpression) {
+ _format((AdditiveExpression) clazz, document);
+ return;
+ } else if (clazz instanceof AssignmentExpression) {
+ _format((AssignmentExpression) clazz, document);
+ return;
+ } else if (clazz instanceof AwaitExpression) {
+ _format((AwaitExpression) clazz, document);
+ return;
+ } else if (clazz instanceof BinaryBitwiseExpression) {
+ _format((BinaryBitwiseExpression) clazz, document);
+ return;
+ } else if (clazz instanceof BinaryLogicalExpression) {
+ _format((BinaryLogicalExpression) clazz, document);
+ return;
+ } else if (clazz instanceof Block) {
+ _format((Block) clazz, document);
+ return;
+ } else if (clazz instanceof CastExpression) {
+ _format((CastExpression) clazz, document);
+ return;
+ } else if (clazz instanceof CommaExpression) {
+ _format((CommaExpression) clazz, document);
+ return;
+ } else if (clazz instanceof ConditionalExpression) {
+ _format((ConditionalExpression) clazz, document);
+ return;
+ } else if (clazz instanceof EqualityExpression) {
+ _format((EqualityExpression) clazz, document);
+ return;
+ } else if (clazz instanceof ExportDeclaration) {
+ _format((ExportDeclaration) clazz, document);
+ return;
+ } else if (clazz instanceof ExpressionStatement) {
+ _format((ExpressionStatement) clazz, document);
+ return;
+ } else if (clazz instanceof IfStatement) {
+ _format((IfStatement) clazz, document);
+ return;
+ } else if (clazz instanceof ImportDeclaration) {
+ _format((ImportDeclaration) clazz, document);
+ return;
+ } else if (clazz instanceof MultiplicativeExpression) {
+ _format((MultiplicativeExpression) clazz, document);
+ return;
+ } else if (clazz instanceof NamespaceImportSpecifier) {
+ _format((NamespaceImportSpecifier) clazz, document);
+ return;
+ } else if (clazz instanceof NewExpression) {
+ _format((NewExpression) clazz, document);
+ return;
+ } else if (clazz instanceof PostfixExpression) {
+ _format((PostfixExpression) clazz, document);
+ return;
+ } else if (clazz instanceof PromisifyExpression) {
+ _format((PromisifyExpression) clazz, document);
+ return;
+ } else if (clazz instanceof RelationalExpression) {
+ _format((RelationalExpression) clazz, document);
+ return;
+ } else if (clazz instanceof ReturnStatement) {
+ _format((ReturnStatement) clazz, document);
+ return;
+ } else if (clazz instanceof ShiftExpression) {
+ _format((ShiftExpression) clazz, document);
+ return;
+ } else if (clazz instanceof SwitchStatement) {
+ _format((SwitchStatement) clazz, document);
+ return;
+ } else if (clazz instanceof ThrowStatement) {
+ _format((ThrowStatement) clazz, document);
+ return;
+ } else if (clazz instanceof UnaryExpression) {
+ _format((UnaryExpression) clazz, document);
+ return;
+ } else if (clazz instanceof VariableBinding) {
+ _format((VariableBinding) clazz, document);
+ return;
+ } else if (clazz instanceof YieldExpression) {
+ _format((YieldExpression) clazz, document);
+ return;
+ } else if (clazz instanceof XtextResource) {
+ _format((XtextResource) clazz, document);
+ return;
+ } else if (clazz instanceof AbstractCaseClause) {
+ _format((AbstractCaseClause) clazz, document);
+ return;
+ } else if (clazz instanceof BindingPattern) {
+ _format((BindingPattern) clazz, document);
+ return;
+ } else if (clazz instanceof CatchBlock) {
+ _format((CatchBlock) clazz, document);
+ return;
+ } else if (clazz instanceof Expression) {
+ _format((Expression) clazz, document);
+ return;
+ } else if (clazz instanceof FinallyBlock) {
+ _format((FinallyBlock) clazz, document);
+ return;
+ } else if (clazz instanceof FunctionOrFieldAccessor) {
+ _format((FunctionOrFieldAccessor) clazz, document);
+ return;
+ } else if (clazz instanceof N4TypeVariable) {
+ _format((N4TypeVariable) clazz, document);
+ return;
+ } else if (clazz instanceof NamedImportSpecifier) {
+ _format((NamedImportSpecifier) clazz, document);
+ return;
+ } else if (clazz instanceof Script) {
+ _format((Script) clazz, document);
+ return;
+ } else if (clazz instanceof EObject) {
+ _format((EObject) clazz, document);
+ return;
+ } else if (clazz == null) {
+ _format((Void) null, document);
+ return;
+ } else {
+ _format(clazz, document);
+ return;
+ }
+ }
+
+ private void configureAnnotations(final Object semEObject, final IFormattableDocument document) {
+ if (semEObject instanceof AnnotablePropertyAssignment) {
+ _configureAnnotations((AnnotablePropertyAssignment) semEObject, document);
+ return;
+ } else if (semEObject instanceof AnnotableExpression) {
+ _configureAnnotations((AnnotableExpression) semEObject, document);
+ return;
+ } else if (semEObject instanceof AnnotableN4MemberDeclaration) {
+ _configureAnnotations((AnnotableN4MemberDeclaration) semEObject, document);
+ return;
+ } else if (semEObject instanceof AnnotableScriptElement) {
+ _configureAnnotations((AnnotableScriptElement) semEObject, document);
+ return;
+ } else if (semEObject instanceof AbstractAnnotationList) {
+ _configureAnnotations((AbstractAnnotationList) semEObject, document);
+ return;
+ } else if (semEObject == null) {
+ _configureAnnotations((Void) null, document);
+ return;
+ } else {
+ _configureAnnotations(semEObject, document);
+ return;
+ }
+ }
+
+ private Integer maxConsecutiveNewLines() {
+ // let's stick to 2
+ return getPreference(FORMAT_MAX_CONSECUTIVE_NEWLINES);
+ }
+
+ void _format(Script script, IFormattableDocument document) {
+ N4JSGenericFormatter generic = new N4JSGenericFormatter(grammarAccess, textRegionExtensions);
+ if (getPreference(FORMAT_PARENTHESIS)) {
+ // script.formatParenthesisBracketsAndBraces(document)
+ }
+
+ // TODO the following line requires more conflict handling with semicolons:
+ // script.interior[noIndentation;];
+
+ generic.formatSemicolons(script, document);
+ generic.formatColon(script, document);
+
+ formatScriptAnnotations(script, document);
+
+ for (ScriptElement element : script.getScriptElements()) {
+ // element.append[setNewLines(1, 1, maxConsecutiveNewLines);hrf.noSpace(); autowrap].prepend,
+ // hrf->hrf.noSpace();
+ document.append(element, hrf -> {
+ hrf.setNewLines(1, 1, maxConsecutiveNewLines());
+ hrf.autowrap();
+ });
+
+ document.format(element);
+ }
+
+ // format last import, overrides default newLines:
+ document.append(last(filter(script.getScriptElements(), ImportDeclaration.class)), hfr -> {
+ hfr.setNewLines(2, 2, 3);
+ hfr.highPriority();
+ });
+ }
+
+ /** put modifiers into a single line separated by one space, last modifier has one space to following element. */
+ void configureModifiers(EObject semObject, IFormattableDocument document) {
+ for (ISemanticRegion sr : textRegionExtensions.regionFor(semObject)
+ .ruleCallsTo(grammarAccess.getN4ModifierRule())) {
+
+ document.append(sr, hrf -> hrf.oneSpace());
+ }
+ }
+
+ void configureTypingStrategy(EObject semObject, IFormattableDocument document) {
+ for (ISemanticRegion sr : textRegionExtensions.regionFor(semObject)
+ .ruleCallsTo(grammarAccess.getTypingStrategyDefSiteOperatorRule(),
+ grammarAccess.getTypingStrategyUseSiteOperatorRule())) {
+
+ document.append(sr, hrf -> hrf.noSpace());
+ }
+ }
+
+ void formatTypeVariables(GenericDeclaration semObject, IFormattableDocument document) {
+ if (semObject.getTypeVars().isEmpty()) {
+ return;
+ }
+ // to "<":
+ ISemanticRegion sr = document.prepend(textRegionExtensions.regionFor(semObject).keyword("<"), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ document.append(sr, hrf -> hrf.noSpace());
+
+ document.prepend(textRegionExtensions.regionFor(semObject).keyword(">"), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+
+ for (N4TypeVariable typeVar : semObject.getTypeVars()) {
+ N4TypeVariable sr2 = document.append(typeVar, hrf -> hrf.noSpace());
+ document.append(textRegionExtensions.immediatelyFollowing(sr2).keyword(","), hrf -> hrf.oneSpace());
+ format(typeVar, document);
+ }
+ }
+
+ void _format(N4ClassDeclaration clazz, IFormattableDocument document) {
+
+ configureAnnotations(clazz, document);
+ insertSpaceInFrontOfCurlyBlockOpener(clazz, document);
+ indentExcludingAnnotations(clazz, document);
+
+ configureTypingStrategy(clazz, document);
+ configureModifiers(clazz, document);
+
+ formatTypeVariables(clazz, document);
+
+ // val semRegModifier = textRegionExtensions.regionFor(clazz).feature(
+ // N4JSPackage.Literals.MODIFIABLE_ELEMENT__DECLARED_MODIFIERS);
+ // if( semRegModifier != null ) { // only if exists.
+ // val beginModifierHR = semRegModifier.previousHiddenRegion;
+ // val endModifierHR = semRegModifier.nextHiddenRegion;
+ // // go over all semantic regions in the modifier location.
+ // var currHR = beginModifierHR.nextHiddenRegion;
+ // while( currHR != endModifierHR ){
+ // currHR.set, hrf->hrf.oneSpace();
+ // currHR = currHR.nextHiddenRegion;
+ // }
+ // endModifierHR.set, hrf->hrf.oneSpace();
+ // } // end modifier formatting TODO extract into method.
+
+ // TODO revise the following pattern of call-back implementations.
+ // Define lambda for callback & normal use:
+ Function twolinesBeforeFirstMember = (prio) -> {
+ return document.prepend(clazz.getOwnedMembersRaw().get(0), hrf -> {
+ hrf.setNewLines(2);
+ hrf.setPriority(prio);
+ });
+ };
+
+ // Defines CallBack for autoWrap:
+ IAutowrapFormatter callBackOnAutoWrap = new IAutowrapFormatter() { // callback for auto-wrapping with implements
+ boolean didReconfigure = false; // track to only execute once.
+
+ @SuppressWarnings("hiding")
+ @Override
+ public void format(ITextSegment region, IHiddenRegionFormatting wrapped, IFormattableDocument document) {
+ if (!didReconfigure) {
+ twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY); // reformat with higher
+ // priority
+ didReconfigure = true; // keep state.
+ }
+ }
+ };
+
+ // 2nd version of implementing the callback:
+ StateTrack state2 = new StateTrack();
+ IAutowrapFormatter callBackOnAutoWrap2 = (region, hrFormatting, document2) -> {
+ if (state2.shouldDoThenDone())
+ twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY);
+ };
+
+ suppressUnusedWarnings(callBackOnAutoWrap2);
+
+ // Allow for lineBreaks in front of keywords:
+ document.append(document.prepend(textRegionExtensions.regionFor(clazz).keyword("extends"), hrf -> {
+ hrf.setNewLines(0, 0, 1); // allow line break in front.
+ hrf.autowrap();
+ }), hrf -> {
+ hrf.oneSpace();
+ hrf.autowrap();
+ });
+
+ document.append(document.prepend(textRegionExtensions.regionFor(clazz).keyword("implements"), hrf -> {
+ hrf.setNewLines(0, 0, 1);
+ hrf.autowrap();
+ hrf.setPriority(IHiddenRegionFormatter.LOW_PRIORITY);
+ hrf.setOnAutowrap(callBackOnAutoWrap);
+ }), hrf -> {
+ hrf.oneSpace();
+ hrf.autowrap();
+ });
+
+ for (TypeReferenceNode trn : tail(clazz.getImplementedInterfaceRefs())) {
+ document.prepend(trn, hrf -> {
+ hrf.autowrap();
+ hrf.setPriority(IHiddenRegionFormatter.LOW_PRIORITY);
+ hrf.setOnAutowrap(callBackOnAutoWrap);
+ });
+ }
+
+ // special case if the header of the class spans multiple lines, then insert extra line break.
+
+ ISemanticRegion kwClass = textRegionExtensions.regionFor(clazz).keyword("class");
+ ISemanticRegion kwBrace = textRegionExtensions.regionFor(clazz).keyword("{"); // autowrap-listener ?
+ if (!kwClass.getLineRegions().get(0).contains(kwBrace)) {
+ twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.NORMAL_PRIORITY);
+ } else {
+ document.prepend(clazz.getOwnedMembersRaw().get(0), hrf -> {
+ hrf.setNewLines(1, 1, maxConsecutiveNewLines());
+ hrf.autowrap();
+ });
+ }
+
+ document.append(kwClass, hrf -> hrf.oneSpace());
+
+ for (N4MemberDeclaration member : clazz.getOwnedMembersRaw()) {
+ document.append(member, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+ document.format(member);
+ }
+
+ // Collapse empty block:
+ if (clazz.getOwnedMembersRaw().isEmpty()) {
+ // Empty body:
+ document.append(kwBrace, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+ }
+
+ void _format(N4InterfaceDeclaration interf, IFormattableDocument document) {
+ configureAnnotations(interf, document);
+ configureModifiers(interf, document);
+ insertSpaceInFrontOfCurlyBlockOpener(interf, document);
+ indentExcludingAnnotations(interf, document);// .interiorBUGFIX(, hrf->hrf.indent(),document);
+ // //interf.interior,
+ // hrf->hrf.indent();
+
+ document.prepend(interf.getOwnedMembersRaw().get(0), hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+ for (N4MemberDeclaration member : interf.getOwnedMembersRaw()) {
+ document.append(member, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+ document.format(member);
+ }
+ }
+
+ // void _format(N4MemberDeclaration member, IFormattableDocument document) {
+ // textRegionExtensions.regionFor(member).keyword("(").prepend[hrf.noSpace(); hrf.setNewLines(1);].append,
+ // hrf->hrf.noSpace()
+ //
+ // member.insertSpaceInfrontOfPropertyNames(document);
+ // for (c : member.eContents) {
+ // document.format(// c);
+ // }
+ // }
+
+ void _format(N4FieldDeclaration field, IFormattableDocument document) {
+ configureAnnotations(field, document);
+ configureModifiers(field, document);
+
+ indentExcludingAnnotations(field, document);
+
+ configureOptionality(field, document);
+ document.append(document.prepend(textRegionExtensions.regionFor(field).keyword("="), hrf -> hrf.oneSpace()),
+ hrf -> hrf.oneSpace());
+ document.format(field.getExpression());
+ document.format(field.getDeclaredTypeRefInAST());
+ }
+
+ // format(N4MethodDeclaration method, IFormattableDocument document) {
+ // configureAnnotations(method, document);
+ // method.insertSpaceInfrontOfPropertyNames(document);
+ //
+ // textRegionExtensions.regionFor(method).keyword("(").prepend[hrf.noSpace(); hrf.setNewLines(1);]
+ //
+ // method.textRegionExtensions.regionFor(body).keyword("{").prepend[hrf.oneSpace(); newLines = 0]
+ // for (child : method.eContents) {
+ // document.format(// child);
+ // }
+ // }
+ //
+ void _format(FunctionExpression funE, IFormattableDocument document) {
+ configureAnnotations(funE, document);
+ configureModifiers(funE, document);
+
+ if (funE.isArrowFunction()) {
+ throw new IllegalStateException("Arrow functions should be formated differently.");
+ }
+
+ configureFormalParameters(funE.getFpars(), document,
+ (ITextSegment $0, IHiddenRegionFormatting $1, IFormattableDocument $2) -> {
+ /* n.t.d. */});
+
+ Pair parenPair = textRegionExtensions.regionFor(funE).keywordPairs("(", ")")
+ .get(0);
+
+ document.append(parenPair.getKey(), hrf -> hrf.noSpace());
+ document.prepend(parenPair.getValue(), hrf -> hrf.noSpace());
+ document.format(funE.getBody());
+ }
+
+ void _format(FunctionOrFieldAccessor fDecl, IFormattableDocument document) {
+ configureAnnotations(fDecl, document);
+ configureModifiers(fDecl, document);
+
+ // State-keeper to avoid clashing reconfigurations if multiple auto-wraps get triggered.
+ final StateTrack state = new StateTrack(); // use state to only trigger one change, even if called multiple
+ // times.
+
+ // Callback to introduce an additional line in body-block.
+ IAutowrapFormatter cbInsertEmptyLineInBody = (
+ ITextSegment ts, IHiddenRegionFormatting hrf, IFormattableDocument fd) -> {
+
+ if (state.shouldDoThenDone() && fDecl.getBody() != null && fDecl.getBody().getStatements() != null
+ && !fDecl.getBody().getStatements().isEmpty()) {
+ document.prepend(fDecl.getBody().getStatements().get(0), hrf2 -> {
+ hrf2.setNewLines(2, 2, maxConsecutiveNewLines());
+ hrf2.highPriority();
+ });
+ }
+ };
+
+ // Formal parameters
+ if (fDecl instanceof FunctionDefinition) {
+ configureFormalParameters(((FunctionDefinition) fDecl).getFpars(), document, cbInsertEmptyLineInBody);
+ } else if (fDecl instanceof N4SetterDeclaration) {
+ /* no autowrap for setters: cbInsertEmptyLineInBody */
+ document.append(document.prepend(((N4SetterDeclaration) fDecl).getFpar(), hrf -> hrf.noSpace()),
+ hrf -> hrf.noSpace());
+ }
+
+ // Type Variables
+ if (fDecl instanceof FunctionDeclaration) {
+ formatTypeVariables((FunctionDeclaration) fDecl, document);
+ }
+
+ // special case for accessors: get / set keywords
+ if (fDecl instanceof FieldAccessor) {
+ configureGetSetKeyword((FieldAccessor) fDecl, document);
+ configureOptionality((FieldAccessor) fDecl, document);
+ }
+
+ Pair parenPair = textRegionExtensions.regionFor(fDecl).keywordPairs("(",
+ ")").get(0);
+
+ document.append(document.prepend(parenPair.getKey(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ }), hrf -> hrf.noSpace());
+
+ document.interior(parenPair, hrf -> hrf.indent());
+
+ if (isMultiLine(parenPair) && !(fDecl instanceof FieldAccessor)) {
+ // it is already a multiline, insert the newLine immediately.
+ // cbInsertEmptyLineInBody.apply(null,null,null); // TODO re-think, if all will be collapsed this assumption
+ // does not hold an
+ } else {
+ // single line parameter block
+ }
+ document.prepend(parenPair.getValue(), hrf -> hrf.noSpace());
+
+ for (EObject child : fDecl.eContents()) {
+ document.format(child);
+ }
+ }
+
+ /** to be used by FunctionDefintiions and SetterDeclarations */
+ void configureFormalParameters(EList list, IFormattableDocument document, IAutowrapFormatter x) {
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+ for (int idx = 0; idx < list.size(); idx++) {
+ FormalParameter it = list.get(idx);
+ if (idx != 0) {
+ document.prepend(it, hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0, 0, 1);
+ hrf.setOnAutowrap(x);
+ });
+ }
+ document.append(it, hrf -> hrf.noSpace());
+ configureAnnotationsInLine(it, document); // TODO maybe we need some in-line-annotation config here.
+ // textRegionExtensions.regionFor(it).ruleCallTo( bindingIdentifierAsFormalParameterRule ) //
+ // feature(N4JSPackage.Literals.FORMAL_PARAMETER__NAME)
+ // .prepend[hrf.oneSpace();hrf.setNewLines(1);].append[]
+ format(it.getDeclaredTypeRefInAST(), document);
+ if (it.isVariadic()) {
+ document.append(document.prepend(textRegionExtensions.regionFor(it).keyword("..."),
+ hrf -> hrf.setNewLines(0)/* hrf.oneSpace(); */), hrf -> {
+ hrf.setNewLines(0);
+ hrf.noSpace();
+ });
+ }
+ if (it.isHasInitializerAssignment()) {
+ document.append(document.prepend(textRegionExtensions.regionFor(it).keyword("="), hrf -> {
+ hrf.setNewLines(0);
+ hrf.noSpace();
+ }), hrf -> {
+ hrf.setNewLines(0);
+ hrf.noSpace();
+ });
+ if (it.getInitializer() != null) {
+ format(it.getInitializer(), document);
+ }
+ }
+ }
+ }
+
+ /** Check if key and value are in different lines. Defined for non-overlapping Regions, e.g. Keyword-pairs. */
+ static boolean isMultiLine(Pair pair) {
+ return !last(pair.getKey().getLineRegions()).contains(pair.getValue());
+ }
+
+ // format(FunctionOrFieldAccessor fofAccessor, IFormattableDocument document) {
+ // val begin = fofAccessor.body.semanticRegions.head
+ // val end = fofAccessor.body.semanticRegions.last
+ // if (begin?.lineRegions?.head?.contains(end?.endOffset)) {
+ // // same line
+ // } else {
+ // // body spans multiple lines
+ // begin.append[newLine;];
+ // end.prepend[newLine;];
+ // // fofAccessor.body.interior, hrf->hrf.indent(); // already by parenthesis?
+ // }
+ //
+ // document.format(// fofAccessor.body?);
+ //
+ // }
+
+ void _format(N4EnumDeclaration enumDecl, IFormattableDocument document) {
+ configureAnnotations(enumDecl, document);
+ configureModifiers(enumDecl, document);
+ insertSpaceInFrontOfCurlyBlockOpener(enumDecl, document);
+ indentExcludingAnnotations(enumDecl, document);// .interiorBUGFIX(, hrf->hrf.indent(),document);
+ // //enumDecl.interior, hrf->hrf.indent();
+ configureCommas(enumDecl, document);
+
+ Pair braces = textRegionExtensions.regionFor(enumDecl).keywordPairs("{", "}")
+ .get(0);
+
+ boolean multiLine = textRegionExtensions.isMultiline(enumDecl);
+
+ for (N4EnumLiteral it : enumDecl.getLiterals()) {
+ format(it, document);
+ if (multiLine) {
+ if (textRegionExtensions.regionForEObject(it).getPreviousHiddenRegion().containsComment()) {
+ // comment above
+ document.prepend(it, hrf -> hrf.setNewLines(2));
+ } else { // no comment above
+ document.prepend(it, hrf -> hrf.newLine());
+ }
+ }
+ }
+ if (multiLine) {
+ document.prepend(braces.getValue(), hrf -> hrf.newLine());
+ }
+ }
+
+ void _format(ParameterizedPropertyAccessExpression exp, IFormattableDocument document) {
+ ISemanticRegion dotKW = textRegionExtensions.regionFor(exp).keyword(".");
+ document.append(document.prepend(dotKW, hrf -> {
+ hrf.noSpace();
+ hrf.autowrap();
+ hrf.setNewLines(0, 0, 1);
+ }), hrf -> hrf.noSpace());
+ if (exp.eContainer() instanceof ExpressionStatement) {
+ // top-level PPA, indent one level.
+ interiorBUGFIX(exp, hrf -> hrf.indent(), document); // exp.interior, hrf->hrf.indent();
+ }
+ document.format(exp.getTarget());
+ }
+
+ void _format(ParameterizedCallExpression exp, IFormattableDocument document) {
+ // FIXME process typeArgs !!!
+ ISemanticRegion dotKW = textRegionExtensions.regionFor(exp).keyword(".");
+ document.append(document.prepend(dotKW, hrf -> {
+ hrf.noSpace();
+ hrf.autowrap();
+ }), hrf -> hrf.noSpace());
+ document.append(document.prepend(textRegionExtensions.regionFor(exp).keyword("("), hrf -> hrf.noSpace()),
+ hrf -> hrf.noSpace());
+ document.prepend(textRegionExtensions.regionFor(exp).keyword(")"), hrf -> hrf.noSpace());
+ configureCommas(exp, document);
+
+ for (Argument arg : tail(exp.getArguments())) {
+ document.prepend(arg, hrf -> {
+ hrf.oneSpace();
+ hrf.autowrap();
+ });
+ }
+
+ for (Argument arg : exp.getArguments()) {
+ format(arg, document);
+ }
+
+ if (exp.eContainer() instanceof ExpressionStatement) {
+ // top-level PPA, indent one level.
+ interiorBUGFIX(exp, hrf -> hrf.indent(), document); // exp.interior, hrf->hrf.indent();
+ }
+ document.format(exp.getTarget());
+ }
+
+ void _format(ImportDeclaration decl, IFormattableDocument document) {
+ // read configuration:
+ boolean extraSpace = getPreference(FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE);
+
+ document.append(document.prepend(textRegionExtensions.regionFor(decl).keyword("{"), hrf -> hrf.noSpace()),
+ hrf -> {
+ if (extraSpace)
+ hrf.oneSpace();
+ else
+ hrf.noSpace();
+ });
+ document.append(document.prepend(textRegionExtensions.regionFor(decl).keyword("}"), hrf -> {
+ if (extraSpace)
+ hrf.oneSpace();
+ else
+ hrf.noSpace();
+ }), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ document.surround(textRegionExtensions.regionFor(decl).keyword("from"), hrf -> hrf.oneSpace());
+ configureCommas(decl, document);
+ for (EObject eobj : decl.eContents()) {
+ format(eobj, document);
+ }
+ }
+
+ void _format(NamedImportSpecifier namedImp, IFormattableDocument document) {
+ document.append(document.prepend(textRegionExtensions.regionFor(namedImp).keyword("as"), hrf -> hrf.oneSpace()),
+ hrf -> hrf.oneSpace());
+ // "+"-KW after alias-name
+ document.append(document.prepend(
+ textRegionExtensions.regionFor(namedImp)
+ .feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC),
+ hrf -> hrf.noSpace()),
+ hrf -> hrf.oneSpace());
+ }
+
+ void _format(NamespaceImportSpecifier nsImp, IFormattableDocument document) {
+ document.append(textRegionExtensions.regionFor(nsImp).keyword("*"), hrf -> hrf.oneSpace());
+ document.append(textRegionExtensions.regionFor(nsImp).keyword("as"), hrf -> hrf.oneSpace());
+ // "+"-KW after alias-name
+ document.append(document.prepend(textRegionExtensions.regionFor(nsImp)
+ .feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC),
+ hrf -> hrf.noSpace()),
+ hrf -> hrf.oneSpace());
+ }
+
+ void _format(ExportDeclaration export, IFormattableDocument document) {
+ document.append(textRegionExtensions.regionFor(export).keyword("export"), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(1);
+ // Apply prioritization to catch cases of 'trapped' annotations e.g. "export @Final public class" which
+ // could also be reordered to "@Final export public class.."
+ hrf.setPriority(PRIO_13); // Priority higher then highPriority used in AnnotationList.
+ });
+
+ for (EObject eo : export.eContents()) {
+ format(eo, document);
+ }
+
+ // Fix Trapped annotations:
+ ExportableElement exported = export.getExportedElement();
+ if (exported instanceof AnnotableScriptElement) {
+ AnnotationList annoList = ((AnnotableScriptElement) exported).getAnnotationList();
+ if (annoList != null && !annoList.getAnnotations().isEmpty()) {
+ document.append(last(annoList.getAnnotations()), hrf -> {
+ hrf.setNewLines(0);
+ hrf.oneSpace();
+ hrf.setPriority(PRIO_13);
+ });
+ }
+ }
+ }
+
+ void _format(IfStatement stmt, IFormattableDocument document) {
+ Pair parenPair = textRegionExtensions.regionFor(stmt).keywordPairs("(", ")")
+ .get(0);
+ document.interior(parenPair, hrf -> {
+ hrf.noSpace();
+ hrf.indent();
+ });
+ document.prepend(parenPair.getKey(), hrf -> hrf.oneSpace());
+ document.append(parenPair.getValue(), hrf -> hrf.oneSpace());
+
+ document.append(document.prepend(textRegionExtensions.regionFor(stmt).keyword("else"), hrf -> {
+ hrf.autowrap();
+ hrf.oneSpace();
+ }), hrf -> hrf.oneSpace());
+
+ document.prepend(stmt.getElseStmt(), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+
+ document.format(stmt.getExpression());
+ document.format(stmt.getIfStmt());
+ document.format(stmt.getElseStmt());
+
+ }
+
+ void _format(SwitchStatement swStmt, IFormattableDocument document) {
+ insertSpaceInFrontOfCurlyBlockOpener(swStmt, document);
+ interiorBUGFIX(swStmt, hrf -> hrf.indent(), document); // swStmt.interior, hrf->hrf.indent();
+ document.format(swStmt.getExpression());
+ document.prepend(swStmt.getCases().get(0), hrf -> hrf.newLine());
+
+ for (EObject eo : swStmt.getCases()) {
+ format(eo, document);
+ }
+ }
+
+ /** Formats DefaultCaseClause + CaseClause */
+ void _format(AbstractCaseClause caseClause, IFormattableDocument document) {
+ interiorBUGFIX(caseClause, hrf -> hrf.indent(), document); // caseClause.interior, hrf->hrf.indent();
+
+ EList stmts = caseClause.getStatements();
+
+ if (stmts.size() == 1) {
+ if (stmts.get(0) instanceof Block) {
+ document.prepend(stmts.get(0), hrf -> hrf.setNewLines(0, 0, 0));
+ } else {
+ document.prepend(stmts.get(0), hrf -> hrf.setNewLines(0, 1, 1));
+ }
+ } else {
+ document.prepend(stmts.get(0), hrf -> hrf.setNewLines(1, 1, 1));
+ }
+
+ // textRegionExtensions.regionFor(caseClause).keyword(":").prepend, hrf->hrf.oneSpace(); // In case one space
+ // before the colon is desired
+ for (EObject eo : stmts) {
+ format(eo, document);
+ }
+ for (EObject eo : stmts) {
+ document.append(eo, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+ }
+
+ document.append(caseClause, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+ }
+
+ void _format(CastExpression expr, IFormattableDocument document) {
+ document.append(document.prepend(textRegionExtensions.regionFor(expr).keyword("as"), hrf -> {
+ hrf.setNewLines(0);
+ hrf.oneSpace();
+ }), hrf -> {
+ hrf.setNewLines(0);
+ hrf.oneSpace();
+ });
+ document.format(expr.getExpression());
+ document.format(expr.getTargetTypeRefNode());
+ }
+
+ void _format(Block block, IFormattableDocument document) {
+ if (debug) {
+ LOGGER.debug("Formatting block " + containmentStructure(block));
+ }
+
+ // Beware there are blocks in the grammar, that are not surrounded by curly braces. (e.g. FunctionExpression)
+
+ // Block not nested in other blocks usually are bodies. We want them separated by a space:
+ if (!(block.eContainer() instanceof Block || block.eContainer() instanceof Script)) {
+ // TODO maybe invert the control here, since the block is formatting the outside.
+ document.prepend(textRegionExtensions.regionFor(block).keyword("{"), hrf -> hrf.oneSpace());
+ }
+
+ interiorBUGFIX(block, hrf -> hrf.indent(), document); // block.interior, hrf->hrf.indent();
+
+ document.prepend(block.getStatements().get(0), hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+
+ for (Statement s : block.getStatements()) {
+ document.append(s, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+ }
+
+ for (Statement s : block.getStatements()) {
+ document.format(s);
+ }
+
+ // Format empty curly blocks, necessary for comments inside:
+ Pair braces = textRegionExtensions.regionFor(block).keywordPairs("{", "}")
+ .get(0);
+ if (braces != null && braces.getKey().getNextSemanticRegion() == braces.getValue()) {
+ // empty block:
+ if (braces.getKey().getNextHiddenRegion().containsComment()) {
+ document.append(braces.getKey(), hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines()));
+ } else {
+ document.append(braces.getKey(), hrf -> {
+ hrf.setNewLines(1);
+ hrf.noSpace();
+ });
+ }
+ }
+ }
+
+ void _format(ReturnStatement ret, IFormattableDocument document) {
+ interiorBUGFIX(ret, hrf -> hrf.indent(), document); // ret.interior[indent;]
+ document.prepend(ret.getExpression(), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ document.format(ret.getExpression());
+ }
+
+ void _format(AdditiveExpression add, IFormattableDocument document) {
+ document.append(document.surround(
+ textRegionExtensions.regionFor(add).feature(N4JSPackage.Literals.ADDITIVE_EXPRESSION__OP),
+ hrf -> hrf.oneSpace()), hrf -> hrf.autowrap());
+ document.format(add.getLhs());
+ document.format(add.getRhs());
+ }
+
+ void _format(MultiplicativeExpression mul, IFormattableDocument document) {
+ document.append(document.surround(
+ textRegionExtensions.regionFor(mul).feature(N4JSPackage.Literals.MULTIPLICATIVE_EXPRESSION__OP),
+ hrf -> hrf.oneSpace()), hrf -> hrf.autowrap());
+ document.format(mul.getLhs());
+ document.format(mul.getRhs());
+ }
+
+ void _format(BinaryBitwiseExpression binbit, IFormattableDocument document) {
+ document.surround(textRegionExtensions.regionFor(binbit)
+ .feature(N4JSPackage.Literals.BINARY_BITWISE_EXPRESSION__OP), hrf -> hrf.oneSpace());
+ document.format(binbit.getLhs());
+ document.format(binbit.getRhs());
+ }
+
+ void _format(BinaryLogicalExpression binLog, IFormattableDocument document) {
+ ISemanticRegion opReg = textRegionExtensions.regionFor(binLog)
+ .feature(N4JSPackage.Literals.BINARY_LOGICAL_EXPRESSION__OP);
+ document.surround(opReg, hrf -> hrf.oneSpace());
+ document.format(binLog.getLhs());
+ document.format(binLog.getRhs());
+ // auto-wrap:
+ boolean autoWrapInFront = getPreference(FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR);
+ if (autoWrapInFront) {
+ document.prepend(opReg, hrf -> {
+ hrf.autowrap();
+ hrf.lowPriority();
+ hrf.setNewLines(0, 0, 1);
+ });
+ } else {
+ document.append(opReg, hrf -> {
+ hrf.autowrap();
+ hrf.lowPriority();
+ hrf.setNewLines(0, 0, 1);
+ });
+ }
+ }
+
+ void _format(EqualityExpression eqExpr, IFormattableDocument document) {
+ document.append(document.surround(
+ textRegionExtensions.regionFor(eqExpr).feature(N4JSPackage.Literals.EQUALITY_EXPRESSION__OP),
+ hrf -> hrf.oneSpace()), hrf -> hrf.autowrap());
+ document.format(eqExpr.getLhs());
+ document.format(eqExpr.getRhs());
+ }
+
+ void _format(RelationalExpression relExpr, IFormattableDocument document) {
+ document.append(document.surround(
+ textRegionExtensions.regionFor(relExpr).feature(N4JSPackage.Literals.RELATIONAL_EXPRESSION__OP),
+ hrf -> hrf.oneSpace()), hrf -> hrf.autowrap());
+ document.format(relExpr.getLhs());
+ document.format(relExpr.getRhs());
+ }
+
+ void _format(ShiftExpression shiftExpr, IFormattableDocument document) {
+ document.append(document.surround(
+ textRegionExtensions.regionFor(shiftExpr).feature(N4JSPackage.Literals.SHIFT_EXPRESSION__OP),
+ hrf -> hrf.oneSpace()), hrf -> hrf.autowrap());
+ document.format(shiftExpr.getLhs());
+ document.format(shiftExpr.getRhs());
+ }
+
+ void _format(CommaExpression comma, IFormattableDocument document) {
+ configureCommas(comma, document);
+ for (EObject eo : comma.eContents()) {
+ document.format(eo);
+ }
+ }
+
+ void _format(ConditionalExpression cond, IFormattableDocument document) {
+ document.append(document.surround(textRegionExtensions.regionFor(cond).keyword("?"), hrf -> hrf.oneSpace()),
+ hrf -> {
+ hrf.autowrap();
+ hrf.lowPriority();
+ hrf.setNewLines(0, 0, 1);
+ });
+ document.append(document.surround(textRegionExtensions.regionFor(cond).keyword(":"), hrf -> hrf.oneSpace()),
+ hrf -> {
+ hrf.autowrap();
+ hrf.lowPriority();
+ hrf.setNewLines(0, 0, 1);
+ });
+ document.format(cond.getExpression());
+ document.format(cond.getTrueExpression());
+ document.format(cond.getFalseExpression());
+ }
+
+ void _format(AwaitExpression await, IFormattableDocument document) {
+ document.append(document.prepend(textRegionExtensions.regionFor(await).keyword("await"), hrf -> hrf.oneSpace()),
+ hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(1);
+ });
+ document.format(await.getExpression());
+ }
+
+ void _format(PromisifyExpression promify, IFormattableDocument document) {
+ noSpaceAfterAT(promify, document);
+ document.append(textRegionExtensions.regionFor(promify).keyword("Promisify"), hrf -> hrf.oneSpace());
+ document.format(promify.getExpression());
+ }
+
+ void _format(IndexedAccessExpression idxAcc, IFormattableDocument document) {
+ IEObjectRegion indexRegion = textRegionExtensions.regionForEObject(idxAcc.getIndex());
+ document.append(document.prepend(indexRegion.getPreviousSemanticRegion(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ }), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ });
+ document.prepend(indexRegion.getNextSemanticRegion(), hrf -> hrf.noSpace());
+
+ document.format(idxAcc.getIndex());
+ document.format(idxAcc.getTarget());
+ }
+
+ void _format(NewExpression newExp, IFormattableDocument document) {
+ document.append(document.prepend(textRegionExtensions.regionFor(newExp).keyword("new"), hrf -> hrf.oneSpace()),
+ hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(1);
+ });
+ document.format(newExp.getCallee());
+ // Watch out, commas are used in Type-args and in argument list ! If necessary distinguish by offset.
+ ISemanticRegion commas = textRegionExtensions.regionFor(newExp).keyword(",");
+ document.append(document.prepend(commas, hrf -> hrf.noSpace()), hrf -> hrf.oneSpace());
+
+ // TODO maybe factor out TypeArgs formatting.
+ Pair typeArgsAngle = textRegionExtensions.regionFor(newExp)
+ .keywordPairs("<", ">").get(0);
+
+ if (typeArgsAngle != null) {
+ document.append(document.prepend(typeArgsAngle.getKey(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ }), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ });
+ document.prepend(typeArgsAngle.getValue(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ });
+ }
+ for (TypeReferenceNode trn : newExp.getTypeArgs()) {
+ document.format(trn);
+ }
+
+ if (newExp.isWithArgs()) {
+ Pair argParen = textRegionExtensions.regionFor(newExp)
+ .keywordPairs("(", ")").get(0);
+ document.append(document.prepend(argParen.getKey(), hrf -> {
+ hrf.setNewLines(1);
+ hrf.noSpace();
+ }), hrf -> hrf.noSpace());
+ document.prepend(argParen.getValue(), hrf -> hrf.noSpace());
+ for (Argument arg : newExp.getArguments()) {
+ document.format(arg);
+ }
+ }
+ }
+
+ void _format(PostfixExpression postFix, IFormattableDocument document) {
+ // no line break allowed between Expression and operator !
+ document.append(document.prepend(
+ textRegionExtensions.regionFor(postFix).feature(N4JSPackage.Literals.POSTFIX_EXPRESSION__OP), hrf -> {
+ hrf.setNewLines(1);
+ hrf.noSpace();
+ }), hrf -> {
+ hrf.oneSpace();
+ hrf.lowPriority();
+ });
+ // giving low priority for situations of closing parenthesis: "(a++)"
+ document.format(postFix.getExpression());
+ }
+
+ void _format(TaggedTemplateString taggedTemplate, IFormattableDocument document) {
+ document.append(textRegionExtensions.regionFor(taggedTemplate)
+ .feature(N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET), hrf -> {
+ hrf.setNewLines(1);
+ hrf.oneSpace();
+ });
+ document.format(taggedTemplate.getTarget());
+ document.format(taggedTemplate.getTemplate());
+ }
+
+ void _format(UnaryExpression unaryExpr, IFormattableDocument document) {
+ // The operators 'void' 'delete' and 'typeof' must be separated from operand.
+ boolean requireSpace = (unaryExpr.getOp().ordinal() <= UnaryOperator.TYPEOF_VALUE);
+ document.append(textRegionExtensions.regionFor(unaryExpr).feature(N4JSPackage.Literals.UNARY_EXPRESSION__OP),
+ hrf -> {
+ if (requireSpace)
+ hrf.oneSpace();
+ else
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ });
+
+ document.format(unaryExpr.getExpression());
+ }
+
+ void _format(YieldExpression yieldExpr, IFormattableDocument document) {
+ // " yield " or " yield* "
+ document.append(document.prepend(textRegionExtensions.regionFor(yieldExpr).keyword("yield"),
+ hrf -> hrf.oneSpace()),
+ hrf -> {
+ if (yieldExpr.isMany()) {
+ hrf.noSpace();
+ } else {
+ hrf.oneSpace();
+ }
+ });
+
+ if (yieldExpr.isMany()) {
+ document.append(document.prepend(textRegionExtensions.regionFor(yieldExpr).keyword("*"),
+ hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ }),
+ hrf -> hrf.oneSpace());
+ }
+ document.format(yieldExpr.getExpression());
+ }
+
+ void _format(ParenExpression parenE, IFormattableDocument document) {
+ document.append(head(textRegionExtensions.semanticRegions(parenE)), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+ document.prepend(last(textRegionExtensions.semanticRegions(parenE)), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+ interiorBUGFIX(parenE, hrf -> hrf.indent(), document); // parenE.interior, hrf->hrf.indent();
+ document.format(parenE.getExpression());
+ }
+
+ void _format(ArrowFunction arrowF, IFormattableDocument document) {
+ configureCommas(arrowF, document);
+ document.surround(textRegionExtensions.regionFor(arrowF).ruleCallTo(grammarAccess.getArrowRule()),
+ hrf -> hrf.oneSpace());
+ document.interior(head(textRegionExtensions.regionFor(arrowF).keywordPairs("(", ")")), hrf -> hrf.noSpace());
+ // too lax: arrowF.fpars.configureFormalParameters(document,[/*NTD*/]);
+
+ if (arrowF.isHasBracesAroundBody()) {
+
+ // format body as block. NOTE: this block differs from other blocks, since the curly braces are defined in
+ // the ArrowExpression. special handling of indentation in inside the braces.
+ Pair bracesPair = textRegionExtensions.regionFor(arrowF).keywordPairs("{",
+ "}").get(0);
+ document.interior(bracesPair, hrf -> hrf.indent());
+
+ if (last(bracesPair.getKey().getLineRegions()).contains(bracesPair.getValue())
+ // one line '{ do; stuff; }'
+ || last(bracesPair.getKey().getLineRegions()).contains(bracesPair.getKey().getNextSemanticRegion())
+ // no line-break after braces e.g. '{ do; \n stuff; }'
+ ) {
+ // one line
+ if (arrowF.getBody() != null) {
+ for (int idx = 0; idx < arrowF.getBody().getStatements().size(); idx++) {
+ Statement it = arrowF.getBody().getStatements().get(idx);
+ document.format(it);
+ if (idx != 0) {
+ document.prepend(it, hrf -> {
+ hrf.oneSpace();
+ hrf.autowrap();
+ hrf.setNewLines(1);
+ });
+ }
+ }
+ }
+
+ // do not autowrap after "{" to keep wrap-semantic
+ document.append(bracesPair.getKey(), hrf -> hrf.oneSpace());
+ document.prepend(bracesPair.getValue(), hrf -> hrf.oneSpace());
+ }
+
+ else {
+ // multi-line
+ if (arrowF.getBody() != null && !arrowF.getBody().getStatements().isEmpty()) {
+ document.prepend(arrowF.getBody().getStatements().get(0), hrf -> hrf.setNewLines(1));
+ for (Statement it : arrowF.getBody().getStatements()) {
+ document.format(it);
+ document.append(it, hrf -> hrf.setNewLines(1));
+ }
+
+ } else {
+ // empty block, squash interior.
+ document.append(bracesPair.getKey(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ });
+ document.prepend(bracesPair.getValue(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ });
+ }
+ }
+
+ } else if (arrowF.getBody() != null) {
+ // no braces Around the implicit return statement.
+ document.format(arrowF.getBody().getStatements().get(0));
+ }
+ }
+
+ void _format(ArrayLiteral al, IFormattableDocument document) {
+ Pair bracketPair = textRegionExtensions.regionFor(al).keywordPairs("[", "]")
+ .get(0);
+ document.interior(bracketPair, hrf -> hrf.indent());
+ boolean sameLine = bracketPair.getKey().getLineRegions().get(0).contains(bracketPair.getValue());
+ // auto wrap in front of AL-Elements, to preserve comma at end.
+ if (!sameLine) {
+ document.append(last(al.getElements()), hrf -> hrf.autowrap());
+ for (int num = 0; num < al.getElements().size(); num++) {
+ ArrayElement it = al.getElements().get(num);
+ final int numF = num;
+ document.append(document.prepend(it, hrf -> {
+ hrf.autowrap();
+ hrf.setNewLines(0, 0, 1);
+ if (numF != 0) {
+ hrf.oneSpace();
+ }
+ }), hrf -> hrf.noSpace());
+ }
+ // format last bracket if in single.line.
+ if (!last(bracketPair.getValue().getPreviousSemanticRegion().getLineRegions())
+ .contains(bracketPair.getValue())) {
+ document.prepend(bracketPair.getValue(), hrf -> hrf.newLine());
+ }
+ } else {
+ for (int num = 0; num < al.getElements().size(); num++) {
+ ArrayElement it = al.getElements().get(num);
+ final int numF = num;
+ document.prepend(it, hrf -> {
+ hrf.autowrap();
+ if (numF != 0) {
+ hrf.oneSpace();
+ }
+ });
+ }
+ }
+ }
+
+ void _format(ObjectLiteral ol, IFormattableDocument document) {
+ configureCommas(ol, document);
+
+ Pair bracePair = textRegionExtensions.regionFor(ol).keywordPairs("{",
+ "}").get(0);
+ document.interior(bracePair, hrf -> hrf.indent());
+
+ // Decide on multiline or not.
+ // Rule: if opening brace is preceded by a line break, then go multiline.
+ boolean sameLine = bracePair.getKey().getLineRegions().get(0)
+ .contains(bracePair.getKey().getNextSemanticRegion().getLineRegions().get(0));
+ // OLD: val sameLine = bracePair.key.lineRegions.head.contains( bracePair.value );
+
+ if (!sameLine) {
+ // format WS in front of closing brace
+ document.prepend(bracePair.getValue(), hrf -> hrf.newLine());
+ for (PropertyAssignment it : ol.getPropertyAssignments()) {
+ document.prepend(it, hrf -> hrf.newLine());
+ }
+
+ if ((bracePair.getKey().getNextSemanticRegion()) == bracePair.getValue()) {
+ // empty multiline, trigger formatting:
+ document.append(bracePair.getKey(), hrf -> hrf.newLine());
+ }
+
+ } else { // in one line
+ document.append(bracePair.getKey(), hrf -> hrf.setNewLines(1));
+ for (int num = 0; num < ol.getPropertyAssignments().size(); num++) {
+ PropertyAssignment it = ol.getPropertyAssignments().get(num);
+
+ final int numF = num;
+ document.prepend(it, hrf -> {
+ hrf.setNewLines(1);
+ if (numF != 0) {
+ hrf.autowrap();
+ hrf.oneSpace();
+ } else {
+ hrf.noSpace();
+ }
+ });
+ }
+ // low priority to avoid conflict with dangling commas
+ document.prepend(bracePair.getValue(), hrf -> {
+ hrf.setNewLines(1);
+ hrf.noSpace();
+ hrf.lowPriority();
+ });
+ }
+
+ for (EObject eo : ol.eContents()) {
+ format(eo, document);
+ }
+ }
+
+ void _format(ForStatement fst, IFormattableDocument document) {
+
+ document.append(textRegionExtensions.regionFor(fst).keyword("for"), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+
+ Pair parenPair = textRegionExtensions.regionFor(fst).keywordPairs("(",
+ ")").get(0);
+ document.append(parenPair.getKey(), hrf -> {
+ hrf.noSpace();
+ hrf.autowrap();
+ hrf.setNewLines(1);
+ });
+ document.append(document.prepend(parenPair.getValue(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ }), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+
+ for (ISemanticRegion it : textRegionExtensions.regionFor(fst).keywords("in", "of")) {
+ document.surround(it, hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+ }
+
+ for (ISemanticRegion it : textRegionExtensions.regionFor(fst).keywords(";")) {
+ document.append(document.prepend(it, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ }), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+ }
+
+ for (EObject eo : fst.eContents()) {
+ format(eo, document);
+ }
+ }
+
+ void _format(TemplateLiteral tl, IFormattableDocument document) {
+ interiorBUGFIX(tl, hrf -> hrf.indent(), document); // tl.interior[indent;];
+ for (Expression it : tl.getSegments()) {
+ if (it instanceof TemplateSegment) {
+ noOp();
+ } else {
+ document.surround(it, hrf -> {
+ hrf.oneSpace();
+ hrf.autowrap();
+ });
+ }
+ document.format(it);
+ }
+ }
+
+ private void noOp() {
+ // empty
+ }
+
+ @SuppressWarnings("unused")
+ void _format(TemplateSegment tl, IFormattableDocument document) {
+ // just leave as is.
+ }
+
+ void _format(N4TypeVariable tv, IFormattableDocument document) {
+ // "out"
+ if (tv.isDeclaredCovariant()) {
+ document.append(textRegionExtensions.regionFor(tv)
+ .feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_COVARIANT), hrf -> hrf.oneSpace());
+ }
+ // "in"
+ if (tv.isDeclaredContravariant()) {
+ document.append(textRegionExtensions.regionFor(tv)
+ .feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_CONTRAVARIANT), hrf -> hrf.oneSpace());
+ }
+
+ TypeReferenceNode upperBoundNode = tv.getDeclaredUpperBoundNode();
+
+ if (upperBoundNode != null) {
+ // "extends"
+ document.surround(textRegionExtensions.regionFor(tv).keyword("extends"), hrf -> hrf.oneSpace());
+ document.surround(textRegionExtensions.immediatelyFollowing(upperBoundNode).keyword("&"),
+ hrf -> hrf.oneSpace());
+ format(upperBoundNode, document);
+ }
+
+ }
+
+ @SuppressWarnings("unused")
+ void _format(Expression exp, IFormattableDocument document) {
+ // Things not to format:
+ if (exp instanceof BooleanLiteral
+ || exp instanceof IdentifierRef
+ || exp instanceof IntLiteral
+ || exp instanceof NullLiteral
+ || exp instanceof NumberLiteral
+ || exp instanceof RegularExpressionLiteral
+ || exp instanceof StringLiteral
+ || exp instanceof ThisLiteral
+ || exp instanceof SuperLiteral
+ || exp instanceof JSXElement) {
+ return;
+ }
+
+ throw new UnsupportedOperationException(
+ "expression " + exp.getClass().getSimpleName() + " not yet implemented.");
+ }
+
+ /** simply formats all content */
+ void genericFormat(Expression exp, IFormattableDocument document) {
+ for (EObject eo : exp.eContents()) {
+ format(eo, document);
+ }
+ }
+
+ void _format(AssignmentExpression ass, IFormattableDocument document) {
+ document.append(ass.getLhs(), hrf -> hrf.oneSpace());
+ document.prepend(ass.getRhs(), hrf -> hrf.oneSpace());
+ document.format(ass.getLhs());
+ document.format(ass.getRhs());
+ }
+
+ void _format(ExpressionStatement eStmt, IFormattableDocument document) {
+ format(eStmt.getExpression(), document);
+ }
+
+ /** var,let,const */
+ void _format(VariableStatement vStmt, IFormattableDocument document) {
+
+ configureModifiers(vStmt, document);
+
+ // "let", "var" or "const"
+ document.append(textRegionExtensions.regionFor(vStmt)
+ .feature(N4JSPackage.Literals.VARIABLE_DECLARATION_CONTAINER__VAR_STMT_KEYWORD), hrf -> hrf.oneSpace());
+
+ configureCommas(vStmt, document);
+
+ interiorBUGFIX(vStmt, hrf -> hrf.indent(), document); // vStmt.interior, hrf->hrf.indent();
+ int lastIdx = vStmt.getVarDeclsOrBindings().size() - 1;
+
+ for (int i = 0; i < vStmt.getVarDeclsOrBindings().size(); i++) {
+ VariableDeclarationOrBinding e = vStmt.getVarDeclsOrBindings().get(i);
+ document.format(e);
+
+ if (i > 0) { // assignments start in separate lines.
+ if (e instanceof VariableDeclaration) {
+ if (e.getExpression() != null) {
+ document.prepend(e, hrf -> hrf.newLine());
+ } else {
+ document.prepend(e, hrf -> {
+ hrf.setNewLines(0, 1, 1);
+ hrf.lowPriority();
+ });
+ }
+ } else if (e instanceof VariableBinding) {
+ if (e.getExpression() != null) {
+ document.prepend(e, hrf -> hrf.newLine());
+ } else {
+ document.prepend(e, hrf -> {
+ hrf.setNewLines(0, 1, 1);
+ hrf.lowPriority();
+ });
+ }
+ }
+ }
+
+ if (i < lastIdx) {
+ // assignments start let following continue in separate lines.
+ if (e instanceof VariableDeclaration) {
+ if (e.getExpression() != null) {
+ document.append(textRegionExtensions.immediatelyFollowing(e).keyword(","),
+ hrf -> hrf.newLine());
+ } else {
+ document.prepend(e, hrf -> {
+ hrf.setNewLines(0, 1, 1);
+ hrf.lowPriority();
+ });
+ }
+
+ } else if (e instanceof VariableBinding) {
+ if (e.getExpression() != null) {
+ document.append(textRegionExtensions.immediatelyFollowing(e).keyword(","),
+ hrf -> hrf.newLine());
+ } else {
+ document.prepend(e, hrf -> {
+ hrf.setNewLines(0, 1, 1);
+ hrf.lowPriority();
+ });
+ }
+ }
+ }
+ }
+ }
+
+ void _format(VariableDeclaration vDecl, IFormattableDocument document) {
+ document.set(textRegionExtensions.previousHiddenRegion(vDecl), hrf -> hrf.oneSpace());
+ document.surround(textRegionExtensions.regionFor(vDecl).keyword("="), hrf -> hrf.oneSpace());
+ document.format(vDecl.getExpression());
+ document.format(vDecl.getDeclaredTypeRefInAST());
+ }
+
+ void _format(VariableBinding vBind, IFormattableDocument document) {
+ document.set(textRegionExtensions.previousHiddenRegion(vBind), hrf -> hrf.oneSpace());
+ document.surround(textRegionExtensions.regionFor(vBind).keyword("="), hrf -> hrf.oneSpace());
+ document.format(vBind.getPattern());
+ document.format(vBind.getExpression());
+ document.format(vBind.getPattern());
+ }
+
+ void _format(BindingPattern bp, IFormattableDocument document) {
+ // ObjectBindingPattern
+ // ArrayBindingPattern
+
+ // '{' or '['
+ document.append(head(textRegionExtensions.semanticRegions(bp)), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+ document.prepend(last(textRegionExtensions.semanticRegions(bp)), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ hrf.autowrap();
+ });
+ configureCommas(bp, document); // doesn't handle elision.
+
+ for (EObject eo : bp.eContents()) {
+ format(eo, document);
+ }
+ }
+
+ void _format(ThrowStatement thrStmt, IFormattableDocument document) {
+ // No autowrap, otherwise ASI
+ document.prepend(thrStmt.getExpression(), hrf -> {
+ hrf.setNewLines(0, 0, 0);
+ hrf.oneSpace();
+ });
+ document.format(thrStmt.getExpression());
+
+ }
+
+ void _format(CatchBlock ctch, IFormattableDocument document) {
+ document.prepend(ctch, hrf -> {
+ hrf.setNewLines(0, 0, 0);
+ hrf.oneSpace();
+ });
+ document.format(ctch.getCatchVariable());
+ document.format(ctch.getBlock());
+
+ }
+
+ void _format(FinallyBlock finlly, IFormattableDocument document) {
+ document.set(textRegionExtensions.previousHiddenRegion(finlly), hrf -> {
+ hrf.setNewLines(1);
+ hrf.oneSpace();
+ });
+ document.format(finlly.getBlock());
+ }
+
+ /**
+ * Insert one space in front of first '{' in the direct content of the element. semEObject is a semanticObject, e.g.
+ * N4EnumDecl, N4Classifier ...
+ */
+ private void insertSpaceInFrontOfCurlyBlockOpener(EObject semEObject, IFormattableDocument document) {
+ document.prepend(textRegionExtensions.regionFor(semEObject).keyword("{"), hrf -> hrf.oneSpace());
+ }
+
+ /** force: " @" and no newLine after '@' */
+ private void noSpaceAfterAT(EObject semEObject, IFormattableDocument document) {
+ document.prepend(document.append(textRegionExtensions.regionFor(semEObject).keyword("@"), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ }), hrf -> hrf.oneSpace());
+ }
+
+ /** 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, hrf -> hrf.noSpace());
+ document.append(sr, hrf -> {
+ hrf.oneSpace();
+ hrf.autowrap();
+ });
+ }
+ }
+
+ void indentExcludingAnnotations(EObject semObject, IFormattableDocument document) {
+ // Exclude Annotations from indentation field.interior, hrf->hrf.indent();
+ ISemanticRegion begin = findFirst(textRegionExtensions.semanticRegions(semObject),
+ sr -> !(sr instanceof Annotation));
+ ISemanticRegion end = last(textRegionExtensions.semanticRegions(semObject));
+
+ if (begin != end) { // guard to prevent wrong indentation
+ document.interior(begin, end, hrf -> hrf.indent());
+ }
+ }
+
+ private void _configureAnnotations(AnnotableN4MemberDeclaration semEObject, IFormattableDocument document) {
+ configureAnnotations(semEObject.getAnnotationList(), document);
+ }
+
+ @SuppressWarnings("unused")
+ private void _configureAnnotations(AnnotablePropertyAssignment semEObject, IFormattableDocument document) {
+ configureAnnotations(semEObject.getAnnotationList(), document);
+ }
+
+ private void _configureAnnotations(AnnotableScriptElement semEObject, IFormattableDocument document) {
+ configureAnnotations(semEObject.getAnnotationList(), document);
+ }
+
+ private void _configureAnnotations(AnnotableExpression semEObject, IFormattableDocument document) {
+ configureAnnotations(semEObject.getAnnotationList(), document);
+ }
+
+ private void _configureAnnotations(AbstractAnnotationList aList, IFormattableDocument document) {
+ if (aList == null || aList.getAnnotations().isEmpty()) {
+ return;
+ }
+
+ // TODO in case of trapped in Annotation like 'export @Final public class A{}' - a reorder would be necessary
+ // (see format for export)
+ document.prepend(aList, hrf -> {
+ hrf.setNewLines(2, 2, 2);
+ hrf.highPriority();
+ });
+
+ // TODO special annotations like @Internal ? --> together with public, reorder to be in same line?
+ document.append(aList, hrf -> hrf.newLine());
+
+ for (int idx = 0; idx < aList.getAnnotations().size(); idx++) {
+ Annotation it = aList.getAnnotations().get(idx);
+ configureAnnotation(it, document, true, idx == 0);
+ }
+ }
+
+ /**
+ *
+ * @param withLineWraps
+ * true
do line-wrapping
+ * @param isFirstAnnotation
+ * if this is the first annotation in a sequence ( used with line-wrapping in front of '@')
+ */
+ private void configureAnnotation(Annotation ann, IFormattableDocument document, boolean withLineWraps,
+ boolean isFirstAnnotation) {
+ // configure arguments
+ Pair parens = textRegionExtensions.regionFor(ann).keywordPairs("(", ")")
+ .get(0);
+ if (parens != null) {
+ document.append(document.prepend(parens.getKey(), hrf -> hrf.noSpace()), hrf -> hrf.noSpace());
+ document.append(document.prepend(parens.getValue(), hrf -> hrf.noSpace()), hrf -> {
+ if (withLineWraps) {
+ hrf.noSpace();
+ hrf.setNewLines(1);
+ } else {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ }
+ });
+ document.interior(parens, hrf -> hrf.indent());
+ // line break before "@":
+ if (withLineWraps && !isFirstAnnotation) {
+ document.prepend(parens.getKey().getPreviousSemanticRegion().getPreviousSemanticRegion(),
+ hrf -> hrf.setNewLines(1));
+ }
+
+ configureCommas(ann, document);
+ }
+
+ // Configure @-Syntax
+ // Special case here: for "@XY" we can either get "@" or "XY" as the first semantic element
+ ISemanticRegion sr = head(textRegionExtensions.semanticRegions(ann));
+ if (sr.getGrammarElement() instanceof Keyword) {
+ // assume '@'
+ document.append(sr, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ } else {
+ // for "@Final" "Final" will be the first semantic region in case of exported classes,
+ document.prepend(sr, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void _configureAnnotations(Object semEObject, IFormattableDocument document) {
+ // no annotations to be configured.
+ }
+
+ @SuppressWarnings("unused")
+ private void _configureAnnotations(Void x, IFormattableDocument document) {
+ // no annotations to be configured.
+ }
+
+ private void configureAnnotationsInLine(FormalParameter fpar, IFormattableDocument document) {
+ if (fpar.getAnnotations().isEmpty()) {
+ return;
+ }
+ // (@x @y("") bogus a:typ)
+ Annotation fann = fpar.getAnnotations().get(0);
+
+ configureAnnotation(fann, document, false, true);
+ document.prepend(fann, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+
+ for (Annotation ann : tail(fpar.getAnnotations())) {
+ configureAnnotation(ann, document, false, false);
+ document.prepend(ann, hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+ }
+
+ document.append(last(fpar.getAnnotations()), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+ }
+
+ /** only script-level annotations '@@' */
+ private void formatScriptAnnotations(Script script, IFormattableDocument document) {
+ if (script.getAnnotations().isEmpty()) {
+ return;
+ }
+
+ if (textRegionExtensions.previousHiddenRegion(script.getAnnotations().get(0)).containsComment()) {
+ document.prepend(script.getAnnotations().get(0), hrf -> hrf.noSpace());
+ } else {
+ document.prepend(script.getAnnotations().get(0), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+ document.append(last(script.getAnnotations()), hrf -> hrf.setNewLines(2, 2, 2));
+
+ for (int idx = 0; idx < script.getAnnotations().size(); idx++) {
+ Annotation it = script.getAnnotations().get(idx);
+ if (idx != 0) {
+ document.prepend(it, hrf -> {
+ hrf.setNewLines(1);
+ hrf.noSpace();
+ });
+ }
+ // its an '@@'
+ ISemanticRegion sr = head(textRegionExtensions.semanticRegions(it));
+ document.append(sr, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+ }
+
+ @Override
+ public ITextReplacer createCommentReplacer(IComment comment) {
+ // Overridden to distinguish between JSDOC-style, standard ML, formatter-off ML-comment.
+ EObject grammarElement = comment.getGrammarElement();
+ if (grammarElement instanceof AbstractRule) {
+ String ruleName = ((AbstractRule) grammarElement).getName();
+ if (ruleName.startsWith("ML")) {
+ String cText = comment.getText();
+ if (cText.startsWith("/**") && !cText.startsWith("/***")) { // JSDOC
+ return new N4MultilineCommentReplacer(comment, '*');
+ } else if (cText.startsWith("/*-")) { // Turn-off formatting.
+ return new OffMultilineCommentReplacer(comment, !isNotFirstInLine(comment));
+ } else { // All other
+ return new FixedMultilineCommentReplacer(comment);
+ }
+ }
+ if (ruleName.startsWith("SL")) {
+ if (isNotFirstInLine(comment)) {
+ return new SinglelineDocCommentReplacer(comment, "//");
+ } else {
+ return new SinglelineCodeCommentReplacer(comment, "//");
+ }
+ }
+ }
+
+ // fall back to super-impl.
+ return super.createCommentReplacer(comment);
+ }
+
+ private static boolean isNotFirstInLine(IComment comment) {
+ ILineRegion lineRegion = comment.getLineRegions().get(0);
+
+ return !comment.contains(lineRegion.getOffset());
+ }
+
+ @Override
+ public IndentHandlingTextReplaceMerger createTextReplacerMerger() {
+ return new IndentHandlingTextReplaceMerger(this);
+ }
+
+ /** DEBUG-helper */
+ private static String containmentStructure(EObject eo) {
+ String name = eo.getClass().getSimpleName();
+ if (eo.eContainer() != null) {
+ return containmentStructure(eo.eContainer()) + "." + eo.eContainingFeature().getName() + "->" + name;
+ }
+ return name;
+ }
+
+ /** HELPER to avoid Warnings in code, since @SuppressWarnings("unused") is not active in xtend code. */
+ @SuppressWarnings("unused")
+ int suppressUnusedWarnings(Object... e) {
+ return PRIO_4;
+ }
+
+ /**
+ * Simple tracker that only gives exactly one time the value {@code true} when calling
+ * {@link StateTrack#shouldDoThenDone()}
+ */
+ private final static class StateTrack {
+ private boolean done = false;
+
+ /**
+ * This method returns {@code true} exactly on it's first invocation. Proceeding calls always return
+ * {@code false}.
+ *
+ * @return Returns {@code true} if not done, immediately switches {@link #done} to {@code true}; returns
+ * {@code false} if already done.
+ */
+ boolean shouldDoThenDone() {
+ boolean ret = !done;
+ done = true;
+ return ret;
+ }
+ }
+
+ /****************************************************************************************************************
+ *
+ * Type Expression
+ *
+ ***************************************************************************************************************/
+ void _format(UnionTypeExpression ute, IFormattableDocument document) {
+ for (ISemanticRegion sr : textRegionExtensions.regionFor(ute).keywords("|")) {
+ document.prepend(document.surround(sr, hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ }), hrf -> {
+ hrf.autowrap();
+ hrf.highPriority();
+ });
+ }
+
+ for (TypeRef tr : ute.getTypeRefs()) {
+ format(tr, document);
+ }
+
+ // OLD syntax:
+ ISemanticRegion kwUnion = textRegionExtensions.regionFor(ute).keyword("union");
+ if (kwUnion != null) {
+ ISequentialRegion sr = document.append(document.prepend(kwUnion, hrf -> hrf.oneSpace()), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+
+ /* '{' */
+ document.append(sr.getNextSemanticRegion(), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ /* '}' */
+ document.prepend(last(textRegionExtensions.semanticRegions(ute)), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ }
+ }
+
+ void _format(IntersectionTypeExpression ite, IFormattableDocument document) {
+ for (ISemanticRegion sr : textRegionExtensions.regionFor(ite).keywords("&")) {
+ document.prepend(document.surround(sr, hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ }), hrf -> {
+ hrf.autowrap();
+ hrf.highPriority();
+ });
+ }
+
+ for (TypeRef tr : ite.getTypeRefs()) {
+ format(tr, document);
+ }
+ // OLD syntax
+ ISemanticRegion kwInersection = textRegionExtensions.regionFor(ite).keyword("intersection");
+ if (kwInersection != null) {
+ ISequentialRegion sr = document.append(document.prepend(kwInersection, hrf -> hrf.oneSpace()), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ /* '{' */
+ document.append(sr.getNextSemanticRegion(), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ /* '}' */
+ document.prepend(last(textRegionExtensions.semanticRegions(ite)), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ });
+ }
+ }
+
+ void _format(TStructMember tsm, IFormattableDocument document) {
+ if (tsm instanceof TField) {
+ configureOptionality((TField) tsm, document);
+ } else if (tsm instanceof org.eclipse.n4js.ts.types.FieldAccessor) {
+ configureGetSetKeyword((org.eclipse.n4js.ts.types.FieldAccessor) tsm, document);
+ configureOptionality((org.eclipse.n4js.ts.types.FieldAccessor) tsm, document);
+
+ Pair parenPair = textRegionExtensions.regionFor(tsm)
+ .keywordPairs("(", ")").get(0);
+ document.append(document.prepend(parenPair.getKey(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ }), hrf -> hrf.noSpace());
+ }
+ // get, set, method, field
+
+ for (EObject eo : tsm.eContents()) {
+ format(eo, document);
+ }
+ // TODO format TStruct* more thoroughly
+ // (note: added some TStructMember formatting while working on IDE-2405, but it is still incomplete!)
+ }
+
+ private void configureUndefModifier(StaticBaseTypeRef sbtr, IFormattableDocument document) {
+ // UndefModifier "?"
+ document.prepend(textRegionExtensions.regionFor(sbtr)
+ .feature(TypeRefsPackage.Literals.TYPE_REF__FOLLOWED_BY_QUESTION_MARK), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+
+ void _format(ThisTypeRef ttr, IFormattableDocument document) {
+ configureUndefModifier(ttr, document);
+ if (ttr instanceof ThisTypeRefStructural) {
+ interiorBUGFIX(ttr, hrf -> hrf.indent(), document);
+ configureStructuralAdditions(ttr, document);
+ for (EObject eo : ttr.eContents()) {
+ format(eo, document);
+ }
+ }
+ }
+
+ void _format(ParameterizedTypeRef ptr, IFormattableDocument document) {
+ interiorBUGFIX(ptr, hrf -> hrf.indent(), document); // ptr.interior, hrf->hrf.indent();
+ configureUndefModifier(ptr, document);
+
+ // Union / Intersection
+ for (ISemanticRegion sr : textRegionExtensions.regionFor(ptr).keywords("&", "|")) {
+ document.append(document.surround(sr, hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ }), hrf -> {
+ hrf.autowrap();
+ hrf.highPriority();
+ });
+ }
+ // Short-Hand Syntax for Arrays
+ if (ptr.isArrayTypeExpression()) {
+ document.append(textRegionExtensions.regionFor(ptr).keyword("["), hrf -> hrf.noSpace());
+ document.append(textRegionExtensions.regionFor(ptr).keyword("]"), hrf -> hrf.noSpace());
+ }
+ // Short-Hand Syntax for IterableN
+ if (ptr.isArrayNTypeExpression()) {
+ document.append(textRegionExtensions.regionFor(ptr).keyword("["), hrf -> hrf.noSpace());
+ document.append(textRegionExtensions.regionFor(ptr).keyword("]"), hrf -> hrf.noSpace());
+ }
+ formatTypeArguments(ptr, document);
+
+ // ParameterizedTypeRefStructural :
+ configureStructuralAdditions(ptr, document);
+
+ // generically format content:
+ for (EObject eo : ptr.eContents()) {
+ format(eo, document);
+ }
+ }
+
+ /** used for "~X with {}" except for the 'X' part. */
+ void configureStructuralAdditions(TypeRef ptr, IFormattableDocument document) {
+ ISemanticRegion semRegTypingStrategy = textRegionExtensions.regionFor(ptr)
+ .ruleCallTo(grammarAccess.getTypingStrategyUseSiteOperatorRule());
+ if (semRegTypingStrategy != null) {
+ document.append(document.prepend(semRegTypingStrategy, hrf -> hrf.oneSpace()), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+
+ // declaredType
+ document.append(semRegTypingStrategy.getNextSemanticRegion(), null);
+ ISemanticRegion kwWith = textRegionExtensions.regionFor(ptr).keyword("with");
+
+ if (kwWith != null) {
+ document.surround(kwWith, hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+
+ Pair bracesPair = textRegionExtensions.regionFor(ptr)
+ .keywordPairs("{", "}").get(0);
+
+ document.append(bracesPair.getKey(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+
+ document.prepend(bracesPair.getValue(), hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+
+ // textRegionExtensions.regionFor(ptr).keywords(",",";").forEach[
+ // prepend[hrf.noSpace();hrf.setNewLines(1);].append[hrf.oneSpace();hrf.setNewLines(1); hrf.autowrap();
+ // } ]
+ for (ISemanticRegion sr : textRegionExtensions.regionFor(ptr).keywords(";")) {
+ document.append(document.prepend(sr, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ }), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ hrf.lowPriority();
+ });
+ }
+
+ for (TStructMember sm : tail((((StructuralTypeRef) ptr).getAstStructuralMembers()))) {
+ document.set(textRegionExtensions.regionForEObject(sm).getPreviousHiddenRegion(), hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+ }
+ }
+ }
+ }
+
+ /** formats type argument section including outside border. */
+ void formatTypeArguments(ParameterizedTypeRef semObject, IFormattableDocument document) {
+ if (semObject.getDeclaredTypeArgs().isEmpty()) {
+ return;
+ }
+ // to "<":
+ document.prepend(document.append(textRegionExtensions.regionFor(semObject).keyword("<"),
+ hrf -> hrf.noSpace()),
+ hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ hrf.lowPriority();
+ });
+ document.append(document.prepend(textRegionExtensions.regionFor(semObject).keyword(">"),
+ hrf -> hrf.noSpace()),
+ hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ hrf.lowPriority();
+ });
+ for (TypeArgument typeArg : semObject.getDeclaredTypeArgs()) {
+ document.append(textRegionExtensions.immediatelyFollowing(document.append(typeArg, hrf -> hrf.noSpace()))
+ .keyword(","), hrf -> hrf.oneSpace());
+ format(typeArg, document);
+ }
+
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Configure Methods
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ private void configureGetSetKeyword(FieldAccessor fieldAccessor, IFormattableDocument document) {
+ String kw = (fieldAccessor instanceof GetterDeclaration) ? "get" : "set";
+
+ document.append(document.prepend(textRegionExtensions.regionFor(fieldAccessor).keyword(kw),
+ hrf -> hrf.oneSpace()),
+ hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+ }
+
+ private void configureGetSetKeyword(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor,
+ IFormattableDocument document) {
+
+ String kw = (tFieldAccessor instanceof TGetter) ? "get" : "set";
+ document.append(document.prepend(textRegionExtensions.regionFor(tFieldAccessor).keyword(kw),
+ hrf -> hrf.oneSpace()),
+ hrf -> {
+ hrf.oneSpace();
+ hrf.setNewLines(0);
+ hrf.autowrap();
+ });
+ }
+
+ private void configureOptionality(N4FieldDeclaration fieldDecl, IFormattableDocument document) {
+ document.prepend(
+ textRegionExtensions.regionFor(fieldDecl)
+ .feature(N4JSPackage.Literals.N4_FIELD_DECLARATION__DECLARED_OPTIONAL),
+ hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+
+ private void configureOptionality(FieldAccessor fieldAccessor, IFormattableDocument document) {
+ document.prepend(
+ textRegionExtensions.regionFor(fieldAccessor)
+ .feature(N4JSPackage.Literals.FIELD_ACCESSOR__DECLARED_OPTIONAL),
+ hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+
+ private void configureOptionality(TField tField, IFormattableDocument document) {
+ document.prepend(
+ textRegionExtensions.regionFor(tField).feature(TypesPackage.Literals.TFIELD__OPTIONAL),
+ hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+
+ private void configureOptionality(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor,
+ IFormattableDocument document) {
+ document.prepend(
+ textRegionExtensions.regionFor(tFieldAccessor).feature(TypesPackage.Literals.FIELD_ACCESSOR__OPTIONAL),
+ hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Bug-workarounds
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ /**
+ * Temporarily used method to replace document.interior(EObject, Procedure1) to prevent wrong indentations.
+ *
+ * Main pattern replace document-extension method call:
+ *
+ *
+ * object.interior, hrf->hrf.indent()
+ *
+ *
+ * by
+ *
+ *
+ * object.interiorBUGFIX(, hrf->hrf.indent(),document);
+ *
+ *
+ *
+ */
+ void interiorBUGFIX(EObject object, Procedure1 super IHiddenRegionFormatter> init,
+ IFormattableDocument document) {
+
+ IEObjectRegion objRegion = getTextRegionAccess().regionForEObject(object);
+ if (objRegion != null) {
+ IHiddenRegion previous = objRegion.getPreviousHiddenRegion();
+ IHiddenRegion next = objRegion.getNextHiddenRegion();
+ if (previous != null && next != null && previous != next) {
+ ISemanticRegion nsr = previous.getNextSemanticRegion();
+ ISemanticRegion psr = next.getPreviousSemanticRegion();
+ if (nsr != psr) { // standard
+ // case
+ document.interior(nsr, psr, init);
+ } else {
+ // former error-case:
+ // there is no interior --> don't do anything!
+ //
+ // applying to the next HiddenRegion is a bad idea,
+ // since it could wrongly indent a multiline-comment (c.f. GHOLD-260)
+ }
+ }
+ }
+ }
+
+ /**
+ * Dummy method to prevent accidentally calling interior - extension method form document. You should call
+ * interiorBUGFIX instead !
+ */
+ @SuppressWarnings("unused")
+ Procedure1 super IFormattableDocument> interior(EObject eo, Procedure1 super IHiddenRegionFormatter> init) {
+ throw new IllegalStateException("Method should not be called.");
+ }
+
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend
deleted file mode 100644
index 9ca9a39a56..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend
+++ /dev/null
@@ -1,1441 +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.formatting2;
-
-import com.google.inject.Inject
-import org.eclipse.emf.common.util.EList
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.n4js.n4JS.AbstractAnnotationList
-import org.eclipse.n4js.n4JS.AbstractCaseClause
-import org.eclipse.n4js.n4JS.AdditiveExpression
-import org.eclipse.n4js.n4JS.AnnotableExpression
-import org.eclipse.n4js.n4JS.AnnotableN4MemberDeclaration
-import org.eclipse.n4js.n4JS.AnnotablePropertyAssignment
-import org.eclipse.n4js.n4JS.AnnotableScriptElement
-import org.eclipse.n4js.n4JS.Annotation
-import org.eclipse.n4js.n4JS.ArrayLiteral
-import org.eclipse.n4js.n4JS.ArrowFunction
-import org.eclipse.n4js.n4JS.AssignmentExpression
-import org.eclipse.n4js.n4JS.AwaitExpression
-import org.eclipse.n4js.n4JS.BinaryBitwiseExpression
-import org.eclipse.n4js.n4JS.BinaryLogicalExpression
-import org.eclipse.n4js.n4JS.BindingPattern
-import org.eclipse.n4js.n4JS.Block
-import org.eclipse.n4js.n4JS.BooleanLiteral
-import org.eclipse.n4js.n4JS.CastExpression
-import org.eclipse.n4js.n4JS.CatchBlock
-import org.eclipse.n4js.n4JS.CommaExpression
-import org.eclipse.n4js.n4JS.ConditionalExpression
-import org.eclipse.n4js.n4JS.EqualityExpression
-import org.eclipse.n4js.n4JS.ExportDeclaration
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.ExpressionStatement
-import org.eclipse.n4js.n4JS.FieldAccessor
-import org.eclipse.n4js.n4JS.FinallyBlock
-import org.eclipse.n4js.n4JS.ForStatement
-import org.eclipse.n4js.n4JS.FormalParameter
-import org.eclipse.n4js.n4JS.FunctionDeclaration
-import org.eclipse.n4js.n4JS.FunctionDefinition
-import org.eclipse.n4js.n4JS.FunctionExpression
-import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor
-import org.eclipse.n4js.n4JS.GenericDeclaration
-import org.eclipse.n4js.n4JS.GetterDeclaration
-import org.eclipse.n4js.n4JS.IdentifierRef
-import org.eclipse.n4js.n4JS.IfStatement
-import org.eclipse.n4js.n4JS.ImportDeclaration
-import org.eclipse.n4js.n4JS.IndexedAccessExpression
-import org.eclipse.n4js.n4JS.IntLiteral
-import org.eclipse.n4js.n4JS.JSXElement
-import org.eclipse.n4js.n4JS.MultiplicativeExpression
-import org.eclipse.n4js.n4JS.N4ClassDeclaration
-import org.eclipse.n4js.n4JS.N4EnumDeclaration
-import org.eclipse.n4js.n4JS.N4FieldDeclaration
-import org.eclipse.n4js.n4JS.N4InterfaceDeclaration
-import org.eclipse.n4js.n4JS.N4JSPackage
-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.ObjectLiteral
-import org.eclipse.n4js.n4JS.ParameterizedCallExpression
-import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression
-import org.eclipse.n4js.n4JS.ParenExpression
-import org.eclipse.n4js.n4JS.PostfixExpression
-import org.eclipse.n4js.n4JS.PromisifyExpression
-import org.eclipse.n4js.n4JS.RegularExpressionLiteral
-import org.eclipse.n4js.n4JS.RelationalExpression
-import org.eclipse.n4js.n4JS.ReturnStatement
-import org.eclipse.n4js.n4JS.Script
-import org.eclipse.n4js.n4JS.ShiftExpression
-import org.eclipse.n4js.n4JS.StringLiteral
-import org.eclipse.n4js.n4JS.SuperLiteral
-import org.eclipse.n4js.n4JS.SwitchStatement
-import org.eclipse.n4js.n4JS.TaggedTemplateString
-import org.eclipse.n4js.n4JS.TemplateLiteral
-import org.eclipse.n4js.n4JS.TemplateSegment
-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.VariableStatement
-import org.eclipse.n4js.n4JS.YieldExpression
-import org.eclipse.n4js.services.N4JSGrammarAccess
-import org.eclipse.n4js.ts.typeRefs.IntersectionTypeExpression
-import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef
-import org.eclipse.n4js.ts.typeRefs.StaticBaseTypeRef
-import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef
-import org.eclipse.n4js.ts.typeRefs.ThisTypeRef
-import org.eclipse.n4js.ts.typeRefs.ThisTypeRefStructural
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage
-import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression
-import org.eclipse.n4js.ts.types.TField
-import org.eclipse.n4js.ts.types.TGetter
-import org.eclipse.n4js.ts.types.TStructMember
-import org.eclipse.n4js.ts.types.TypesPackage
-import org.eclipse.xtext.AbstractRule
-import org.eclipse.xtext.Keyword
-import org.eclipse.xtext.formatting2.IAutowrapFormatter
-import org.eclipse.xtext.formatting2.IFormattableDocument
-import org.eclipse.xtext.formatting2.IHiddenRegionFormatter
-import org.eclipse.xtext.formatting2.IHiddenRegionFormatting
-import org.eclipse.xtext.formatting2.ITextReplacer
-import org.eclipse.xtext.formatting2.internal.SinglelineCodeCommentReplacer
-import org.eclipse.xtext.formatting2.internal.SinglelineDocCommentReplacer
-import org.eclipse.xtext.formatting2.regionaccess.IComment
-import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion
-import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion
-import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion
-import org.eclipse.xtext.formatting2.regionaccess.ITextSegment
-import org.eclipse.xtext.xbase.lib.Procedures.Procedure1
-import org.eclipse.xtext.xtext.generator.parser.antlr.splitting.simpleExpressions.NumberLiteral
-
-import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.*
-import static org.eclipse.n4js.formatting2.N4JSGenericFormatter.*
-
-class N4JSFormatter extends TypeExpressionsFormatter {
-
- /** Debug switch */
- private static var debug = false;
-
- @Inject extension N4JSGrammarAccess
-
- /** PRIO_4 = -7 - still very low.
- * Standard priorities in the formatter are:
- *
- * - lowPriority = -1; == PRIO_10
- *
- normal priority = 0; == PRIO_11
- *
- high priority = +1; == PRIO_12
- *
- */
- static val PRIO_4 = PRIO_3 + 1;
- static val PRIO_13 = IHiddenRegionFormatter.HIGH_PRIORITY + 1;
-
-
- private def maxConsecutiveNewLines() {
- // let's stick to 2
- getPreference(FORMAT_MAX_CONSECUTIVE_NEWLINES);
- }
-
-
- def dispatch void format(Script script, extension IFormattableDocument document) {
-
- val extension generic = new N4JSGenericFormatter(_n4JSGrammarAccess, textRegionExtensions)
- if (getPreference(FORMAT_PARENTHESIS)) {
-// script.formatParenthesisBracketsAndBraces(document)
- }
-
- // TODO the following line requires more conflict handling with semicolons:
- // script.interior[noIndentation;];
-
- script.formatSemicolons(document);
- script.formatColon(document);
-
- formatScriptAnnotations(script,document);
-
- for (element : script.scriptElements) {
- // element.append[setNewLines(1, 1, maxConsecutiveNewLines);noSpace; autowrap].prepend[noSpace];
- element.append[setNewLines(1, 1, maxConsecutiveNewLines); autowrap];
- element.format;
- }
-
- // format last import, overrides default newLines:
- script.scriptElements.filter(ImportDeclaration).last?.append[setNewLines(2, 2, 3); highPriority];
-
- }
-
- /** put modifiers into a single line separated by one space, last modifier has one space to following element. */
- def void configureModifiers(EObject semObject, extension IFormattableDocument document){
- semObject.regionFor.ruleCallsTo( n4ModifierRule ).forEach[
- append[oneSpace];
- ];
- }
-
- def void configureTypingStrategy(EObject semObject, extension IFormattableDocument document) {
- semObject.regionFor.ruleCallsTo( typingStrategyDefSiteOperatorRule, typingStrategyUseSiteOperatorRule).forEach[
- append[noSpace];
- ]
- }
-
- def void formatTypeVariables(GenericDeclaration semObject, extension IFormattableDocument document) {
- if( semObject.typeVars.isEmpty ) return;
- // to "<":
- semObject.regionFor.keyword("<").prepend[oneSpace;newLines=0].append[noSpace];
- semObject.regionFor.keyword(">").prepend[noSpace;newLines=0];
- for( typeVar: semObject.typeVars ){
- typeVar.append[noSpace].immediatelyFollowing.keyword(",").append[oneSpace];
- typeVar.format(document);
- }
-
- }
-
- def dispatch format(N4ClassDeclaration clazz, extension IFormattableDocument document) {
-
- clazz.configureAnnotations(document);
- clazz.insertSpaceInFrontOfCurlyBlockOpener(document);
- clazz.indentExcludingAnnotations(document);
-
-
- clazz.configureTypingStrategy(document);
- clazz.configureModifiers(document);
-
- clazz.formatTypeVariables(document);
-
-// val semRegModifier = clazz.regionFor.feature( N4JSPackage.Literals.MODIFIABLE_ELEMENT__DECLARED_MODIFIERS);
-// if( semRegModifier !== null ) { // only if exists.
-// val beginModifierHR = semRegModifier.previousHiddenRegion;
-// val endModifierHR = semRegModifier.nextHiddenRegion;
-// // go over all semantic regions in the modifier location.
-// var currHR = beginModifierHR.nextHiddenRegion;
-// while( currHR != endModifierHR ){
-// currHR.set[oneSpace];
-// currHR = currHR.nextHiddenRegion;
-// }
-// endModifierHR.set[oneSpace];
-// } // end modifier formatting TODO extract into method.
-
- // TODO revise the following pattern of call-back implementations.
- // Define lambda for callback & normal use:
- val twolinesBeforeFirstMember = [int prio | clazz.ownedMembersRaw.head.prepend[newLines=2;priority = prio]];
-
- // Defines CallBack for autoWrap:
- val callBackOnAutoWrap = new IAutowrapFormatter{ // callback for auto-wrapping with implements
- var didReconfigure = false; // track to only execute once.
- override format(ITextSegment region, IHiddenRegionFormatting wrapped, IFormattableDocument document) {
- if( !didReconfigure ) {
- twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY); // reformat with higher priority
- didReconfigure=true; // keep state.
- }
- }
- };
-
- // 2nd version of implementing the callback:
- val StateTrack state2 = new StateTrack;
- val IAutowrapFormatter callBackOnAutoWrap2 = [region, hrFormatting, document2 |
- if( state2.shouldDoThenDone ) twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY);
- ];
- suppressUnusedWarnings( callBackOnAutoWrap2 );
-
- // Allow for lineBreaks in front of keywords:
- clazz.regionFor.keyword("extends").prepend[
- setNewLines(0,0,1); // allow line break in front.
- autowrap;
- ].append[oneSpace; autowrap;];
-
- clazz.regionFor.keyword("implements").prepend[
- setNewLines(0,0,1);
- autowrap;
- priority = IHiddenRegionFormatter.LOW_PRIORITY;
- onAutowrap=callBackOnAutoWrap;
- ].append[oneSpace; autowrap;];
-
- clazz.implementedInterfaceRefs.tail.forEach[prepend[
- autowrap;
- priority = IHiddenRegionFormatter.LOW_PRIORITY;
- onAutowrap=callBackOnAutoWrap;
- ]];
-
- // special case if the header of the class spans multiple lines, then insert extra line break.
- val kwClass = clazz.regionFor.keyword("class");
- val kwBrace = clazz.regionFor.keyword("{"); // autowrap-listener ?
- if( ! kwClass.lineRegions.head.contains( kwBrace ) ) {
- twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.NORMAL_PRIORITY);
- } else {
- clazz.ownedMembersRaw.head.prepend[setNewLines(1,1,maxConsecutiveNewLines);autowrap;];
- }
-
- kwClass.append[oneSpace];
-
- for (member : clazz.ownedMembersRaw) {
- member.append[setNewLines(1, 1, maxConsecutiveNewLines)]
- member.format
- }
-
- // Collapse empty block:
- if( clazz.ownedMembersRaw.isEmpty) {
- // Empty body:
- kwBrace.append[noSpace;newLines=0];
- }
- }
-
- def dispatch format(N4InterfaceDeclaration interf, extension IFormattableDocument document) {
- interf.configureAnnotations(document);
- interf.configureModifiers(document);
- interf.insertSpaceInFrontOfCurlyBlockOpener(document);
- interf.indentExcludingAnnotations(document);// .interiorBUGFIX([indent],document); //interf.interior[indent];
-
- interf.ownedMembersRaw.head.prepend[setNewLines(1, 1, maxConsecutiveNewLines)]
- for (member : interf.ownedMembersRaw) {
- member.append[setNewLines(1, 1, maxConsecutiveNewLines)]
- member.format
- }
- }
-
-// def dispatch void format(N4MemberDeclaration member, extension IFormattableDocument document) {
-// member.regionFor.keyword("(").prepend[noSpace; newLines=0].append[noSpace]
-//
-// member.insertSpaceInfrontOfPropertyNames(document);
-// for (c : member.eContents) {
-// c.format;
-// }
-// }
-
- def dispatch void format(N4FieldDeclaration field, extension IFormattableDocument document) {
- field.configureAnnotations(document);
- field.configureModifiers(document);
-
- field.indentExcludingAnnotations(document);
-
- field.configureOptionality(document);
- field.regionFor.keyword("=").prepend[oneSpace].append[oneSpace];
- field.expression.format;
- field.declaredTypeRefInAST.format;
- }
-
-
-
-
-// def dispatch format(N4MethodDeclaration method, extension IFormattableDocument document) {
-// method.configureAnnotations(document);
-// method.insertSpaceInfrontOfPropertyNames(document);
-//
-// method.regionFor.keyword("(").prepend[noSpace; newLines=0]
-//
-// method.body.regionFor.keyword("{").prepend[oneSpace; newLines = 0]
-// for (child : method.eContents) {
-// child.format;
-// }
-// }
-//
- def dispatch void format(FunctionExpression funE, extension IFormattableDocument document) {
- funE.configureAnnotations(document);
- funE.configureModifiers(document);
- if (funE.isArrowFunction) {
- throw new IllegalStateException("Arrow functions should be formated differently.")
- }
- funE.fpars.configureFormalParameters(document,[/*n.t.d.*/]);
- val parenPair = funE.regionFor.keywordPairs("(",")").head;
- parenPair.key.append[noSpace];
- parenPair.value.prepend[noSpace];
- funE.body.format;
- }
-
-
-
- def dispatch format(FunctionOrFieldAccessor fDecl, extension IFormattableDocument document) {
- fDecl.configureAnnotations(document);
- fDecl.configureModifiers(document);
-
- // State-keeper to avoid clashing reconfigurations if multiple auto-wraps get triggered.
- val state = new StateTrack; // use state to only trigger one change, even if called multiple times.
-
- // Callback to introduce an additional line in body-block.
- val (ITextSegment , IHiddenRegionFormatting , IFormattableDocument )=>void cbInsertEmptyLineInBody = [
- if(state.shouldDoThenDone){
- fDecl.body?.statements?.head.prepend[ setNewLines(2,2,maxConsecutiveNewLines); highPriority];
- }
- ];
-
- // Formal parameters
- switch( fDecl) {
- FunctionDefinition :
- fDecl.fpars.configureFormalParameters(document, cbInsertEmptyLineInBody )
- N4SetterDeclaration:
- fDecl.fpar.prepend[noSpace].append[noSpace] /* no autowrap for setters: cbInsertEmptyLineInBody */
- }
-
- // Type Variables
- switch( fDecl) {
- FunctionDeclaration :
- fDecl.formatTypeVariables(document)
- }
-
- // special case for accessors: get / set keywords
- if(fDecl instanceof FieldAccessor){
- fDecl.configureGetSetKeyword(document);
- fDecl.configureOptionality(document);
- }
-
-
-
- val parenPair = fDecl.regionFor.keywordPairs("(",")").head;
- parenPair.key.prepend[noSpace; newLines = 0].append[noSpace];
- parenPair.interior[indent];
- if( parenPair.isMultiLine && ! (fDecl instanceof FieldAccessor)) {
- // it is already a multiline, insert the newLine immediately.
- // cbInsertEmptyLineInBody.apply(null,null,null); // TODO re-think, if all will be collapsed this assumption does not hold an
- } else {
- // single line parameter block
- }
- parenPair.value.prepend[noSpace]
-
- for (child : fDecl.eContents) {
- child.format;
- }
- }
-
- /** to be used by FunctionDefintiions and SetterDeclarations */
- def void configureFormalParameters(EList list, extension IFormattableDocument document, (ITextSegment , IHiddenRegionFormatting , IFormattableDocument )=>void x){
- if( list === null || list.isEmpty ) return;
- list.forEach[it,idx|
- if(idx !== 0) it.prepend[oneSpace;setNewLines(0,0,1);onAutowrap=x];
- it.append[noSpace];
- it.configureAnnotationsInLine(document); // TODO maybe we need some in-line-annotation config here.
-// it.regionFor.ruleCallTo( bindingIdentifierAsFormalParameterRule ) // feature(N4JSPackage.Literals.FORMAL_PARAMETER__NAME)
-// .prepend[oneSpace;newLines=0].append[]
- it.declaredTypeRefInAST.format(document);
- if( it.isVariadic )
- it.regionFor.keyword("...").prepend[newLines=0;/*oneSpace;*/].append[newLines=0;noSpace];
- if( it.hasInitializerAssignment ) {
- it.regionFor.keyword("=").prepend[newLines=0;noSpace;].append[newLines=0;noSpace];
- if (it.initializer !== null) {
- it.initializer.format(document);
- }
- }
- ];
- }
-
- /** Check if key and value are in different lines. Defined for non-overlapping Regions, e.g. Keyword-pairs.*/
- def static boolean isMultiLine(Pair pair) {
- ! pair.key.lineRegions.last.contains( pair.value )
- }
-
-// def dispatch format(FunctionOrFieldAccessor fofAccessor, extension IFormattableDocument document) {
-// val begin = fofAccessor.body.semanticRegions.head
-// val end = fofAccessor.body.semanticRegions.last
-// if (begin?.lineRegions?.head?.contains(end?.endOffset)) {
-// // same line
-// } else {
-// // body spans multiple lines
-// begin.append[newLine;];
-// end.prepend[newLine;];
-// // fofAccessor.body.interior[indent]; // already by parenthesis?
-// }
-//
-// fofAccessor.body?.format;
-//
-// }
-
- def dispatch void format(N4EnumDeclaration enumDecl, extension IFormattableDocument document) {
- enumDecl.configureAnnotations(document);
- enumDecl.configureModifiers(document);
- enumDecl.insertSpaceInFrontOfCurlyBlockOpener(document);
- enumDecl.indentExcludingAnnotations(document);//.interiorBUGFIX([indent],document); //enumDecl.interior[indent];
- enumDecl.configureCommas(document);
-
- val braces = enumDecl.regionFor.keywordPairs("{","}").head;
-
- val multiLine = enumDecl.isMultiline;
-
- enumDecl.literals.forEach[
- format;
- if( multiLine ) {
- if( it.regionForEObject.previousHiddenRegion.containsComment )
- { // comment above
- it.prepend[newLines=2];
- } else { // no comment above
- it.prepend[newLine];
- }
- }
- ];
- if( multiLine ) {
- braces.value.prepend[newLine];
- }
- }
-
- def dispatch void format(ParameterizedPropertyAccessExpression exp, extension IFormattableDocument document) {
- val dotKW = exp.regionFor.keyword(".");
- dotKW.prepend[noSpace; autowrap; setNewLines(0,0,1)].append[noSpace;];
- if( exp.eContainer instanceof ExpressionStatement) {
- // top-level PPA, indent one level.
- exp.interiorBUGFIX([indent],document); //exp.interior[indent];
- }
- exp.target.format;
- }
-
- def dispatch void format(ParameterizedCallExpression exp, extension IFormattableDocument document) {
- // FIXME process typeArgs !!!
- val dotKW = exp.regionFor.keyword(".");
- dotKW.prepend[noSpace; autowrap;].append[noSpace;]
- exp.regionFor.keyword("(").prepend[noSpace].append[noSpace];
- exp.regionFor.keyword(")").prepend[noSpace];
- exp.configureCommas(document);
-
- exp.arguments.tail.forEach[prepend[oneSpace;autowrap]];
- exp.arguments.forEach[format];
-
- if( exp.eContainer instanceof ExpressionStatement) {
- // top-level PPA, indent one level.
- exp.interiorBUGFIX([indent],document); //exp.interior[indent];
- }
- exp.target.format;
- }
-
-
-
- def dispatch void format(ImportDeclaration decl, extension IFormattableDocument document) {
-
- // read configuration:
- val extraSpace = getPreference(FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE)
-
- decl.regionFor.keyword("{").prepend[oneSpace].append[if(extraSpace) oneSpace else noSpace];
- decl.regionFor.keyword("}").prepend[if(extraSpace) oneSpace else noSpace].append[oneSpace; newLines = 0];
- decl.regionFor.keyword("from").surround[oneSpace];
- decl.configureCommas(document);
- decl.eContents.forEach[format];
- }
-
- def dispatch void format(NamedImportSpecifier namedImp, extension IFormattableDocument document){
- namedImp.regionFor.keyword("as").prepend[oneSpace].append[oneSpace];
- namedImp.regionFor.feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC).prepend[noSpace].append[oneSpace]; // "+"-KW after alias-name
- }
-
- def dispatch void format(NamespaceImportSpecifier nsImp, extension IFormattableDocument document){
- nsImp.regionFor.keyword("*").append[oneSpace];
- nsImp.regionFor.keyword("as").append[oneSpace];
- nsImp.regionFor.feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC).prepend[noSpace].append[oneSpace]; // "+"-KW after alias-name
- }
-
- def dispatch void format(ExportDeclaration export, extension IFormattableDocument document){
- export.regionFor.keyword("export").append[
- oneSpace;
- newLines=0;
- // Apply prioritization to catch cases of 'trapped' annotations e.g. "export @Final public class" which
- // could also be reordered to "@Final export public class.."
- priority=PRIO_13; // Priority higher then highPriority used in AnnotationList.
- ];
-
- export.eContents.forEach[format];
-
- // Fix Trapped annotations:
- val exported = export.exportedElement;
- if( exported instanceof AnnotableScriptElement ){
- val annoList = exported.annotationList;
- if( annoList !== null && !annoList.annotations.isEmpty) {
- annoList.annotations.last.append[
- newLines = 0; oneSpace; priority = PRIO_13;
- ]
- }
- }
- }
-
- def dispatch void format(IfStatement stmt, extension IFormattableDocument document) {
- val parenPair = stmt.regionFor.keywordPairs("(",")").head;
- parenPair.interior[noSpace;indent];
- parenPair.key.prepend[oneSpace];
- parenPair.value.append[oneSpace];
-
- stmt.regionFor.keyword("else").prepend[autowrap;oneSpace].append[oneSpace];
-
- stmt.elseStmt.prepend[oneSpace; newLines = 0];
-
- stmt.expression.format;
- stmt.ifStmt.format;
- stmt.elseStmt.format;
- }
-
- def dispatch void format(SwitchStatement swStmt, extension IFormattableDocument document) {
- swStmt.insertSpaceInFrontOfCurlyBlockOpener(document);
- swStmt.interiorBUGFIX([indent],document); //swStmt.interior[indent];
- swStmt.expression.format;
- swStmt.cases.head.prepend[newLine];
- swStmt.cases.forEach[format];
- }
-
- /** Formats DefaultCaseClause + CaseClause */
- def dispatch void format(AbstractCaseClause caseClause, extension IFormattableDocument document) {
- caseClause.interiorBUGFIX([indent],document); //caseClause.interior[indent];
-
- if (caseClause.statements.size == 1) {
- if (caseClause.statements.head instanceof Block) {
- caseClause.statements.head.prepend[setNewLines(0,0,0)];
- } else {
- caseClause.statements.head.prepend[setNewLines(0,1,1)];
- }
- } else {
- caseClause.statements.head.prepend[setNewLines(1,1,1)];
- }
-
- // caseClause.regionFor.keyword(":").prepend[oneSpace]; // In case one space before the colon is desired
- caseClause.statements.forEach[format];
- caseClause.statements.forEach[append[setNewLines(1,1,maxConsecutiveNewLines)]];
- caseClause.append[setNewLines(1, 1, maxConsecutiveNewLines)];
- }
-
- def dispatch void format(CastExpression expr, extension IFormattableDocument document) {
- expr.regionFor.keyword("as").prepend[newLines = 0; oneSpace].append[newLines = 0; oneSpace];
- expr.expression.format;
- expr.targetTypeRefNode.format;
- }
-
- def dispatch void format(Block block, extension IFormattableDocument document) {
- if( debug ) println("Formatting block "+containmentStructure(block));
-
- // Beware there are blocks in the grammar, that are not surrounded by curly braces. (e.g. FunctionExpression)
-
- // Block not nested in other blocks usually are bodies. We want them separated by a space:
- if (! (block.eContainer instanceof Block || block.eContainer instanceof Script)) { // TODO maybe invert the control here, since the block is formatting the outside.
- block.regionFor.keyword("{").prepend[oneSpace];
- }
-
- block.interiorBUGFIX([indent],document); //block.interior[indent];
-
- block.statements.head.prepend[setNewLines(1,1,maxConsecutiveNewLines)];
- block.statements.forEach[append[setNewLines(1,1,maxConsecutiveNewLines)]];
-
- block.statements.forEach[format];
-
- // Format empty curly blocks, necessary for comments inside:
- val braces = block.regionFor.keywordPairs("{","}").head
- if( braces !== null
- && braces.key.nextSemanticRegion == braces.value
- ) {
- // empty block:
- if( braces.key.nextHiddenRegion.containsComment ) {
- braces.key.append[setNewLines(1,1,maxConsecutiveNewLines)];
- } else {
- braces.key.append[newLines=0;noSpace];
- }
- }
- }
-
-
- def dispatch void format(ReturnStatement ret, extension IFormattableDocument document) {
- ret.interiorBUGFIX([indent],document); //ret.interior[indent;]
- ret.expression.prepend[oneSpace; newLines = 0];
- ret.expression.format;
- }
-
- def dispatch void format(AdditiveExpression add, extension IFormattableDocument document) {
- add.regionFor.feature(N4JSPackage.Literals.ADDITIVE_EXPRESSION__OP).surround[oneSpace].append[autowrap];
- add.lhs.format
- add.rhs.format
- }
-
- def dispatch void format(MultiplicativeExpression mul, extension IFormattableDocument document) {
- mul.regionFor.feature(N4JSPackage.Literals.MULTIPLICATIVE_EXPRESSION__OP).surround[oneSpace].append[autowrap];
- mul.lhs.format
- mul.rhs.format
- }
-
- def dispatch void format(BinaryBitwiseExpression binbit, extension IFormattableDocument document) {
- binbit.regionFor.feature(N4JSPackage.Literals.BINARY_BITWISE_EXPRESSION__OP).surround[oneSpace];
- binbit.lhs.format
- binbit.rhs.format
- }
-
- def dispatch void format(BinaryLogicalExpression binLog, extension IFormattableDocument document) {
- val opReg = binLog.regionFor.feature(N4JSPackage.Literals.BINARY_LOGICAL_EXPRESSION__OP);
- opReg.surround[oneSpace];
- binLog.lhs.format
- binLog.rhs.format
- // auto-wrap:
- val autoWrapInFront = getPreference(FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR);
- if( autoWrapInFront ) {
- opReg.prepend[autowrap; lowPriority; setNewLines(0,0,1);]
- } else {
- opReg.append[autowrap; lowPriority; setNewLines(0,0,1);]
- };
- }
-
- def dispatch void format(EqualityExpression eqExpr, extension IFormattableDocument document) {
- eqExpr.regionFor.feature(N4JSPackage.Literals.EQUALITY_EXPRESSION__OP).surround[oneSpace].append[autowrap];
- eqExpr.lhs.format
- eqExpr.rhs.format
- }
-
- def dispatch void format(RelationalExpression relExpr, extension IFormattableDocument document) {
- relExpr.regionFor.feature(N4JSPackage.Literals.RELATIONAL_EXPRESSION__OP).surround[oneSpace].append[autowrap];
- relExpr.lhs.format
- relExpr.rhs.format
- }
-
- def dispatch void format(ShiftExpression shiftExpr, extension IFormattableDocument document) {
- shiftExpr.regionFor.feature(N4JSPackage.Literals.SHIFT_EXPRESSION__OP).surround[oneSpace].append[autowrap];
- shiftExpr.lhs.format
- shiftExpr.rhs.format
- }
-
- def dispatch void format(CommaExpression comma, extension IFormattableDocument document) {
- comma.configureCommas(document);
- comma.eContents.forEach[format];
- }
-
- def dispatch void format(ConditionalExpression cond, extension IFormattableDocument document) {
- cond.regionFor.keyword("?").surround[oneSpace].append[autowrap; lowPriority; setNewLines(0,0,1);];
- cond.regionFor.keyword(":").surround[oneSpace].append[autowrap; lowPriority; setNewLines(0,0,1);];
- cond.expression.format;
- cond.trueExpression.format;
- cond.falseExpression.format;
- }
-
- def dispatch void format(AwaitExpression await, extension IFormattableDocument document) {
- await.regionFor.keyword("await").prepend[oneSpace].append[oneSpace; newLines = 0];
- await.expression.format
- }
-
- def dispatch void format(PromisifyExpression promify, extension IFormattableDocument document) {
- promify.noSpaceAfterAT(document);
- promify.regionFor.keyword("Promisify").append[oneSpace];
- promify.expression.format
- }
-
- def dispatch void format(IndexedAccessExpression idxAcc, extension IFormattableDocument document) {
- val indexRegion = idxAcc.index.regionForEObject();
- indexRegion.previousSemanticRegion.prepend[noSpace;newLines=0].append[noSpace;newLines = 0];
- indexRegion.nextSemanticRegion.prepend[noSpace];
-
- idxAcc.index.format;
- idxAcc.target.format;
- }
-
- def dispatch void format(NewExpression newExp, extension IFormattableDocument document) {
- newExp.regionFor.keyword("new").prepend[oneSpace].append[oneSpace;newLines=0];
- newExp.callee.format;
- // Watch out, commas are used in Type-args and in argument list ! If necessary distinguish by offset.
- val commas = newExp.regionFor.keyword(",");
- commas.prepend[noSpace].append[oneSpace];
-
- // TODO maybe factor out TypeArgs formatting.
- val typeArgsAngle = newExp.regionFor.keywordPairs("<",">").head;
- if( typeArgsAngle !== null ) {
- typeArgsAngle.key.append[noSpace;newLines=0].prepend[noSpace;newLines=0];
- typeArgsAngle.value.prepend[noSpace;newLines=0];
- }
- newExp.typeArgs.forEach[format];
-
-
- if( newExp.isWithArgs ) {
- val argParen = newExp.regionFor.keywordPairs("(",")").head;
- argParen.key.prepend[newLines=0;noSpace].append[noSpace];
- argParen.value.prepend[noSpace];
- newExp.arguments.forEach[format];
- }
- }
-
- def dispatch void format(PostfixExpression postFix, extension IFormattableDocument document) {
- // no line break allowed between Expression and operator !
- postFix.regionFor.feature(N4JSPackage.Literals.POSTFIX_EXPRESSION__OP)
- .prepend[newLines=0;noSpace;].append[oneSpace;lowPriority]; // giving low priority for situations of closing parenthesis: "(a++)"
- postFix.expression.format;
- }
-
- def dispatch void format(TaggedTemplateString taggedTemplate, extension IFormattableDocument document) {
- taggedTemplate.regionFor.feature(N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET).append[ newLines = 0; oneSpace ];
- taggedTemplate.target.format;
- taggedTemplate.template.format;
- }
-
- def dispatch void format(UnaryExpression unaryExpr, extension IFormattableDocument document) {
- // The operators 'void' 'delete' and 'typeof' must be separated from operand.
- val boolean requireSpace=(unaryExpr.op.ordinal <= UnaryOperator.TYPEOF_VALUE);
- unaryExpr.regionFor.feature(N4JSPackage.Literals.UNARY_EXPRESSION__OP)
- .append[if(requireSpace) oneSpace else noSpace; newLines = 0;];
- unaryExpr.expression.format;
- }
-
- def dispatch void format(YieldExpression yieldExpr, extension IFormattableDocument document) {
- // " yield " or " yield* "
- yieldExpr.regionFor.keyword("yield")
- .prepend[oneSpace;]
- .append[if( yieldExpr.isMany ) noSpace else oneSpace];
- if( yieldExpr.isMany ){
- yieldExpr.regionFor.keyword("*").prepend[noSpace;newLines=0].append[oneSpace]
- }
- yieldExpr.expression.format;
- }
-
- def dispatch void format(ParenExpression parenE, extension IFormattableDocument document) {
- parenE.semanticRegions.head.append[noSpace;newLines=0;autowrap];
- parenE.semanticRegions.last.prepend[noSpace;newLines=0;autowrap];
- parenE.interiorBUGFIX([indent],document); //parenE.interior[indent];
- parenE.expression.format;
- }
-
- def dispatch void format(ArrowFunction arrowF, extension IFormattableDocument document) {
- arrowF.configureCommas(document);
- arrowF.regionFor.ruleCallTo(arrowRule).surround[oneSpace];
- arrowF.regionFor.keywordPairs("(",")").head?.interior[noSpace];
- // too lax: arrowF.fpars.configureFormalParameters(document,[/*NTD*/]);
-
- if( arrowF.isHasBracesAroundBody ) {
- // format body as block. NOTE: this block differs from other blocks, since the curly braces are defined in the ArrowExpression.
- // special handling of indentation in inside the braces.
- val bracesPair = arrowF.regionFor.keywordPairs("{","}").head;
- bracesPair.interior[indent];
- if( bracesPair.key.lineRegions.last.contains( bracesPair.value) // one line '{ do; stuff; }'
- || bracesPair.key.lineRegions.last.contains( bracesPair.key.nextSemanticRegion ) // no line-break after braces e.g. '{ do; \n stuff; }'
- ) {
- // one line
- arrowF.body?.statements.forEach[ it,idx|
- format; if( idx !==0 ) {it.prepend[oneSpace; autowrap; newLines=0;]}
- ];
- bracesPair.key.append[oneSpace]; // do not autowrap after "{" to keep wrap-semantic
- bracesPair.value.prepend[oneSpace];
- } else {
- // multi-line
- if( arrowF.body !== null && !arrowF.body.statements.empty ) {
- arrowF.body?.statements.head.prepend[newLines=1;];
- arrowF.body?.statements.forEach[format;append[newLines=1]];
- } else {
- // empty block, squash interior.
- bracesPair.key.append[noSpace;newLines=1;];
- bracesPair.value.prepend[noSpace;newLines=1];
- }
- }
- } else {
- // no braces Around the implicit return statement.
- arrowF.body?.statements.head.format;
- }
- }
-
-
- def dispatch void format(ArrayLiteral al, extension IFormattableDocument document) {
- val bracketPair = al.regionFor.keywordPairs("[","]").head;
- bracketPair.interior[indent];
- val sameLine = bracketPair.key.lineRegions.head.contains( bracketPair.value );
- // auto wrap in front of AL-Elements, to preserve comma at end.
- if( ! sameLine) {
- al.elements.last.append[autowrap];
- al.elements.forEach[it,num|prepend[autowrap;setNewLines(0,0,1);if(num!==0)oneSpace;].append[noSpace]];
- // format last bracket if in single.line.
- if( ! bracketPair.value.previousSemanticRegion.lineRegions.last.contains( bracketPair.value ) ) {
- bracketPair.value.prepend[newLine];
- }
- } else {
- al.elements.forEach[it,num|prepend[autowrap;if(num!==0)oneSpace;]];
- }
- }
-
- def dispatch void format(ObjectLiteral ol, extension IFormattableDocument document) {
- ol.configureCommas(document);
-
- val bracePair = ol.regionFor.keywordPairs("{","}").head;
- bracePair.interior[indent];
-
-
- // Decide on multiline or not.
- // Rule: if opening brace is preceded by a line break, then go multiline.
- val sameLine = bracePair.key.lineRegions.head.contains( bracePair.key.nextSemanticRegion.lineRegions.head );
- // OLD: val sameLine = bracePair.key.lineRegions.head.contains( bracePair.value );
- if( ! sameLine) {
- bracePair.value.prepend[newLine]; // format WS in front of closing brace
- ol.propertyAssignments.forEach[it,num|prepend[newLine]];
- if( bracePair.key.nextSemanticRegion == bracePair.value ) {
- // empty multiline, trigger formatting:
- bracePair.key.append[newLine];
- }
- } else { // in one line
- bracePair.key.append[newLines=0];
- ol.propertyAssignments.forEach[it,num|prepend[newLines=0;if(num!==0) { autowrap; oneSpace; } else {noSpace;}]];
- bracePair.value.prepend[newLines=0; noSpace; lowPriority]; // low priority to avoid conflict with dangling commas
- }
-
- ol.eContents.forEach[format];
- }
-
- def dispatch void format( ForStatement fst, extension IFormattableDocument document){
-
- fst.regionFor.keyword("for").append[oneSpace;newLines=0; autowrap];
-
- val parenPair = fst.regionFor.keywordPairs("(",")").head;
- parenPair.key.append[noSpace;autowrap;newLines=0];
- parenPair.value.prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap;];
-
- fst.regionFor.keywords("in","of").forEach[ it.surround[oneSpace; newLines=0; autowrap] ];
- fst.regionFor.keywords(";").forEach[it.prepend[noSpace;newLines=0;].append[oneSpace;newLines=0;autowrap]];
-
- fst.eContents.forEach[format];
- }
-
- def dispatch void format(TemplateLiteral tl, extension IFormattableDocument document) {
- tl.interiorBUGFIX([indent],document); //tl.interior[indent;];
- tl.segments.forEach[
- switch(it) {
- TemplateSegment: noOp
- default: it.surround[oneSpace; autowrap;]
- };
- it.format;
- ];
- }
- private def noOp (){}
-
- def dispatch void format(TemplateSegment tl, extension IFormattableDocument document) {
- // just leave as is.
- }
-
- def dispatch void format(N4TypeVariable tv, extension IFormattableDocument document) {
- // "out"
- if( tv.declaredCovariant ) { tv.regionFor.feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_COVARIANT).append[oneSpace]; }
- // "in"
- if( tv.declaredContravariant ) {tv.regionFor.feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_CONTRAVARIANT).append[oneSpace];}
-
- val upperBoundNode = tv.declaredUpperBoundNode;
- if( upperBoundNode!==null ) {
- // "extends"
- tv.regionFor.keyword("extends").surround[oneSpace];
- upperBoundNode.immediatelyFollowing.keyword("&").surround[oneSpace];
- upperBoundNode.format(document);
- }
-
- }
-
-
-
- def dispatch void format(Expression exp, extension IFormattableDocument document) {
- switch(exp) {
- // Things not to format:
- BooleanLiteral,
- IdentifierRef,
- IntLiteral,
- NullLiteral,
- NumberLiteral,
- RegularExpressionLiteral,
- StringLiteral,
- ThisLiteral,
- SuperLiteral,
- JSXElement
- : return
- }
- throw new UnsupportedOperationException("expression "+exp.class.simpleName+" not yet implemented.");
- }
-
- /** simply formats all content */
- def void genericFormat(Expression exp, extension IFormattableDocument document) {
- exp.eContents.forEach[format];
- }
-
-
- def dispatch void format(AssignmentExpression ass, extension IFormattableDocument document) {
- ass.lhs.append[oneSpace]
- ass.rhs.prepend[oneSpace]
- ass.lhs.format;
- ass.rhs.format;
- }
-
- def dispatch void format( ExpressionStatement eStmt, extension IFormattableDocument docuemt){
- eStmt.expression.format;
- }
-
- /** var,let,const */
- def dispatch void format(VariableStatement vStmt, extension IFormattableDocument document) {
-
- vStmt.configureModifiers(document);
-
- vStmt.regionFor.feature(
- N4JSPackage.Literals.VARIABLE_DECLARATION_CONTAINER__VAR_STMT_KEYWORD).append [
- oneSpace;
- ]; // "let", "var" or "const"
-
- vStmt.configureCommas(document);
-
- vStmt.interiorBUGFIX([indent],document); //vStmt.interior[indent];
- val lastIdx = vStmt.varDeclsOrBindings.size - 1;
-
- vStmt.varDeclsOrBindings.forEach [ e, int i |
- e.format;
- if (i > 0) { // assignments start in separate lines.
- if (e instanceof VariableDeclaration) {
- if (e.expression !== null) e.prepend[newLine]
- else e.prepend[setNewLines(0,1,1); lowPriority];
- } else if (e instanceof VariableBinding) {
- if (e.expression !== null) e.prepend[newLine]
- else e.prepend[setNewLines(0,1,1); lowPriority];
- }
- }
- if (i < lastIdx) { // assignments start let following continue in separate lines.
- if (e instanceof VariableDeclaration) {
- if (e.expression !== null) e.immediatelyFollowing.keyword(",").append[newLine]
- else e.prepend[setNewLines(0,1,1); lowPriority];
- } else if (e instanceof VariableBinding) {
- if (e.expression !== null) e.immediatelyFollowing.keyword(",").append[newLine]
- else e.prepend[setNewLines(0,1,1); lowPriority];
- }
-
- }
- ];
- }
-
- def dispatch void format(VariableDeclaration vDecl, extension IFormattableDocument document) {
- vDecl.previousHiddenRegion.set[oneSpace];
- vDecl.regionFor.keyword("=").surround[oneSpace];
- vDecl.expression.format;
- vDecl.declaredTypeRefInAST.format;
- }
-
- def dispatch void format(VariableBinding vBind, extension IFormattableDocument document) {
- vBind.previousHiddenRegion.set[oneSpace];
- vBind.regionFor.keyword("=").surround[oneSpace];
- vBind.pattern.format;
- vBind.expression.format;
- vBind.pattern.format;
- }
-
- def dispatch void format( BindingPattern bp, extension IFormattableDocument document) {
- // ObjectBindingPattern
- // ArrayBindingPattern
-
- // '{' or '['
- bp.semanticRegions.head.append[noSpace;newLines=0;autowrap];
- bp.semanticRegions.last.prepend[noSpace;newLines=0;autowrap];
- bp.configureCommas(document); // doesn't handle elision.
-
- bp.eContents.forEach[format];
- }
-
-
- def dispatch void format(ThrowStatement thrStmt, extension IFormattableDocument document) {
- thrStmt.expression.prepend[setNewLines(0, 0, 0); oneSpace]; // No autowrap, otherwise ASI
- thrStmt.expression.format;
- }
-
-
- def dispatch void format(CatchBlock ctch, extension IFormattableDocument document) {
- ctch.prepend[setNewLines(0, 0, 0); oneSpace];
- ctch.catchVariable.format;
- ctch.block.format;
- }
-
- def dispatch void format(FinallyBlock finlly, extension IFormattableDocument document) {
- finlly.previousHiddenRegion.set[newLines = 0; oneSpace];
- finlly.block.format;
- }
-
- /** Insert one space in front of first '{' in the direct content of the element.
- * semEObject is a semanticObject, e.g. N4EnumDecl, N4Classifier ...*/
- private def void insertSpaceInFrontOfCurlyBlockOpener(EObject semEObject, extension IFormattableDocument document) {
- semEObject.regionFor.keyword("{").prepend[oneSpace];
- }
-
- /** force: " @" and no newLine after '@' */
- private def void noSpaceAfterAT(EObject semEObject, extension IFormattableDocument document) {
- semEObject.regionFor.keyword("@").append[noSpace;newLines=0].prepend[oneSpace];
- }
-
- /** 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];
- ];
- }
-
- def void indentExcludingAnnotations(EObject semObject, extension IFormattableDocument document) {
- //Exclude Annotations from indentation field.interior[indent];
- val begin = semObject.semanticRegions.findFirst[!(semanticElement instanceof Annotation)];
- val end = semObject.semanticRegions.last;
- if( begin !== end ) { // guard to prevent wrong indentation
- interior(begin,end,[indent]);
- }
- }
-
-
- private def dispatch void configureAnnotations(AnnotableN4MemberDeclaration semEObject, extension IFormattableDocument document) {
- configureAnnotations( semEObject.annotationList, document );
- }
-
- private def dispatch void configureAnnotations(AnnotablePropertyAssignment semEObject, extension IFormattableDocument document) {
- configureAnnotations( semEObject.annotationList, document );
- }
-
- private def dispatch void configureAnnotations(AnnotableScriptElement semEObject, extension IFormattableDocument document) {
- configureAnnotations( semEObject.annotationList, document );
- }
-
- private def dispatch void configureAnnotations(AnnotableExpression semEObject, extension IFormattableDocument document) {
- configureAnnotations( semEObject.annotationList, document );
- }
-
- private def dispatch void configureAnnotations(AbstractAnnotationList aList, extension IFormattableDocument document) {
- if( aList === null || aList.annotations.isEmpty ) return;
-
- aList.prepend[setNewLines(2,2,2);highPriority]; // TODO in case of trapped in Annotation like 'export @Final public class A{}' - a reorder would be necessary (see format for export)
- aList.append[newLine]; // TODO special annotations like @Internal ? --> together with public, reorder to be in same line?
- aList.annotations.forEach[it, idx |
- it.configureAnnotation(document,true,idx ===0);
- ];
- }
-
- /**
- *
- * @param withLineWraps true
do line-wrapping
- * @param isFirstenAnnotation if this is the first annotation in a sequence ( used with line-wrapping in front of '@')
- */
- private def configureAnnotation(Annotation it, extension IFormattableDocument document, boolean withLineWraps, boolean isFirstAnnotation ){
- // configure arguments
- val parens = it.regionFor.keywordPairs("(",")").head;
- if( parens !== null ) {
- parens=>[
- it.key.prepend[noSpace].append[noSpace];
- it.value.prepend[noSpace].append[if( withLineWraps ) {noSpace; newLines=1;} else {oneSpace; newLines=0;}];
- it.interior[indent];
- // line break before "@":
- if( withLineWraps && !isFirstAnnotation ) {
- it.key.previousSemanticRegion.previousSemanticRegion.prepend[newLines = 1];
- }
- ];
- it.configureCommas(document);
- }
-
- // Configure @-Syntax
- // Special case here: for "@XY" we can either get "@" or "XY" as the first semantic element
- it.semanticRegions.head =>[
- if( it.grammarElement instanceof Keyword) {
- // assume '@'
- it.append[ noSpace; newLines=0 ];
- } else {
- it.prepend[ // for "@Final" "Final" will be the first semantic region in case of exported classes,
- noSpace; newLines=0
- ];
- }
- ];
- }
-
- private def dispatch void configureAnnotations(Object semEObject, extension IFormattableDocument document) {
- // no annotations to be configured.
- }
-
- private def dispatch void configureAnnotations(Void x, extension IFormattableDocument document) {
- // no annotations to be configured.
- }
-
- private def void configureAnnotationsInLine(FormalParameter fpar, extension IFormattableDocument document) {
- if( fpar.annotations.isEmpty ) return;
- // (@x @y("") bogus a:typ)
- fpar.annotations.head=>[
- it.configureAnnotation(document,false,true);
- prepend[noSpace;newLines=0;autowrap;];
- ]
- fpar.annotations.tail.forEach[
- configureAnnotation(document,false,false);
- prepend[oneSpace;newLines=0;autowrap;];
- ]
- fpar.annotations.last.append[oneSpace;newLines=0;autowrap;];
- }
-
-
- /** only script-level annotations '@@' */
- private def void formatScriptAnnotations(Script script, extension IFormattableDocument document) {
- if( script.annotations.isEmpty ) return;
-
- if (script.annotations.head.previousHiddenRegion.containsComment) {
- script.annotations.head.prepend[noSpace;];
- } else {
- script.annotations.head.prepend[noSpace;newLines=0;];
- }
- script.annotations.last.append[setNewLines(2,2,2)];
-
- script.annotations.forEach[it,idx|
- if( idx !==0 ) it.prepend[newLines=1;noSpace];
- it.semanticRegions.head=>[ // its an '@@'
- append[noSpace;newLines=0]
- ]
- ]
-
- }
-
-
-
-
- public override ITextReplacer createCommentReplacer(IComment comment) {
- // Overridden to distinguish between JSDOC-style, standard ML, formatter-off ML-comment.
- val EObject grammarElement = comment.getGrammarElement();
- if (grammarElement instanceof AbstractRule) {
- val String ruleName = (/*(AbstractRule)*/ grammarElement).getName();
- if (ruleName.startsWith("ML")) {
- val cText = comment.text;
- if (cText.startsWith("/**") && !cText.startsWith("/***")) { // JSDOC
- return new N4MultilineCommentReplacer(comment, '*');
- } else if (cText.startsWith("/*-")) { // Turn-off formatting.
- return new OffMultilineCommentReplacer(comment, !comment.isNotFirstInLine);
- } else { // All other
- return new FixedMultilineCommentReplacer(comment);
- }
- }
- if (ruleName.startsWith("SL")) {
- if (comment.isNotFirstInLine) {
- return new SinglelineDocCommentReplacer(comment, "//");
- } else {
- return new SinglelineCodeCommentReplacer(comment, "//");
- }
- }
- }
-
- // fall back to super-impl.
- super.createCommentReplacer(comment);
- }
-
- private static def boolean isNotFirstInLine(IComment comment) {
- val lineRegion = comment.getLineRegions().get(0);
-
- return !comment.contains( lineRegion.offset );
- }
-
-
- public override createTextReplacerMerger(){
- return new IndentHandlingTextReplaceMerger(this);
- }
-
- /** DEBUG-helper */
- private def static String containmentStructure(EObject eo) {
- val name = eo.class.simpleName;
- if( eo.eContainer !== null )
- return '''«eo.eContainer.containmentStructure».«eo.eContainingFeature.name»-> «name»'''
- return name
- }
-
- /** HELPER to avoid Warnings in code, since @SuppressWarnings("unused") is not active in xtend code.*/
- def suppressUnusedWarnings(Object ... e)
- {
- PRIO_4;
- }
-
- /**
- * Simple tracker that only gives exactly one time the value {@code true}
- * when calling {@link StateTrack#shouldDoThenDone()}
- */
- private final static class StateTrack {
- private boolean done=false;
-
- /**
- * This method returns {@code true} exactly on it's first invocation. Proceeding calls always return {@code false}.
- *
- * @return Returns {@code true} if not done, immediately switches {@link #done} to {@code true};
- * returns {@code false} if already done.
- */
- def boolean shouldDoThenDone(){
- val ret = !done;
- done = true;
- return ret;
- }
- }
-
-
- /****************************************************************************************************************
- *
- * Type Expression
- *
- ***************************************************************************************************************/
- def dispatch void format(UnionTypeExpression ute, extension IFormattableDocument document) {
- ute.regionFor.keywords("|").forEach[ surround[oneSpace;newLines=0].prepend[autowrap;highPriority] ];
- ute.typeRefs.forEach[format];
- // OLD syntax:
- val kwUnion = ute.regionFor.keyword("union");
- if( kwUnion !== null ) {
- kwUnion.prepend[oneSpace].append[oneSpace;newLines=0].nextSemanticRegion/*'{'*/.append[oneSpace;newLines=0];
- ute.semanticRegions.last/*'}'*/.prepend[oneSpace;newLines=0];
- }
- }
-
- def dispatch void format(IntersectionTypeExpression ite, extension IFormattableDocument document) {
- ite.regionFor.keywords("&").forEach[surround[oneSpace;newLines=0].prepend[autowrap;highPriority]];
- ite.typeRefs.forEach[format];
- // OLD syntax
- val kwInersection = ite.regionFor.keyword("intersection");
- if( kwInersection !== null ) {
- kwInersection.prepend[oneSpace].append[oneSpace;newLines=0].nextSemanticRegion/*'{'*/.append[oneSpace;newLines=0];
- ite.semanticRegions.last/*'}'*/.prepend[oneSpace;newLines=0];
- }
- }
-
- def dispatch void format( TStructMember tsm, extension IFormattableDocument document) {
- if(tsm instanceof TField) {
- tsm.configureOptionality(document);
- } else if(tsm instanceof org.eclipse.n4js.ts.types.FieldAccessor) {
- tsm.configureGetSetKeyword(document);
- tsm.configureOptionality(document);
-
- val parenPair = tsm.regionFor.keywordPairs("(",")").head;
- parenPair.key.prepend[noSpace; newLines = 0].append[noSpace];
- }
- // get, set, method, field
- tsm.eContents.forEach[format;];
- // TODO format TStruct* more thoroughly
- // (note: added some TStructMember formatting while working on IDE-2405, but it is still incomplete!)
- }
-
-
-
- def private void configureUndefModifier( StaticBaseTypeRef sbtr, extension IFormattableDocument document){
- // UndefModifier "?"
- sbtr.regionFor.feature(TypeRefsPackage.Literals.TYPE_REF__FOLLOWED_BY_QUESTION_MARK).prepend[noSpace;newLines=0;];
- }
-
- def dispatch void format( ThisTypeRef ttr, extension IFormattableDocument document) {
- ttr.configureUndefModifier(document);
- if( ttr instanceof ThisTypeRefStructural) {
- ttr.interiorBUGFIX([indent],document)
- configureStructuralAdditions(ttr,document);
- ttr.eContents.forEach[
- format;
- ]
- }
- }
-
- def dispatch void format( ParameterizedTypeRef ptr, extension IFormattableDocument document) {
- ptr.interiorBUGFIX([indent],document); //ptr.interior[indent];
- ptr.configureUndefModifier(document);
-
- // Union / Intersection
- ptr.regionFor.keywords("&","|").forEach[ surround[oneSpace;newLines=0].append[autowrap;highPriority]];
- // Short-Hand Syntax for Arrays
- if( ptr.isArrayTypeExpression ) {
- ptr.regionFor.keyword("[").append[noSpace];
- ptr.regionFor.keyword("]").append[noSpace];
- }
- // Short-Hand Syntax for IterableN
- if( ptr.isArrayNTypeExpression ) {
- ptr.regionFor.keyword("[").append[noSpace];
- ptr.regionFor.keyword("]").append[noSpace];
- }
- ptr.formatTypeArguments(document);
-
- // ParameterizedTypeRefStructural :
- configureStructuralAdditions(ptr,document);
-
- // generically format content:
- ptr.eContents.forEach[format]
- }
-
- /** used for "~X with {}" except for the 'X' part. */
- def void configureStructuralAdditions( TypeRef ptr, extension IFormattableDocument document) {
- val semRegTypingStrategy = ptr.regionFor.ruleCallTo(typingStrategyUseSiteOperatorRule);
- if( semRegTypingStrategy!== null) {
- semRegTypingStrategy.prepend[oneSpace].append[noSpace;newLines=0;];
-
- // declaredType
- semRegTypingStrategy.nextSemanticRegion.append[];
-
- val kwWith = ptr.regionFor.keyword("with");
- if( kwWith !== null ) {
- kwWith.surround[oneSpace;newLines=0;autowrap];
- val bracesPair = ptr.regionFor.keywordPairs("{","}").head;
- bracesPair.key.append[noSpace;newLines=0;autowrap];
- bracesPair.value.prepend[noSpace;newLines=0;autowrap];
- //ptr.regionFor.keywords(",",";").forEach[ prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap] ]
- ptr.regionFor.keywords(";").forEach[ prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap;lowPriority] ]
- ((ptr as StructuralTypeRef).astStructuralMembers.tail
- .forEach[ regionForEObject.previousHiddenRegion.set[oneSpace;newLines=0;autowrap]]
- );
- }
- }
- }
-
- /** formats type argument section including outside border. */
- def void formatTypeArguments(ParameterizedTypeRef semObject, extension IFormattableDocument document) {
- if( semObject.declaredTypeArgs.isEmpty ) return;
- // to "<":
- semObject.regionFor.keyword("<").append[noSpace].prepend[noSpace; newLines=0; lowPriority];
- semObject.regionFor.keyword(">").prepend[noSpace].append[noSpace; newLines=0; lowPriority];
- for( typeArg: semObject.declaredTypeArgs ){
- typeArg.append[noSpace].immediatelyFollowing.keyword(",").append[oneSpace];
- typeArg.format(document);
- }
-
- }
-
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Configure Methods
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- def private void configureGetSetKeyword(FieldAccessor fieldAccessor, extension IFormattableDocument document) {
- val kw = if(fieldAccessor instanceof GetterDeclaration) "get" else "set";
- fieldAccessor.regionFor.keyword(kw).prepend[oneSpace].append[oneSpace; newLines=0; autowrap];
- }
- def private void configureGetSetKeyword(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, extension IFormattableDocument document) {
- val kw = if(tFieldAccessor instanceof TGetter) "get" else "set";
- tFieldAccessor.regionFor.keyword(kw).prepend[oneSpace].append[oneSpace; newLines=0; autowrap];
- }
-
- def private void configureOptionality(N4FieldDeclaration fieldDecl, extension IFormattableDocument document) {
- fieldDecl.regionFor.feature(N4JSPackage.Literals.N4_FIELD_DECLARATION__DECLARED_OPTIONAL).prepend[noSpace;newLines=0;];
- }
- def private void configureOptionality(FieldAccessor fieldAccessor, extension IFormattableDocument document) {
- fieldAccessor.regionFor.feature(N4JSPackage.Literals.FIELD_ACCESSOR__DECLARED_OPTIONAL).prepend[noSpace;newLines=0;];
- }
- def private void configureOptionality(TField tField, extension IFormattableDocument document) {
- tField.regionFor.feature(TypesPackage.Literals.TFIELD__OPTIONAL).prepend[noSpace;newLines=0;];
- }
- def private void configureOptionality(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, extension IFormattableDocument document) {
- tFieldAccessor.regionFor.feature(TypesPackage.Literals.FIELD_ACCESSOR__OPTIONAL).prepend[noSpace;newLines=0;];
- }
-
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Bug-workarounds
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /** Temporarily used method to replace document.interior(EObject, Procedure1) to prevent wrong indentations.
- *
- * Main pattern replace document-extension method call:
- *
- * object.interior[indent]
- *
- * by
- *
- * object.interiorBUGFIX([indent],document);
- *
- *
- */
- def void interiorBUGFIX(EObject object, Procedure1 super IHiddenRegionFormatter> init,IFormattableDocument document ){
- val IEObjectRegion objRegion = getTextRegionAccess().regionForEObject(object);
- if (objRegion !== null) {
- val IHiddenRegion previous = objRegion.getPreviousHiddenRegion();
- val IHiddenRegion next = objRegion.getNextHiddenRegion();
- if (previous !== null && next !== null && previous != next) {
- val nsr = previous.getNextSemanticRegion();
- val psr = next.getPreviousSemanticRegion();
- if( nsr != psr )
- { // standard case
- document.interior(nsr, psr , init);
- } else {
- // former error-case:
- // there is no interior --> don't do anything!
- //
- // applying to the next HiddenRegion is a bad idea,
- // since it could wrongly indent a multiline-comment (c.f. GHOLD-260)
- }
- }
- }
- }
-
-
- /** Dummy method to prevent accidentally calling interior - extension method form document. You should call interiorBUGFIX instead ! */
- def Procedure1 super IFormattableDocument> interior( EObject eo, Procedure1 super IHiddenRegionFormatter> init ){
- throw new IllegalStateException("Method should not be called.")
- }
-
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.java
new file mode 100644
index 0000000000..ac9d39dff7
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.java
@@ -0,0 +1,42 @@
+/**
+ * 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.formatting2;
+
+import org.eclipse.xtext.formatting2.FormatterPreferenceKeys;
+import org.eclipse.xtext.preferences.BooleanKey;
+import org.eclipse.xtext.preferences.IntegerKey;
+
+/**
+ *
+ */
+public class N4JSFormatterPreferenceKeys extends FormatterPreferenceKeys {
+ /***/
+ public static BooleanKey FORMAT_PARENTHESIS = new BooleanKey("format.parenthesis", false);
+ /***/
+ public static BooleanKey FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE = new BooleanKey(
+ "format.surround_paren_content_with_space", false);
+ /***/
+ public static IntegerKey FORMAT_MAX_CONSECUTIVE_NEWLINES = new IntegerKey("format.max_consecutive_newlines", 2);
+ /***/
+ public static BooleanKey FORMAT_SWITCH_CASES_HAVE_SPACE_IN_FRONT_OF_COLON = new BooleanKey(
+ "format.switch_cases_have_space_in_front_of_colon", false);
+ /***/
+ public static BooleanKey FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR = new BooleanKey(
+ "format.auto_wrap_in_front_of_logical_operator", true);
+ /**
+ * Considering the code import a, {b,c,d} from "xy";
a value of true
will render an
+ * additional space after "{" and one before the closing bracket "}" Default value is false
and the
+ * line will be rendered as above.
+ */
+ public static BooleanKey FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE = new BooleanKey(
+ "format.surround_import_list_with_space", false);
+
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.xtend
deleted file mode 100644
index c14a9a65f7..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.xtend
+++ /dev/null
@@ -1,31 +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.formatting2
-
-import org.eclipse.xtext.formatting2.FormatterPreferenceKeys
-import org.eclipse.xtext.preferences.BooleanKey
-import org.eclipse.xtext.preferences.IntegerKey
-
-/**
- *
- */
-class N4JSFormatterPreferenceKeys extends FormatterPreferenceKeys {
- public static val BooleanKey FORMAT_PARENTHESIS = new BooleanKey("format.parenthesis", false);
- public static val BooleanKey FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE = new BooleanKey("format.surround_paren_content_with_space", false);
- public static val IntegerKey FORMAT_MAX_CONSECUTIVE_NEWLINES = new IntegerKey("format.max_consecutive_newlines",2);
- public static val BooleanKey FORMAT_SWITCH_CASES_HAVE_SPACE_IN_FRONT_OF_COLON = new BooleanKey("format.switch_cases_have_space_in_front_of_colon", false);
- public static val BooleanKey FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR= new BooleanKey("format.auto_wrap_in_front_of_logical_operator", true);
- /** Considering the code import a, {b,c,d} from "xy";
a value of true
will render an additional space after "{" and one before the closing bracket "}"
- * Default value is false
and the line will be rendered as above.*/
- public static val BooleanKey FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE = new BooleanKey("format.surround_import_list_with_space", false);
-
-
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java
new file mode 100644
index 0000000000..17560c88f9
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java
@@ -0,0 +1,368 @@
+/**
+ * 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.formatting2;
+
+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.groupBy;
+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.sortBy;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.log4j.Logger;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.services.N4JSGrammarAccess;
+import org.eclipse.xtend.lib.annotations.Accessors;
+import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor;
+import org.eclipse.xtext.RuleCall;
+import org.eclipse.xtext.formatting2.AbstractFormatter2;
+import org.eclipse.xtext.formatting2.IFormattableDocument;
+import org.eclipse.xtext.formatting2.IHiddenRegionFormatting;
+import org.eclipse.xtext.formatting2.ITextReplacer;
+import org.eclipse.xtext.formatting2.ITextReplacerContext;
+import org.eclipse.xtext.formatting2.internal.HiddenRegionReplacer;
+import org.eclipse.xtext.formatting2.internal.TextReplacerMerger;
+import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions;
+import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
+import org.eclipse.xtext.xbase.lib.Pair;
+
+/**
+ *
+ */
+@SuppressWarnings("restriction")
+@FinalFieldsConstructor
+class N4JSGenericFormatter {
+
+ N4JSGrammarAccess grammarAccess;
+ ITextRegionExtensions trExtensions;
+
+ public static int PRIO_1 = -10;
+ public static int PRIO_2 = -9;
+ public static int PRIO_3 = -8;
+
+ public N4JSGenericFormatter(N4JSGrammarAccess grammarAccess, ITextRegionExtensions trExtensions) {
+ this.grammarAccess = grammarAccess;
+ this.trExtensions = trExtensions;
+ }
+
+ public void formatColon(EObject semanticElement, IFormattableDocument document) {
+ for (ISemanticRegion colon : trExtensions.allRegionsFor(semanticElement).keywords(":")) {
+ ISemanticRegion sr = document.prepend(colon, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ hrf.setPriority(PRIO_3);
+ });
+ document.append(sr, hrf -> {
+ hrf.oneSpace();
+ hrf.setPriority(PRIO_2);
+ });
+ }
+ }
+
+ /**
+ * Formats whitespace around already present semicolons (;) and inserts new semicolons where the parser expects
+ * them.
+ */
+ public void formatSemicolons(EObject script, IFormattableDocument document) {
+ for (ISemanticRegion region : trExtensions.allRegionsFor(script).ruleCallsTo(grammarAccess.getSemiRule())) {
+ String text = region.getText();
+ IHiddenRegion previous = region.getPreviousHiddenRegion();
+ if (text == ";") {
+
+ // there is already a ";" so let's format it
+ document.prepend(region, hrf -> {
+ hrf.noSpace();
+ hrf.setNewLines(0);
+ hrf.highPriority();
+ });
+ } else if (region.getNextSemanticRegion() != null && "}".equals(region.getNextSemanticRegion().getText())
+ && !region.isMultiline()) {
+ // do nothing
+ } else if (previous.containsComment()) {
+
+ // we're behind a comment - insert semicolon before the comment
+ ITextSegment insertAt = region.getTextRegionAccess().regionForOffset(previous.getOffset(), 0);
+ document.addReplacer(new InsertSemi(insertAt, ";"));
+ } else if (text.trim().isEmpty()) {
+ // Don't eat up white space here.
+ // Look for first line break and replace with ";\n":
+ int lbIdx = text.indexOf("\n");
+ if (lbIdx >= 0) {
+ ITextSegment replaceRegion = region.getTextRegionAccess().regionForOffset(region.getOffset(),
+ lbIdx + 1);
+ document.addReplacer(new InsertSemi(replaceRegion, ";\n"));
+ } else {
+ // the text region only contains whitespace, e.g. so let's insert a ; and a "\n"
+ document.addReplacer(new InsertSemi(region, ";"));
+ }
+ } else {
+
+ // we probably are the comment, so let's prefix it with ;
+ ITextSegment insertAt = region.getTextRegionAccess().regionForOffset(region.getOffset(), 0);
+ document.addReplacer(new InsertSemi(insertAt, ";"));
+ }
+ }
+ }
+
+ /**
+ * Format whitespace around (), [], and {}
+ *
+ * When multiple pairs of (), [], or {} open in the same line, indentation is only applied for the innermost pair.
+ */
+ public void formatParenthesisBracketsAndBraces(EObject script, IFormattableDocument document) {
+ List> all = new ArrayList<>();
+ all.addAll(trExtensions.allRegionsFor(script).keywordPairs("(", ")"));
+ all.addAll(trExtensions.allRegionsFor(script).keywordPairs("{", "}"));
+ all.addAll(trExtensions.allRegionsFor(script).keywordPairs("[", "]"));
+
+ Map>> byLine = groupBy(all,
+ p -> p.getKey().getLineRegions().get(0).getOffset());
+
+ for (Entry>> e : byLine.entrySet()) {
+ List> bracePairsInSameLine = sortBy(e.getValue(),
+ p -> p.getKey().getOffset());
+ Pair outermost = bracePairsInSameLine.get(0);
+ Pair innermost = last(bracePairsInSameLine);
+
+ // keep track of lastOpen/lastClose to avoid formatting the HiddenRegion twice if that hidden region
+ // is located directly between two brackets. Then, first.append[] would collide with second.prepend[].
+ IHiddenRegion lastOpen = null;
+ IHiddenRegion lastClose = null;
+ for (Pair pair : bracePairsInSameLine) {
+ ISemanticRegion open = pair.getKey();
+ ISemanticRegion close = pair.getValue();
+ if (open.getPreviousHiddenRegion() != lastOpen && lastOpen != null) {
+ // something between last opening and this one; null-check for first opening-> don't force noSpace
+ // here
+ document.prepend(open, hrf -> {
+ hrf.noSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ }
+ if (pair != innermost) {
+ document.append(open, hrf -> {
+ hrf.noSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ document.prepend(close, hrf -> {
+ hrf.noSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ }
+ if (close.getNextHiddenRegion() != lastClose) {
+ if (pair == outermost) {
+ // close.appendNewLine(document)
+ } else {
+ document.append(close, hrf -> {
+ hrf.noSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ }
+ }
+ lastOpen = open.getNextHiddenRegion();
+ lastClose = close.getPreviousHiddenRegion();
+ }
+
+ ISemanticRegion open = innermost.getKey();
+ ISemanticRegion close = innermost.getValue();
+ if (open.getNextSemanticRegion() == close && !open.getNextHiddenRegion().isMultiline()) { // empty
+ // brace-pair
+ document.append(open, hrf -> {
+ hrf.noSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ } // otherwise, if there is a newline before the innermost closing bracket, we want to format the surrounded
+ // tokens multiline style.
+ else if (close.getPreviousHiddenRegion().isMultiline()) {
+ appendNewLine(document.prepend(close, hrf -> {
+ hrf.newLine();
+ hrf.setPriority(PRIO_1);
+ }), document);
+ document.append(open, hrf -> {
+ hrf.newLine();
+ hrf.setPriority(PRIO_1);
+ });
+ document.interior(innermost, hrf -> hrf.indent());
+ for (ISemanticRegion comma : trExtensions.regionFor(open.getSemanticElement()).keywords(",")) {
+ // FIXME: bug in toString: println(comma.toString);
+ ISemanticRegion sr = document.prepend(comma, hrf -> {
+ hrf.noSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ document.append(sr, hfr -> {
+ // If dangling comma, then a conflict arises with new line of close.
+ // Avoid here by using Prio_2
+ hfr.setNewLines(1, 1, 2);
+ hfr.setPriority(PRIO_2);
+ });
+ }
+ } // otherwise, format the tokens into a single line.
+ else {
+ if (document.getRequest().getPreferences()
+ .getPreference(N4JSFormatterPreferenceKeys.FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE)) {
+ // configured way to have single space in parenthesised expression.
+ document.prepend(close, hrf -> {
+ hrf.oneSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ document.append(open, hrf -> {
+ hrf.oneSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ }
+ for (ISemanticRegion comma : trExtensions.regionFor(open.getSemanticElement()).keywords(",")) {
+ ISemanticRegion sr = document.prepend(comma, hrf -> {
+ hrf.noSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ document.append(sr, hrf -> {
+ hrf.oneSpace();
+ hrf.setPriority(PRIO_1);
+ });
+ }
+ }
+ }
+ }
+
+ public ISemanticRegion appendNewLine(ISemanticRegion appendAfter, IFormattableDocument doc) {
+ EObject semi = appendAfter.getNextSemanticRegion() == null ? null
+ : appendAfter.getNextSemanticRegion().getGrammarElement();
+ if (semi instanceof RuleCall && ((RuleCall) semi).getRule() == grammarAccess.getSemiRule()) {
+ // noop, handled by org.eclipse.n4js.formatting2.N4JSGenericFormatter.formatSemicolons(EObject,
+ // IFormattableDocument)
+ } else {
+ doc.append(appendAfter, hrf -> {
+ hrf.newLine();
+ hrf.setPriority(PRIO_1);
+ });
+ }
+ return appendAfter;
+ }
+
+ static interface InsertSemiBase extends ITextReplacer {
+ // marker interface
+ }
+
+ @Accessors
+ static class InsertSemi implements InsertSemiBase {
+ ITextSegment region;
+ String text;
+
+ public InsertSemi(ITextSegment region, String text) {
+ this.region = region;
+ this.text = text;
+ }
+
+ @Override
+ public ITextReplacerContext createReplacements(ITextReplacerContext context) {
+ context.addReplacement(region.replaceWith(text));
+ return context;
+ }
+
+ @Override
+ public ITextSegment getRegion() {
+ return this.region;
+ }
+
+ public String getText() {
+ return this.text;
+ }
+ }
+
+ static class InsertSemiFollowedByTextReplacer implements InsertSemiBase {
+ InsertSemiBase insertSemi;
+ ITextReplacer textReplacer;
+ ITextSegment region;
+
+ InsertSemiFollowedByTextReplacer(InsertSemiBase insertSemi, ITextReplacer textReplacer) {
+ this.insertSemi = insertSemi;
+ this.textReplacer = textReplacer;
+ this.region = insertSemi.getRegion().merge(textReplacer.getRegion());
+ }
+
+ @Override
+ public ITextReplacerContext createReplacements(ITextReplacerContext context) {
+ // First insert semicolon
+ ITextReplacerContext replContext = insertSemi.createReplacements(context).withReplacer(textReplacer);
+ // Then apply the text replacer
+ return textReplacer.createReplacements(replContext);
+ }
+
+ @Override
+ public ITextSegment getRegion() {
+ return this.region;
+ }
+ }
+
+ static class IndentHandlingTextReplaceMerger extends TextReplacerMerger {
+ private final static Logger LOGGER = Logger.getLogger(IndentHandlingTextReplaceMerger.class);
+
+ AbstractFormatter2 fmt;
+
+ IndentHandlingTextReplaceMerger(AbstractFormatter2 formatter) {
+ super(formatter);
+ fmt = formatter;
+ }
+
+ /**
+ * Overridden for special case of {@link InsertSemi} & {@link HiddenRegionReplacer} merging. Calls super
+ * implementation if no InsertSemi object is involved
+ */
+ @Override
+ public ITextReplacer merge(List extends ITextReplacer> conflicting) {
+ if (findFirst(conflicting, tr -> tr instanceof InsertSemiBase) == null) {
+ // standard case, but not handled as we want by super because due to ASI there can be
+ // HiddenRegionReplacer that have equal offsets and length but are not identical.
+ List hrf = toList(filter(conflicting, HiddenRegionReplacer.class));
+ if (hrf.size() == conflicting.size()) {
+ IHiddenRegionFormatting merged = fmt.createHiddenRegionFormattingMerger()
+ .merge(toList(map(hrf, hrr -> hrr.getFormatting())));
+ if (merged != null) {
+ return fmt.createHiddenRegionReplacer(hrf.get(0).getRegion(), merged);
+ }
+ }
+ return super.merge(conflicting);
+ }
+
+ // there is an insertSemi.
+ List extends ITextReplacer> semiReplacements = toList(
+ filter(conflicting, tr -> tr instanceof InsertSemiBase));
+ List extends ITextReplacer> otherReplacements = toList(
+ filter(conflicting, tr -> !(tr instanceof InsertSemiBase)));
+
+ if (semiReplacements.size() != 1 || otherReplacements.size() != 1) {
+ LOGGER.warn("""
+ Unhandled merge-case: "
+ "Semis replacer («semiReplacements.size») :«semiReplacements»
+ "Non-Semi replacer ( «otherReplacements.size» «otherReplacements»
+ """);
+ return null; // null creates merge Exception
+ }
+ // exactly one:
+ InsertSemiBase semiRepl = (InsertSemiBase) semiReplacements.get(0);
+ ITextReplacer otherRepl = otherReplacements.get(0);
+
+ if (otherRepl instanceof HiddenRegionReplacer) {
+ return new InsertSemiFollowedByTextReplacer(semiRepl, otherRepl);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend
deleted file mode 100644
index 886173e018..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend
+++ /dev/null
@@ -1,257 +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.formatting2
-
-import org.eclipse.n4js.services.N4JSGrammarAccess
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.xtend.lib.annotations.Accessors
-import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor
-import org.eclipse.xtext.RuleCall
-import org.eclipse.xtext.formatting2.IFormattableDocument
-import org.eclipse.xtext.formatting2.ITextReplacer
-import org.eclipse.xtext.formatting2.ITextReplacerContext
-import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion
-import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion
-import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions
-import org.eclipse.xtext.formatting2.regionaccess.ITextSegment
-import org.eclipse.xtext.formatting2.internal.TextReplacerMerger
-import org.eclipse.xtext.formatting2.AbstractFormatter2
-import java.util.List
-import org.eclipse.n4js.utils.Log
-import org.eclipse.xtext.formatting2.internal.HiddenRegionReplacer
-
-/**
- *
- */
-@FinalFieldsConstructor class N4JSGenericFormatter {
-
- val extension N4JSGrammarAccess
- val extension ITextRegionExtensions
-
- public static val PRIO_1 = -10
- public static val PRIO_2 = -9
- public static val PRIO_3 = -8
-
- def void formatColon(EObject semanticElement, extension IFormattableDocument document) {
- for (colon : semanticElement.allRegionsFor.keywords(":")) {
- colon.prepend[noSpace; newLines = 0; priority = PRIO_3]
- .append[oneSpace; priority = PRIO_2];
- }
- }
-
- /**
- * Formats whitespace around already present semicolons (;) and inserts new semicolons where the parser expects them.
- */
- def void formatSemicolons(EObject script, extension IFormattableDocument document) {
- for (region : script.allRegionsFor.ruleCallsTo(semiRule)) {
- val text = region.text;
- val previous = region.previousHiddenRegion;
- if (text == ";") {
-
- // there is already a ";" so let's format it
- region.prepend[noSpace; newLines = 0; highPriority];
- } else if(region.nextSemanticRegion?.text == "}" && !region.isMultiline){
- // do nothing
- }
- else if (previous.containsComment) {
-
- // we're behind a comment - insert semicolon before the comment
- val insertAt = region.textRegionAccess.regionForOffset(previous.offset, 0)
- document.addReplacer(new InsertSemi(insertAt, ";"));
- } else if (text.trim.isEmpty) {
- // Don't eat up white space here.
- // Look for first line break and replace with ";\n":
- val lbIdx = text.indexOf("\n");
- if( lbIdx >= 0 ) {
- val replaceRegion = region.textRegionAccess.regionForOffset(region.offset, lbIdx+1 );
- document.addReplacer(new InsertSemi(replaceRegion, ";\n"));
- } else {
- // the text region only contains whitespace, e.g. so let's insert a ; and a "\n"
- document.addReplacer(new InsertSemi(region, ";"));
- }
- } else {
-
- // we probably are the comment, so let's prefix it with ;
- val insertAt = region.textRegionAccess.regionForOffset(region.offset, 0);
- document.addReplacer(new InsertSemi(insertAt, ";"));
- }
- }
- }
-
- /**
- * Format whitespace around (), [], and {}
- *
- * When multiple pairs of (), [], or {} open in the same line, indentation is only applied for the innermost pair.
- */
- def void formatParenthesisBracketsAndBraces(EObject script, extension IFormattableDocument document) {
- val all = newArrayList()
- all += script.allRegionsFor.keywordPairs("(", ")")
- all += script.allRegionsFor.keywordPairs("{", "}")
- all += script.allRegionsFor.keywordPairs("[", "]")
-
- val byLine = all.groupBy[key.lineRegions.head.offset]
-
- for (e : byLine.entrySet) {
- val bracePairsInSameLine = e.value.sortBy[key.offset]
- val outermost = bracePairsInSameLine.head
- val innermost = bracePairsInSameLine.last
-
- // keep track of lastOpen/lastClose to avoid formatting the HiddenRegion twice if that hidden region
- // is located directly between two brackets. Then, first.append[] would collide with second.prepend[].
- var IHiddenRegion lastOpen = null
- var IHiddenRegion lastClose = null
- for (pair : bracePairsInSameLine) {
- val open = pair.key
- val close = pair.value
- if (open.previousHiddenRegion != lastOpen && lastOpen !== null) { // something between last opening and this one; null-check for first opening-> don't force noSpace here
- open.prepend[noSpace; priority = PRIO_1]
- }
- if (pair !== innermost) {
- open.append[noSpace; priority = PRIO_1]
- close.prepend[noSpace; priority = PRIO_1]
- }
- if (close.nextHiddenRegion != lastClose) {
- if (pair === outermost) {
-// close.appendNewLine(document)
- } else {
- close.append[noSpace; priority = PRIO_1]
- }
- }
- lastOpen = open.nextHiddenRegion
- lastClose = close.previousHiddenRegion
- }
-
- val ISemanticRegion open = innermost.key;
- val ISemanticRegion close = innermost.value;
- if (open.nextSemanticRegion == close && !open.nextHiddenRegion.isMultiline) { // empty brace-pair
- open.append[noSpace; priority = PRIO_1];
- } // otherwise, if there is a newline before the innermost closing bracket, we want to format the surrounded tokens multiline style.
- else if (close.previousHiddenRegion.isMultiline) {
- close.prepend[newLine; priority = PRIO_1].appendNewLine(document);
- open.append[newLine; priority = PRIO_1];
- innermost.interior[indent];
- for (comma : open.semanticElement.regionFor.keywords(",")) {
- // FIXME: bug in toString: println(comma.toString);
- comma.prepend[noSpace; priority = PRIO_1]
- .append[
- // If dangling comma, then a conflict arises with new line of close.
- // Avoid here by using Prio_2
- setNewLines(1,1,2);
- priority = PRIO_2
- ];
- }
- } // otherwise, format the tokens into a single line.
- else {
- if( document.request.preferences.getPreference( N4JSFormatterPreferenceKeys.FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE ) ) { // configured way to have single space in parenthesised expression.
- close.prepend[oneSpace; priority = PRIO_1]
- open.append[oneSpace; priority = PRIO_1]
- }
- for (comma : open.semanticElement.regionFor.keywords(",")) {
- comma.prepend[noSpace; priority = PRIO_1].append[oneSpace; priority = PRIO_1]
- }
- }
- }
- }
-
- def ISemanticRegion appendNewLine(ISemanticRegion appendAfter, extension IFormattableDocument doc) {
- val semi = appendAfter.nextSemanticRegion?.grammarElement
- if (semi instanceof RuleCall && (semi as RuleCall)?.rule == semiRule) {
- // noop, handled by org.eclipse.n4js.formatting2.N4JSGenericFormatter.formatSemicolons(EObject, IFormattableDocument)
- } else {
- appendAfter.append[newLine; priority = PRIO_1]
- }
- return appendAfter
- }
-}
-
-interface InsertSemiBase extends ITextReplacer {}
-
-@Accessors class InsertSemi implements InsertSemiBase {
- val ITextSegment region
- val String text
-
- override createReplacements(ITextReplacerContext context) {
- context.addReplacement(region.replaceWith(text))
- return context;
- }
-}
-
-class InsertSemiFollowedByTextReplacer implements InsertSemiBase {
- val InsertSemiBase insertSemi;
- val ITextReplacer textReplacer;
- val ITextSegment region;
-
- new(InsertSemiBase insertSemi, ITextReplacer textReplacer) {
- this.insertSemi = insertSemi;
- this.textReplacer = textReplacer;
- this.region = insertSemi.region.merge(textReplacer.region)
- }
-
- override createReplacements(ITextReplacerContext context) {
- // First insert semicolon
- var replContext = insertSemi.createReplacements(context).withReplacer(textReplacer);
- // Then apply the text replacer
- return textReplacer.createReplacements(replContext);
- }
-
- override getRegion() {
- return this.region;
- }
-}
-
-@Log
-class IndentHandlingTextReplaceMerger extends TextReplacerMerger {
- val AbstractFormatter2 fmt
-
- new(AbstractFormatter2 formatter) {
- super(formatter)
- fmt = formatter
- }
-
- /** Overridden for special case of {@link InsertSemi} & {@link HiddenRegionReplacer} merging.
- * Calls super implementation if no InsertSemi object is involved */
- override merge(List extends ITextReplacer> conflicting) {
- if(conflicting.findFirst[it instanceof InsertSemiBase] === null ) {
- // standard case, but not handled as we want by super because due to ASI there can be
- // HiddenRegionReplacer that have equal offsets and length but are not identical.
- val hrf = conflicting.filter(HiddenRegionReplacer).toList
- if(hrf.size === conflicting.size) {
- val merged = fmt.createHiddenRegionFormattingMerger.merge(hrf.map[formatting])
- if(merged !== null) {
- return fmt.createHiddenRegionReplacer(hrf.head.region, merged)
- }
- }
- return super.merge(conflicting);
- }
-
- // there is an insertSemi.
- val semiReplacements = conflicting.filter[it instanceof InsertSemiBase].toList;
- val otherReplacements = conflicting.filter[!(it instanceof InsertSemiBase)].toList;
-
- if( semiReplacements.size !== 1 || otherReplacements.size !== 1 ) {
- logger.warn( '''
- Unhandled merge-case: "
- "Semis replacer («semiReplacements.size») :«semiReplacements»
- "Non-Semi replacer ( «otherReplacements.size» «otherReplacements»
- ''');
- return null; // null creates merge Exception
- }
- // exactly one:
- val semiRepl = semiReplacements.get(0) as InsertSemiBase;
- val otherRepl = otherReplacements.get(0);
-
- if( otherRepl instanceof HiddenRegionReplacer ) {
- return new InsertSemiFollowedByTextReplacer(semiRepl, otherRepl);
- }
-
- return null;
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionFormatterNoOp.java
similarity index 61%
rename from plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.xtend
rename to plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionFormatterNoOp.java
index 5f3645ec9a..896e4444e5 100644
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.xtend
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionFormatterNoOp.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2016 NumberFour AG.
+ * Copyright (c) 2024 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
@@ -10,16 +10,15 @@
*/
package org.eclipse.n4js.formatting2;
-import org.eclipse.xtext.formatting2.AbstractFormatter2
-import org.eclipse.xtext.formatting2.IFormattableDocument
-
-abstract class TypeExpressionsFormatter extends AbstractFormatter2 {
-}
+import org.eclipse.xtext.formatting2.IFormattableDocument;
+/**
+ *
+ */
public class TypeExpressionFormatterNoOp extends TypeExpressionsFormatter {
- override format(Object obj, IFormattableDocument document) {
- throw new UnsupportedOperationException("TypeExpressionFormatter should not be used directly.")
+ @Override
+ public void format(Object obj, IFormattableDocument document) {
+ throw new UnsupportedOperationException("TypeExpressionFormatter should not be used directly.");
}
-
}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.java
similarity index 55%
rename from plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.xtend
rename to plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.java
index 7509a2c621..9ff8ad432f 100644
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.xtend
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.java
@@ -8,16 +8,10 @@
* Contributors:
* NumberFour AG - Initial API and implementation
*/
-package org.eclipse.n4js.resource
+package org.eclipse.n4js.formatting2;
-import org.eclipse.xtext.resource.DescriptionUtils
-import org.eclipse.xtext.resource.IResourceDescription
+import org.eclipse.xtext.formatting2.AbstractFormatter2;
-/**
- */
-class N4JSDescriptionUtils extends DescriptionUtils {
-
- override collectOutgoingReferences(IResourceDescription description) {
- newHashSet
- }
+abstract class TypeExpressionsFormatter extends AbstractFormatter2 {
+ // empty
}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.java
new file mode 100644
index 0000000000..c09948e05e
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.java
@@ -0,0 +1,539 @@
+/**
+ * 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.generator;
+
+import static org.eclipse.xtext.diagnostics.Severity.ERROR;
+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.toList;
+
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.eclipse.emf.common.EMFPlugin;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.n4js.N4JSGlobals;
+import org.eclipse.n4js.N4JSLanguageConstants;
+import org.eclipse.n4js.generator.IGeneratorMarkerSupport.Severity;
+import org.eclipse.n4js.n4JS.Script;
+import org.eclipse.n4js.packagejson.projectDescription.ProjectType;
+import org.eclipse.n4js.resource.N4JSCache;
+import org.eclipse.n4js.resource.N4JSResource;
+import org.eclipse.n4js.ts.types.TModule;
+import org.eclipse.n4js.utils.FolderContainmentHelper;
+import org.eclipse.n4js.utils.ResourceNameComputer;
+import org.eclipse.n4js.utils.ResourceType;
+import org.eclipse.n4js.utils.StaticPolyfillHelper;
+import org.eclipse.n4js.utils.URIUtils;
+import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot;
+import org.eclipse.n4js.workspace.N4JSSourceFolderSnapshot;
+import org.eclipse.n4js.workspace.N4JSWorkspaceConfigSnapshot;
+import org.eclipse.n4js.workspace.WorkspaceAccess;
+import org.eclipse.xtext.generator.AbstractFileSystemAccess;
+import org.eclipse.xtext.generator.IFileSystemAccess;
+import org.eclipse.xtext.generator.IFileSystemAccess2;
+import org.eclipse.xtext.generator.IGenerator2;
+import org.eclipse.xtext.generator.IGeneratorContext;
+import org.eclipse.xtext.generator.OutputConfiguration;
+import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
+import org.eclipse.xtext.service.OperationCanceledManager;
+import org.eclipse.xtext.util.CancelIndicator;
+import org.eclipse.xtext.util.UriExtensions;
+import org.eclipse.xtext.validation.CheckMode;
+import org.eclipse.xtext.validation.IResourceValidator;
+import org.eclipse.xtext.validation.Issue;
+
+import com.google.inject.Inject;
+
+/**
+ * All sub generators should extend this class. It provides basic blocks of the logic, and shared implementations.
+ */
+abstract public class AbstractSubGenerator implements ISubGenerator, IGenerator2 {
+ private final static Logger LOGGER = Logger.getLogger(AbstractSubGenerator.class);
+
+ private CompilerDescriptor compilerDescriptor = null;
+
+ /***/
+ @Inject
+ protected StaticPolyfillHelper staticPolyfillHelper;
+
+ /***/
+ @Inject
+ protected WorkspaceAccess workspaceAccess;
+
+ /***/
+ @Inject
+ protected ResourceNameComputer resourceNameComputer;
+
+ /***/
+ @Inject
+ protected IResourceValidator resVal;
+
+ /***/
+ @Inject
+ protected N4JSCache cache;
+
+ /***/
+ @Inject
+ protected IGeneratorMarkerSupport genMarkerSupport;
+
+ /***/
+ @Inject
+ protected OperationCanceledManager operationCanceledManager;
+
+ /***/
+ @Inject
+ protected GeneratorExceptionHandler exceptionHandler;
+
+ /***/
+ @Inject
+ protected N4JSPreferenceAccess preferenceAccess;
+
+ @Inject
+ private FolderContainmentHelper containmentHelper;
+
+ @Inject
+ private UriExtensions uriExtensions;
+
+ @Override
+ public CompilerDescriptor getCompilerDescriptor() {
+ if (compilerDescriptor == null) {
+ compilerDescriptor = getDefaultDescriptor();
+ }
+ return compilerDescriptor;
+ }
+
+ @Override
+ public void setCompilerDescriptor(CompilerDescriptor compilerDescriptor) {
+ this.compilerDescriptor = compilerDescriptor;
+ }
+
+ @Override
+ public void doGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
+ doGenerate(input, fsa);
+ }
+
+ @Override
+ public void beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
+ // TODO Auto-generated method stub
+
+ }
+
+ /**
+ * This override declares an {@link IFileSystemAccess} parameter. At runtime, its actual type depends on whether IDE
+ * or headless, which in turn determines whether the actual argument has a progress monitor or not:
+ *
+ * - IDE scenario: Actual type is {@code EclipseResourceFileSystemAccess2}. A progress monitor can be obtained via
+ * {@code getMonitor()}. It is checked automatically behind the scenes, for example in {@code generateFile()}. Upon
+ * detecting a pending cancellation request, an {@code OperationCanceledException} is raised.
+ * - Headless scenario: Actual type is {@code JavaIoFileSystemAccess}. No progress monitor is available.
+ *
+ */
+ @Override
+ public void doGenerate(Resource input, IFileSystemAccess fsa) {
+ if (justCopy(input)) {
+ copyWithoutTranspilation(input, fsa);
+ return;
+ }
+
+ N4JSWorkspaceConfigSnapshot ws = workspaceAccess.getWorkspaceConfig(input);
+
+ try {
+ // remove error-marker
+ genMarkerSupport.deleteMarker(input);
+
+ updateOutputPath(ws, fsa, getCompilerID(), input);
+ internalDoGenerate(ws, input, GeneratorOption.DEFAULT_OPTIONS, fsa);
+
+ } catch (UnresolvedProxyInSubGeneratorException e) {
+ genMarkerSupport.createMarker(input, e.getMessage(), Severity.ERROR);
+
+ } catch (Exception e) {
+ // cancellation is not an error case, so simply propagate as usual
+ operationCanceledManager.propagateIfCancelException(e);
+
+ // issue error marker
+ String target = input.getURI() == null ? "unknown" : input.getURI().toString();
+ if (input instanceof N4JSResource && ((N4JSResource) input).getModule() != null) {
+ target = ((N4JSResource) input).getModule().getModuleSpecifier();
+ }
+ String msgMarker = "Severe error occurred while transpiling module " + target
+ + ". Check error log for details about the failure.";
+ genMarkerSupport.createMarker(input, msgMarker, Severity.ERROR);
+
+ // re-throw as GeneratorException to have the frameworks notify the error.
+ if (e instanceof GeneratorException) {
+ throw e;
+ }
+ String msg = (e.getMessage() == null) ? "type=" + e.getClass().getName() : "message=" + e.getMessage();
+ exceptionHandler.handleError("Severe error occurred in transpiler=" + getCompilerID() + " " + msg + ".", e);
+ }
+ }
+
+ @Override
+ public boolean shouldBeCompiled(Resource input, CancelIndicator monitor) {
+ N4JSWorkspaceConfigSnapshot ws = workspaceAccess.getWorkspaceConfig(input);
+
+ boolean autobuildEnabled = isActive(input);
+ boolean isXPECTMode = N4JSGlobals.XT_FILE_EXTENSION
+ .equals(URIUtils.fileExtension(input.getURI()).toLowerCase());
+ URI inputUri = input.getURI();
+
+ boolean result = (autobuildEnabled
+ && isGenerateProjectType(ws, inputUri)
+ && hasOutput(ws, inputUri)
+ && isOutputNotInSourceContainer(ws, inputUri)
+ && isOutsideOfOutputFolder(ws, inputUri)
+ && isSource(ws, inputUri)
+ && (isNoValidate(ws, inputUri)
+ || isExternal(ws, inputUri)
+ // if platform is running (but not in XPECT mode) the generator is called from the builder,
+ // hence cannot have any validation errors
+ // (note: XPECT swallows errors hence we cannot rely on Eclipse in case of .xt files)
+ || ((EMFPlugin.IS_ECLIPSE_RUNNING && !isXPECTMode) || hasNoErrors(input, monitor))))
+ && (!isStaticPolyfillingModule(input)) // compile driven by filled type
+ && hasNoPolyfillErrors(input, monitor);
+ return result;
+ }
+
+ private boolean hasOutput(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
+ return getOutputPath(ws, n4jsSourceURI) != null;
+ }
+
+ private boolean isSource(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
+ return ws.findSourceFolderContaining(n4jsSourceURI) != null;
+ }
+
+ private boolean isNoValidate(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
+ return ws.isNoValidate(n4jsSourceURI);
+ }
+
+ private boolean isExternal(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
+ N4JSSourceFolderSnapshot sourceContainerOpt = ws.findSourceFolderContaining(n4jsSourceURI);
+ if (sourceContainerOpt != null) {
+ N4JSSourceFolderSnapshot sourceContainer = sourceContainerOpt;
+ return sourceContainer.isExternal();
+ }
+ return false;
+ }
+
+ /**
+ * If the resource has a static polyfill, then ensure it is error-free. Calls
+ * {@link #hasNoErrors(Resource, CancelIndicator)} on the static polyfill resource.
+ */
+ private boolean hasNoPolyfillErrors(Resource input, CancelIndicator monitor) {
+ N4JSResource resSPoly = staticPolyfillHelper.getStaticPolyfillResource(input);
+ if (resSPoly == null) {
+ return true;
+ }
+ // re-validation is necessary since the changes of the current resource (i.e. filled resource)
+ // can affect the filling resource in a way that validation errors will be removed or created.
+ cache.recreateIssues(resVal, resSPoly, CheckMode.ALL, monitor);
+ return hasNoErrors(resSPoly, monitor);
+ }
+
+ /**
+ * Does validation report no errors for the given resource? If errors exists, log them as a side-effect. If
+ * validation was canceled before finishing, don't assume absence of errors.
+ */
+ private boolean hasNoErrors(Resource input, CancelIndicator monitor) {
+ List issues = cache.getOrUpdateIssues(resVal, input, CheckMode.ALL, monitor);
+ if (issues == null || input instanceof N4JSResource && !((N4JSResource) input).isFullyProcessed()) {
+ // Cancellation occurred likely before all validations completed, thus can't assume absence of errors.
+ // Cancellation may result in exit via normal control-flow (this case) or via exceptional control-flow (see
+ // exception handler below)
+ warnDueToCancelation(input, null);
+ return false;
+ }
+
+ List errors = toList(filter(issues, i -> i.getSeverity() == ERROR));
+ if (errors.isEmpty()) {
+ return true;
+ }
+ if (LOGGER.isDebugEnabled()) {
+ for (Issue it : errors) {
+ LOGGER.debug(input.getURI() + " " + it.getMessage() + " " + it.getSeverity() + " @L_"
+ + it.getLineNumber() + " ");
+ }
+ }
+ return false;
+ }
+
+ private void warnDueToCancelation(Resource input, Throwable exc) {
+ String msg = "User canceled the validation of " + input.getURI() + ". Will not compile.";
+ if (null == exc) {
+ LOGGER.warn(msg);
+ } else {
+ LOGGER.warn(msg, exc);
+ }
+ }
+
+ /** @return true iff the current project has a project type that is supposed to generate code. */
+ private boolean isGenerateProjectType(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
+ N4JSProjectConfigSnapshot project = ws.findProjectContaining(n4jsSourceURI);
+ if (project != null) {
+ ProjectType projectType = project.getType();
+ if (N4JSGlobals.PROJECT_TYPES_WITHOUT_GENERATION.contains(projectType)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /** @return true iff the given resource does not lie within the output folder. */
+ private boolean isOutsideOfOutputFolder(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
+ return !containmentHelper.isContainedInOutputFolder(ws, n4jsSourceURI);
+ }
+
+ /** @return true iff the output folder of the given n4js resource is not contained by a source container. */
+ private boolean isOutputNotInSourceContainer(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
+ N4JSProjectConfigSnapshot project = ws.findProjectContaining(n4jsSourceURI);
+ if (project != null) {
+ return !containmentHelper.isOutputContainedInSourceContainer(ws, project);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Actual generation to be overridden by subclasses.
+ */
+ abstract protected void internalDoGenerate(N4JSWorkspaceConfigSnapshot ws, Resource resource,
+ GeneratorOption[] options, IFileSystemAccess access);
+
+ /**
+ * Returns the name of the target file (without path) to which the source is to be compiled to. Default
+ * implementation returns a configured project Name with version + file name + extension. E.g., "proj-0.0.1/p/A.js"
+ * for a file A in proj.
+ *
+ * Convenience method, to provide all necessary API for the sub-classes. Delegates to
+ * {@link ResourceNameComputer#generateFileDescriptor(Resource, String)}.
+ */
+ protected String getTargetFileName(Resource n4jsSourceFile, String compiledFileExtension) {
+ return resourceNameComputer.generateFileDescriptor(n4jsSourceFile, compiledFileExtension);
+ }
+
+ /**
+ * Convenient access to the Script-Element
+ */
+ protected Script rootElement(Resource resource) {
+ return head(filter(resource.getContents(), Script.class));
+ }
+
+ /** The file-extension of the compiled result */
+ protected String getCompiledFileExtension(Resource input) {
+ return preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.COMPILED_FILE_EXTENSION,
+ getDefaultDescriptor());
+ }
+
+ /** The file-extension of the source-map to the compiled result */
+ protected String getCompiledFileSourceMapExtension(Resource input) {
+ return preferenceAccess.getPreference(input, getCompilerID(),
+ CompilerProperties.COMPILED_FILE_SOURCEMAP_EXTENSION,
+ getDefaultDescriptor());
+ }
+
+ /** Adjust output-path of the generator to match the N4JS projects-settings. */
+ private void updateOutputPath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa, String compilerID,
+ Resource input) {
+ String outputPath = getOutputPath(ws, input.getURI());
+ if (outputPath == null) {
+ outputPath = N4JSLanguageConstants.DEFAULT_PROJECT_OUTPUT;
+ }
+ if (fsa instanceof AbstractFileSystemAccess) {
+ OutputConfiguration conf = ((AbstractFileSystemAccess) fsa).getOutputConfigurations().get(compilerID);
+ if (conf != null) {
+ conf.setOutputDirectory(outputPath);
+ }
+ }
+ }
+
+ private static String getOutputPath(N4JSWorkspaceConfigSnapshot ws, URI nestedLocation) {
+ if (ws.findProjectContaining(nestedLocation) == null) {
+ return null;
+ }
+ if (ws.findProjectContaining(nestedLocation).getProjectDescription() == null) {
+ return null;
+ }
+ return ws.findProjectContaining(nestedLocation).getProjectDescription().getOutputPath();
+ }
+
+ /** Navigation from the generated output-location to the location of the input-resource */
+ @SuppressWarnings("unused")
+ protected Path calculateNavigationFromOutputToSourcePath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa,
+ String compilerID, N4JSResource input) {
+ // --- Project locations ---
+ N4JSProjectConfigSnapshot project = ws.findProjectContaining(input.getURI());
+
+ // /home/user/workspace/Project/
+ Path projectPath = project.getPathAsFileURI().toFileSystemPath();
+ // platform:/resource/Project/
+ URI projectLocURI = project.getPathAsFileURI().withTrailingPathDelimiter().toURI();
+
+ // --- output locations ---
+ // src-gen
+ String outputPath = project.getOutputPath();
+ // Project/a/b/c
+ Path outputRelativeLocation = getOutputRelativeLocation(input);
+
+ // --- source locations ---
+ // src/a/b/c
+ URI inputURI = uriExtensions.withEmptyAuthority(input.getURI());
+ URI completetSourceURI = inputURI.trimSegments(1).deresolve(projectLocURI);
+ String completetSource = URIUtils.toFile(completetSourceURI).toString();
+
+ // Handling case when source container is the project root itself. (Sources { source { '.' } })
+ if (null == completetSource && project.getPathAsFileURI().toURI() == input.getURI().trimSegments(1)) {
+ completetSource = projectPath.toFile().getAbsolutePath();
+ }
+
+ // /home/user/workspace/Project/src-gen/a/b/c
+ Path fullOutpath = projectPath.resolve(outputPath).normalize().resolve(outputRelativeLocation).normalize();
+ // /home/user/workspace/Project/src/a/b/c
+ Path fullSourcePath = projectPath.resolve(completetSource).normalize();
+
+ // ../../../../../../src/a/b/c
+ Path rel = fullOutpath.relativize(fullSourcePath);
+
+ return rel;
+ }
+
+ /**
+ * Calculates local output path for a given resource. Depending on the configuration this path can be in various
+ * forms, {@code Project-1.0.0/a/b/c/}, {@code Project/a/b/c/} or just {@code a/b/c/}
+ *
+ */
+ private Path getOutputRelativeLocation(N4JSResource input) {
+ URI uri = uriExtensions.withEmptyAuthority(input.getURI());
+ // Project/a/b/c/Input.XX
+ Path localOutputFilePath = Paths.get(resourceNameComputer.generateFileDescriptor(input, uri, ".XX"));
+
+ // if calculated path has just one element, e.g. "Input.XX"
+ // then local path segment is empty
+ if (localOutputFilePath.getNameCount() < 2) {
+ return Paths.get("");
+ }
+
+ // otherwise strip resource to get local path, i.e. Project/a/b/c/Input.XX => Project/a/b/c/
+ return localOutputFilePath.subpath(0, localOutputFilePath.getNameCount() - 1);
+ }
+
+ /**
+ * TODO IDE-1487 currently there is no notion of default compiler. We fake call to the ES5 sub generator.
+ */
+ final static String calculateProjectBasedOutputDirectory(N4JSProjectConfigSnapshot project,
+ boolean includeProjectName) {
+ return (includeProjectName) ? project.getPackageName() + "/" + project.getOutputPath()
+ : project.getOutputPath();
+ }
+
+ /** Access to compiler ID */
+ abstract public String getCompilerID();
+
+ /** Access to compiler descriptor */
+ abstract public CompilerDescriptor getDefaultDescriptor();
+
+ /** Answers: Is this compiler activated for the input at hand? */
+ public boolean isActive(Resource input) {
+ return Boolean.valueOf(preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.IS_ACTIVE,
+ getDefaultDescriptor()));
+ }
+
+ /**
+ * Checking the availability of a static polyfill, which will override the compilation of this module.
+ **/
+ public boolean isNotStaticallyPolyfilled(Resource resource) {
+ // val TModule tmodule = (N4JSResource::getModule(resource) ); // for some reason xtend cannot see static
+ // getModule ?!
+ if (resource instanceof N4JSResource) {
+ TModule tmodule = N4JSResource.getModule(resource);
+ // 1. resource must be StaticPolyfillAware and
+ // 2. there must exist a polyfilling instance (second instance with same fqn)
+ if (tmodule.isStaticPolyfillAware()) {
+ // search for second instance.
+ if (staticPolyfillHelper.hasStaticPolyfill(resource)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checking if this resource represents a static polyfill, which will contribute to a filled resource.
+ **/
+ private boolean isStaticPolyfillingModule(Resource resource) {
+ TModule tmodule = (N4JSResource.getModule(resource));
+ if (null != tmodule) {
+ return tmodule.isStaticPolyfillModule();
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @return true if the composite generator is applicable to the given resource and false otherwise.
+ */
+ @Override
+ public boolean isApplicableTo(Resource input) {
+ return shouldBeCompiled(input, null);
+ }
+
+ /**
+ * Depending on the file-extension, determines if the given resource requires actual transpilation as opposed to
+ * simply copying the source file to the output folder.
+ *
+ * @param eResource
+ * N4JS resource to check.
+ * @return true if the code requires transpilation.
+ */
+ protected boolean justCopy(Resource eResource) {
+ ResourceType resourceType = ResourceType.getResourceType(eResource);
+ return !(resourceType.equals(ResourceType.N4JS) || resourceType.equals(ResourceType.N4JSX)
+ || resourceType.equals(ResourceType.N4JSD));
+ }
+
+ /**
+ * Take the content of resource and copy it over to the output folder without any transformation.
+ *
+ * @param resource
+ * JS-code snippet which will be treated as text.
+ * @param fsa
+ * file system access
+ */
+ protected void copyWithoutTranspilation(Resource resource, IFileSystemAccess fsa) {
+ StringWriter outCode = new StringWriter();
+ // get script
+ EObject script = resource.getContents().get(0);
+
+ // obtain text
+ String scriptAsText = NodeModelUtils.getNode(script).getRootNode().getText();
+
+ // write
+ String decorated = scriptAsText.toString();
+ outCode.write(decorated);
+ String filename = resourceNameComputer.generateFileDescriptor(resource,
+ URIUtils.fileExtension(resource.getURI()));
+ fsa.generateFile(filename, getCompilerID(), outCode.toString());
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.xtend
deleted file mode 100644
index 56c17a092f..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.xtend
+++ /dev/null
@@ -1,482 +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.generator
-
-import com.google.inject.Inject
-import java.io.StringWriter
-import java.nio.file.Path
-import java.nio.file.Paths
-import java.util.List
-import org.eclipse.emf.common.EMFPlugin
-import org.eclipse.emf.common.util.URI
-import org.eclipse.emf.ecore.resource.Resource
-import org.eclipse.n4js.N4JSGlobals
-import org.eclipse.n4js.N4JSLanguageConstants
-import org.eclipse.n4js.generator.IGeneratorMarkerSupport.Severity
-import org.eclipse.n4js.n4JS.Script
-import org.eclipse.n4js.resource.N4JSCache
-import org.eclipse.n4js.resource.N4JSResource
-import org.eclipse.n4js.ts.types.TModule
-import org.eclipse.n4js.utils.FolderContainmentHelper
-import org.eclipse.n4js.utils.Log
-import org.eclipse.n4js.utils.ResourceNameComputer
-import org.eclipse.n4js.utils.ResourceType
-import org.eclipse.n4js.utils.StaticPolyfillHelper
-import org.eclipse.n4js.utils.URIUtils
-import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot
-import org.eclipse.n4js.workspace.N4JSWorkspaceConfigSnapshot
-import org.eclipse.n4js.workspace.WorkspaceAccess
-import org.eclipse.xtend.lib.annotations.Accessors
-import org.eclipse.xtext.generator.AbstractFileSystemAccess
-import org.eclipse.xtext.generator.IFileSystemAccess
-import org.eclipse.xtext.generator.IFileSystemAccess2
-import org.eclipse.xtext.generator.IGenerator2
-import org.eclipse.xtext.generator.IGeneratorContext
-import org.eclipse.xtext.nodemodel.util.NodeModelUtils
-import org.eclipse.xtext.service.OperationCanceledManager
-import org.eclipse.xtext.util.CancelIndicator
-import org.eclipse.xtext.util.UriExtensions
-import org.eclipse.xtext.validation.CheckMode
-import org.eclipse.xtext.validation.IResourceValidator
-import org.eclipse.xtext.validation.Issue
-
-import static org.eclipse.xtext.diagnostics.Severity.*
-
-/**
- * All sub generators should extend this class. It provides basic blocks of the logic, and
- * shared implementations.
- */
-@Log
-abstract class AbstractSubGenerator implements ISubGenerator, IGenerator2 {
-
- @Accessors
- private CompilerDescriptor compilerDescriptor = null
-
- @Inject protected StaticPolyfillHelper staticPolyfillHelper
-
- @Inject protected WorkspaceAccess workspaceAccess
-
- @Inject protected ResourceNameComputer resourceNameComputer
-
- @Inject protected IResourceValidator resVal
-
- @Inject protected N4JSCache cache
-
- @Inject protected IGeneratorMarkerSupport genMarkerSupport
-
- @Inject protected OperationCanceledManager operationCanceledManager
-
- @Inject protected GeneratorExceptionHandler exceptionHandler
-
- @Inject protected N4JSPreferenceAccess preferenceAccess
-
- @Inject private FolderContainmentHelper containmentHelper;
-
- @Inject private UriExtensions uriExtensions;
-
-
- override getCompilerDescriptor() {
- if (compilerDescriptor === null) {
- compilerDescriptor = getDefaultDescriptor
- }
- return compilerDescriptor
- }
-
-
- override doGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
- doGenerate(input, fsa);
- }
-
- override beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
- // TODO Auto-generated method stub
-
- }
-
- override afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
- // TODO Auto-generated method stub
-
- }
-
- /**
- * This override declares an {@link IFileSystemAccess} parameter. At runtime, its actual type depends on whether IDE or headless,
- * which in turn determines whether the actual argument has a progress monitor or not:
- *
- * -
- * IDE scenario: Actual type is {@code EclipseResourceFileSystemAccess2}. A progress monitor can be obtained via {@code getMonitor()}.
- * It is checked automatically behind the scenes, for example in {@code generateFile()}.
- * Upon detecting a pending cancellation request, an {@code OperationCanceledException} is raised.
- *
- * -
- * Headless scenario: Actual type is {@code JavaIoFileSystemAccess}. No progress monitor is available.
- *
- *
- */
- override doGenerate(Resource input, IFileSystemAccess fsa) {
- if (justCopy(input)) {
- copyWithoutTranspilation(input, fsa);
- return;
- }
-
- val ws = workspaceAccess.getWorkspaceConfig(input);
-
- try {
- // remove error-marker
- genMarkerSupport.deleteMarker(input)
-
- updateOutputPath(ws, fsa, getCompilerID, input);
- internalDoGenerate(ws, input, GeneratorOption.DEFAULT_OPTIONS, fsa);
-
- } catch (UnresolvedProxyInSubGeneratorException e) {
- genMarkerSupport.createMarker(input, e.message, Severity.ERROR);
-
- } catch (Exception e) {
- // cancellation is not an error case, so simply propagate as usual
- operationCanceledManager.propagateIfCancelException(e);
-
- // issue error marker
- val target = (if (input instanceof N4JSResource) input.module?.moduleSpecifier) ?: input.URI?.toString;
- val msgMarker = "Severe error occurred while transpiling module " + target
- + ". Check error log for details about the failure.";
- genMarkerSupport.createMarker(input, msgMarker, Severity.ERROR);
-
- // re-throw as GeneratorException to have the frameworks notify the error.
- if (e instanceof GeneratorException) {
- throw e;
- }
- var msg = if (e.message === null) "type=" + e.class.name else "message=" + e.message;
- exceptionHandler.handleError('''Severe error occurred in transpiler=«compilerID» «msg».''', e);
- }
- }
-
- override shouldBeCompiled(Resource input, CancelIndicator monitor) {
- val ws = workspaceAccess.getWorkspaceConfig(input);
-
- val autobuildEnabled = isActive(input)
- val isXPECTMode = N4JSGlobals.XT_FILE_EXTENSION.equals(URIUtils.fileExtension(input.URI).toLowerCase);
- val inputUri = input.URI
-
- val boolean result = (autobuildEnabled
- && isGenerateProjectType(ws, inputUri)
- && hasOutput(ws, inputUri)
- && isOutputNotInSourceContainer(ws, inputUri)
- && isOutsideOfOutputFolder(ws, inputUri)
- && isSource(ws, inputUri)
- && (isNoValidate(ws, inputUri)
- || isExternal(ws, inputUri)
- // if platform is running (but not in XPECT mode) the generator is called from the builder, hence cannot have any validation errors
- // (note: XPECT swallows errors hence we cannot rely on Eclipse in case of .xt files)
- || ((EMFPlugin.IS_ECLIPSE_RUNNING && !isXPECTMode) || hasNoErrors(input, monitor))
- ))
- && (!input.isStaticPolyfillingModule) // compile driven by filled type
- && hasNoPolyfillErrors(input,monitor)
- return result
- }
-
- private def hasOutput(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI){
- return getOutputPath(ws, n4jsSourceURI) !== null;
- }
- private def isSource(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
- return ws.findSourceFolderContaining(n4jsSourceURI) !== null;
- }
-
- private def isNoValidate(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
- return ws.isNoValidate(n4jsSourceURI);
- }
-
- private def isExternal(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
- val sourceContainerOpt = ws.findSourceFolderContaining(n4jsSourceURI);
- if (sourceContainerOpt !== null) {
- val sourceContainer = sourceContainerOpt;
- return sourceContainer.external;
- }
- return false;
- }
-
- /** If the resource has a static polyfill, then ensure it is error-free.
- * Calls {@link #hasNoErrors()} on the static polyfill resource.
- */
- private def boolean hasNoPolyfillErrors(Resource input, CancelIndicator monitor) {
- val resSPoly = staticPolyfillHelper.getStaticPolyfillResource(input)
- if (resSPoly === null) {
- return true;
- }
- // re-validation is necessary since the changes of the current resource (i.e. filled resource)
- // can affect the filling resource in a way that validation errors will be removed or created.
- cache.recreateIssues(resVal, resSPoly, CheckMode.ALL, monitor);
- return hasNoErrors(resSPoly, monitor)
- }
-
- /**
- * Does validation report no errors for the given resource?
- * If errors exists, log them as a side-effect.
- * If validation was canceled before finishing, don't assume absence of errors.
- */
- private def boolean hasNoErrors(Resource input, CancelIndicator monitor) {
- val List issues = cache.getOrUpdateIssues(resVal, input, CheckMode.ALL, monitor);
- if (issues === null || input instanceof N4JSResource && !(input as N4JSResource).isFullyProcessed) {
- // Cancellation occurred likely before all validations completed, thus can't assume absence of errors.
- // Cancellation may result in exit via normal control-flow (this case) or via exceptional control-flow (see exception handler below)
- warnDueToCancelation(input, null);
- return false;
- }
-
- val Iterable errors = issues.filter[severity == ERROR];
- if (errors.isEmpty()) {
- return true
- }
- if (logger.isDebugEnabled) {
- errors.forEach[logger.debug(input.URI + " " + it.message + " " + it.severity + " @L_" + it.lineNumber + " ")]
- }
- return false
- }
-
- private def void warnDueToCancelation(Resource input, Throwable exc) {
- val msg = "User canceled the validation of " + input.URI + ". Will not compile.";
- if (null === exc) {
- logger.warn(msg)
- } else {
- logger.warn(msg,exc)
- }
- }
-
- /** @return true iff the current project has a project type that is supposed to generate code. */
- private def boolean isGenerateProjectType(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
- val project = ws.findProjectContaining(n4jsSourceURI);
- if (project !== null) {
- val projectType = project.getType();
- if (N4JSGlobals.PROJECT_TYPES_WITHOUT_GENERATION.contains(projectType)) {
- return false;
- }
- }
-
- return true;
- }
-
- /** @return true iff the given resource does not lie within the output folder. */
- private def boolean isOutsideOfOutputFolder(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
- return !containmentHelper.isContainedInOutputFolder(ws, n4jsSourceURI);
- }
-
- /** @return true iff the output folder of the given n4js resource is not contained by a source container. */
- private def boolean isOutputNotInSourceContainer(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) {
- val project = ws.findProjectContaining(n4jsSourceURI);
- if (project !== null) {
- return !containmentHelper.isOutputContainedInSourceContainer(ws, project)
- } else {
- return false;
- }
- }
-
- /**
- * Actual generation to be overridden by subclasses.
- */
- protected def void internalDoGenerate(N4JSWorkspaceConfigSnapshot ws, Resource resource, GeneratorOption[] options, IFileSystemAccess access)
-
- /**
- * Returns the name of the target file (without path) to which the source is to be compiled to.
- * Default implementation returns a configured project Name with version + file name + extension.
- * E.g., "proj-0.0.1/p/A.js" for a file A in proj.
- *
- * Convenience method, to provide all necessary API for the sub-classes.
- * Delegates to {@link ResourceNameComputer#getTargetFileName}.
- */
- protected def String getTargetFileName(Resource n4jsSourceFile, String compiledFileExtension) {
- return resourceNameComputer.generateFileDescriptor(n4jsSourceFile, compiledFileExtension)
- }
-
- /**
- * Convenient access to the Script-Element
- */
- protected def rootElement(Resource resource) {
- resource.contents.filter(Script).head
- }
-
- /** The file-extension of the compiled result */
- protected def String getCompiledFileExtension(Resource input) {
- preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.COMPILED_FILE_EXTENSION,
- getDefaultDescriptor())
- }
-
- /** The file-extension of the source-map to the compiled result */
- protected def String getCompiledFileSourceMapExtension(Resource input) {
- preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.COMPILED_FILE_SOURCEMAP_EXTENSION,
- getDefaultDescriptor())
- }
-
- /** Adjust output-path of the generator to match the N4JS projects-settings. */
- private def void updateOutputPath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa, String compilerID, Resource input) {
- val outputPath = getOutputPath(ws, input.URI) ?: N4JSLanguageConstants.DEFAULT_PROJECT_OUTPUT;
- if (fsa instanceof AbstractFileSystemAccess) {
- val conf = fsa.outputConfigurations.get(compilerID)
- if (conf !== null) {
- conf.setOutputDirectory(outputPath)
- }
- }
- }
-
- private static def String getOutputPath(N4JSWorkspaceConfigSnapshot ws, URI nestedLocation) {
- return ws.findProjectContaining(nestedLocation)?.getProjectDescription()?.getOutputPath();
- }
-
- /** Navigation from the generated output-location to the location of the input-resource */
- protected def Path calculateNavigationFromOutputToSourcePath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa, String compilerID, N4JSResource input) {
- // --- Project locations ---
- val project = ws.findProjectContaining(input.URI);
-
- // /home/user/workspace/Project/
- val projectPath = project.pathAsFileURI.toFileSystemPath
- // platform:/resource/Project/
- val projectLocURI = project.pathAsFileURI.withTrailingPathDelimiter.toURI
-
- // --- output locations ---
- // src-gen
- val outputPath = project.outputPath
- // Project/a/b/c
- val outputRelativeLocation = getOutputRelativeLocation(input)
-
- // --- source locations ---
- // src/a/b/c
- val inputURI = uriExtensions.withEmptyAuthority(input.URI);
- val completetSourceURI = inputURI.trimSegments(1).deresolve(projectLocURI)
- var completetSource = URIUtils.toFile(completetSourceURI).toString();
-
- // Handling case when source container is the project root itself. (Sources { source { '.' } })
- if (null === completetSource && project.pathAsFileURI.toURI === input.URI.trimSegments(1)) {
- completetSource = projectPath.toFile.absolutePath;
- }
-
- // /home/user/workspace/Project/src-gen/a/b/c
- val fullOutpath = projectPath.resolve(outputPath).normalize.resolve(outputRelativeLocation).normalize
- // /home/user/workspace/Project/src/a/b/c
- val fullSourcePath = projectPath.resolve(completetSource).normalize
-
- // ../../../../../../src/a/b/c
- val rel = fullOutpath.relativize(fullSourcePath)
-
- return rel
- }
-
- /**
- * Calculates local output path for a given resource.
- * Depending on the configuration this path can be in various forms, {@code Project-1.0.0/a/b/c/},
- * {@code Project/a/b/c/} or just {@code a/b/c/}
- *
- */
- private def Path getOutputRelativeLocation(N4JSResource input){
- val uri = uriExtensions.withEmptyAuthority(input.URI);
- // Project/a/b/c/Input.XX
- val localOutputFilePath = Paths.get(resourceNameComputer.generateFileDescriptor(input, uri, ".XX"))
-
- // if calculated path has just one element, e.g. "Input.XX"
- // then local path segment is empty
- if(localOutputFilePath.nameCount<2){
- return Paths.get("")
- }
-
- //otherwise strip resource to get local path, i.e. Project/a/b/c/Input.XX => Project/a/b/c/
- return localOutputFilePath.subpath(0,localOutputFilePath.nameCount - 1)
- }
-
- /**
- * Convenience for {@link AbstractSubGenerator#calculateOutputDirectory(String, String)},
- * uses default compiler ID.
- *
- * TODO IDE-1487 currently there is no notion of default compiler. We fake call to the ES5 sub generator.
- */
- def final static String calculateProjectBasedOutputDirectory(N4JSProjectConfigSnapshot project, boolean includeProjectName) {
- return if (includeProjectName) project.packageName + "/" + project.outputPath else project.outputPath;
- }
-
- /** Access to compiler ID */
- def abstract String getCompilerID()
-
- /** Access to compiler descriptor */
- def abstract CompilerDescriptor getDefaultDescriptor()
-
- /** Answers: Is this compiler activated for the input at hand? */
- def boolean isActive(Resource input) {
- return Boolean.valueOf(preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.IS_ACTIVE, getDefaultDescriptor()))
- }
-
- /** Checking the availability of a static polyfill, which will override the compilation of this module.
- **/
- def boolean isNotStaticallyPolyfilled(Resource resource) {
- // val TModule tmodule = (N4JSResource::getModule(resource) ); // for some reason xtend cannot see static getModule ?!
- if (resource instanceof N4JSResource) {
- val TModule tmodule = N4JSResource.getModule(resource)
- // 1. resource must be StaticPolyfillAware and
- // 2. there must exist a polyfilling instance (second instance with same fqn)
- if (tmodule.isStaticPolyfillAware) {
- // search for second instance.
- if (staticPolyfillHelper.hasStaticPolyfill(resource)) {
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Checking if this resource represents a static polyfill, which will contribute to a filled resource.
- **/
- private def boolean isStaticPolyfillingModule(Resource resource) {
- val TModule tmodule = (N4JSResource.getModule(resource));
- if (null !== tmodule) {
- return tmodule.isStaticPolyfillModule;
- }
- return false;
- }
-
- /**
- *
- * @return true if the composite generator is applicable to the given resource and false otherwise.
- */
- override boolean isApplicableTo(Resource input) {
- return shouldBeCompiled(input, null);
- }
-
- /**
- * Depending on the file-extension, determines if the given resource requires actual transpilation as opposed to
- * simply copying the source file to the output folder.
- *
- * @param eResource
- * N4JS resource to check.
- * @return true if the code requires transpilation.
- */
- def protected boolean justCopy(Resource eResource) {
- val resourceType = ResourceType.getResourceType(eResource);
- return !(resourceType.equals(ResourceType.N4JS) || resourceType.equals(ResourceType.N4JSX)
- || resourceType.equals(ResourceType.N4JSD));
- }
-
- /**
- * Take the content of resource and copy it over to the output folder without any transformation.
- *
- * @param resource
- * JS-code snippet which will be treated as text.
- * @param fsa
- * file system access
- */
- def protected void copyWithoutTranspilation(Resource resource, IFileSystemAccess fsa) {
- val outCode = new StringWriter();
- // get script
- val script = resource.getContents().get(0);
-
- // obtain text
- val scriptAsText = NodeModelUtils.getNode(script).getRootNode().getText();
-
- // write
- val decorated = scriptAsText.toString();
- outCode.write(decorated);
- val filename = resourceNameComputer.generateFileDescriptor(resource, URIUtils.fileExtension(resource.getURI()));
- fsa.generateFile(filename, compilerID, outCode.toString());
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.java
similarity index 58%
rename from plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.xtend
rename to plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.java
index 2d21c7328c..2a58482697 100644
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.xtend
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.java
@@ -8,20 +8,20 @@
* Contributors:
* NumberFour AG - Initial API and implementation
*/
-package org.eclipse.n4js.generator
+package org.eclipse.n4js.generator;
/**
* Code generator exception handler.
*/
-class GeneratorExceptionHandler {
+public class GeneratorExceptionHandler {
/**
- * Prints provided message to the out stream, and the stack trace of the provided cause.
- * Afterwards throws new {@link GeneratorException} wrapping provided cause.
+ * Prints provided message to the out stream, and the stack trace of the provided cause. Afterwards throws new
+ * {@link GeneratorException} wrapping provided cause.
*/
- def handleError(String message, Throwable cause) {
- println(message)
- cause.printStackTrace
- throw new GeneratorException( message, cause);
+ public void handleError(String message, Throwable cause) {
+ System.out.println(message);
+ cause.printStackTrace();
+ throw new GeneratorException(message, cause);
}
}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.java
new file mode 100644
index 0000000000..c91ec98e79
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.java
@@ -0,0 +1,380 @@
+/**
+ * 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.packagejson;
+
+import static com.google.common.base.Optional.fromNullable;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.eclipse.n4js.json.JSON.JSONDocument;
+import org.eclipse.n4js.json.model.utils.JSONModelUtils;
+import org.eclipse.n4js.packagejson.projectDescription.ProjectType;
+import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType;
+import org.eclipse.n4js.utils.ProjectDescriptionLoader;
+import org.eclipse.n4js.workspace.utils.N4JSPackageName;
+
+/**
+ * Convenient builder for creating the N4JS package.json compliant {@link JSONDocument} model instances or file
+ * content.#
+ *
+ * Provides support for most supported N4JS-specific package.json properties. See {@link ProjectDescriptionLoader} for
+ * details on all supported properties.
+ */
+@SuppressWarnings("hiding")
+public class PackageJsonBuilder {
+
+ /**
+ * Creates a new builder instance with a default project type of {@link ProjectType#VALIDATION}.
+ */
+ public static PackageJsonBuilder newBuilder() {
+ return new PackageJsonBuilder(ProjectType.VALIDATION); // use project type 'validation'
+ }
+
+ /**
+ * Creates a new builder instance without a default project type. Use this for creating a plain package.json without
+ * an "n4js" section.
+ */
+ public static PackageJsonBuilder newPlainBuilder() {
+ return new PackageJsonBuilder(null);
+ }
+
+ private String projectName;
+ private ProjectType type;
+ private String version;
+ private Boolean _private;
+
+ private final SortedMap dependencies;
+ private final SortedMap devDependencies;
+
+ private String vendorId;
+ private String vendorName;
+
+ private String output;
+
+ private final Collection providedRLs;
+ private final Collection requiredRLs;
+ private final Collection testedProjects;
+ private String extendedRE;
+ private final Collection implementedProjects;
+ private String implementationId;
+
+ private final Map sourceContainers;
+
+ private final Collection workspaces;
+
+ private PackageJsonBuilder(ProjectType defaultProjectType) {
+ type = defaultProjectType;
+ providedRLs = new ArrayList<>();
+ requiredRLs = new ArrayList<>();
+ dependencies = new TreeMap<>();
+ devDependencies = new TreeMap<>();
+ implementedProjects = new ArrayList<>();
+ testedProjects = new ArrayList<>();
+ sourceContainers = new HashMap<>();
+ workspaces = new ArrayList<>();
+ }
+
+ /**
+ * Builds the N4JS package.json file contents for a project with the given name.
+ *
+ * @return the N4JS package.json file contents as a string.
+ */
+ public String build() {
+ JSONDocument document = this.buildModel();
+ return JSONModelUtils.serializeJSON(document);
+ }
+
+ /**
+ * Builds the N4JS package.json {@code JSONDocument} model representation.
+ *
+ * @return the N4JS package.json {@code JSONDocument} representation.
+ */
+ public JSONDocument buildModel() {
+ return PackageJsonContentProvider.getModel(
+ fromNullable(projectName),
+ fromNullable(version),
+ fromNullable(_private),
+ workspaces,
+ fromNullable(type),
+ fromNullable(vendorId),
+ fromNullable(vendorName),
+ fromNullable(output),
+ fromNullable(extendedRE),
+ dependencies,
+ devDependencies,
+ providedRLs,
+ requiredRLs,
+ fromNullable(implementationId),
+ implementedProjects,
+ testedProjects,
+ sourceContainers);
+ }
+
+ /**
+ * Sets the project name and returns with the builder.
+ *
+ * @param name
+ * The N4JS project name.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withName(String name) {
+ this.projectName = checkNotNull(name);
+ return this;
+ }
+
+ /**
+ * Sets the project version and returns with the builder.
+ *
+ * @param version
+ * The project version.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withVersion(String version) {
+ this.version = checkNotNull(version);
+ return this;
+ }
+
+ /**
+ * Adds top-level property "private" to the package.json, with the given value.
+ */
+ public PackageJsonBuilder withPrivate(boolean _private) {
+ this._private = _private;
+ return this;
+ }
+
+ /**
+ * Sets the project type and returns with the builder.
+ *
+ * @param type
+ * the N4JS project type.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withType(ProjectType type) {
+ this.type = checkNotNull(type);
+ return this;
+ }
+
+ /**
+ * Sets the vendorId and returns with the builder.
+ *
+ * @param vendorId
+ * The project's vendor ID.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withVendorId(String vendorId) {
+ this.vendorId = vendorId;
+ return this;
+ }
+
+ /**
+ * Sets the vendor name and returns with the builder.
+ *
+ * @param vendorName
+ * The project's vendor name.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withVendorName(String vendorName) {
+ this.vendorName = vendorName;
+ return this;
+ }
+
+ /**
+ * Sets the output folder and returns with the builder.
+ *
+ * @param output
+ * The project's vendor name.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withOutput(String output) {
+ this.output = output;
+ return this;
+ }
+
+ /**
+ * Adds a source container with the given folder specifier and type.
+ *
+ * @param type
+ * The source container type.
+ * @param path
+ * The source container path.
+ */
+ public PackageJsonBuilder withSourceContainer(SourceContainerType type, String path) {
+ this.sourceContainers.put(checkNotNull(type), checkNotNull(path));
+ return this;
+ }
+
+ /**
+ * Sets the extended runtime environment on the builder.
+ *
+ * @param extendedRE
+ * the extended runtime environment. Optional. Can be {@code null}.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withExtendedRE(String extendedRE) {
+ this.extendedRE = extendedRE;
+ return this;
+ }
+
+ /**
+ * Adds a new provided runtime library to the current builder.
+ *
+ * @param providedRL
+ * the provided runtime library to add to the current builder. Cannot be {@code null}.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withProvidedRL(String providedRL) {
+ providedRLs.add(checkNotNull(providedRL));
+ return this;
+ }
+
+ /**
+ * Adds a new required runtime library to the current builder.
+ *
+ * This method will add the required runtime library both to the "requiredRuntimeLibraries" section, as well as the
+ * "dependencies" section.
+ *
+ * @param requiredRL
+ * the required runtime library to add to the current builder. Cannot be {@code null}.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withRequiredRL(String requiredRL) {
+ requiredRLs.add(checkNotNull(requiredRL));
+ // also add to dependencies
+ dependencies.put(requiredRL, "*");
+ return this;
+ }
+
+ /**
+ * Adds a new required runtime library with the given version constraint.
+ *
+ * This method will add the required runtime library both to the "requiredRuntimeLibraries" section, as well as the
+ * "dependencies" section.
+ *
+ * @param requiredRL
+ * The project id of the required runtime library.
+ * @param versionConstraint
+ * The version constraint to be used in the dependency section.
+ */
+ public PackageJsonBuilder withRequiredRL(String requiredRL, String versionConstraint) {
+ requiredRLs.add(checkNotNull(requiredRL));
+ // also add to dependencies
+ dependencies.put(requiredRL, versionConstraint);
+ return this;
+ }
+
+ /**
+ * Adds a new project dependency to the current builder.
+ *
+ * @param projectDependency
+ * the project dependency to add to the current builder. Cannot be {@code null}.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withDependency(N4JSPackageName projectDependency) {
+ dependencies.put(checkNotNull(projectDependency).getRawName(), "*");
+ return this;
+ }
+
+ /**
+ * Adds a new project dependency to the current builder.
+ *
+ * @param projectDependency
+ * The project dependency to add to the current builder. Cannot be {@code null}.
+ * @param versionConstraint
+ * The version constraint of the added project dependency.
+ *
+ * @return the builder.
+ */
+ public PackageJsonBuilder withDependency(N4JSPackageName projectDependency, String versionConstraint) {
+ dependencies.put(checkNotNull(projectDependency).getRawName(), checkNotNull(versionConstraint));
+ return this;
+ }
+
+ /**
+ * Adds a new project devDependency to the current builder.
+ *
+ * @param projectDevDependency
+ * the project devDependency to add to the current builder. Cannot be {@code null}.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withDevDependency(N4JSPackageName projectDevDependency) {
+ devDependencies.put(checkNotNull(projectDevDependency).getRawName(), "*");
+ return this;
+ }
+
+ /**
+ * Adds a new project devDependency to the current builder.
+ *
+ * @param projectDevDependency
+ * The project devDependency to add to the current builder. Cannot be {@code null}.
+ * @param versionConstraint
+ * The version constraint of the added project devDependency.
+ *
+ * @return the builder.
+ */
+ public PackageJsonBuilder withDevDependency(String projectDevDependency, String versionConstraint) {
+ devDependencies.put(checkNotNull(projectDevDependency), checkNotNull(versionConstraint));
+ return this;
+ }
+
+ /**
+ * Adds the given project id to the list of tested projects.
+ *
+ * This method also adds the given tested project as project dependency.
+ */
+ public PackageJsonBuilder withTestedProject(String testedProject) {
+ dependencies.put(checkNotNull(testedProject), "*");
+ testedProjects.add(checkNotNull(testedProject));
+ return this;
+ }
+
+ /**
+ * Sets the implementation id to the current builder.
+ *
+ * @param implementationId
+ * id for the implementations to choose from.
+ * @return the builder.
+ */
+ public PackageJsonBuilder withImplementationId(String implementationId) {
+ this.implementationId = checkNotNull(implementationId);
+ return this;
+ }
+
+ /**
+ * Adds a project to the list of implemented Projects.
+ *
+ * @param implementationAPI
+ * id of the implemented API
+ * @return the builder.
+ */
+ public PackageJsonBuilder withImplementedProject(String implementationAPI) {
+ implementedProjects.add(checkNotNull(implementationAPI));
+ return this;
+ }
+
+ /** Adds top-level property "workspaces" to the package.json, using an array with the given strings as value. */
+ public PackageJsonBuilder withWorkspaces(String... workspaces) {
+ this.workspaces.addAll(Arrays.asList(workspaces));
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "!!! This is just a preview of the N4JS package.json file !!!\n" + this.build();
+ }
+
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.xtend
deleted file mode 100644
index b85a94da8c..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.xtend
+++ /dev/null
@@ -1,358 +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.packagejson
-
-import java.util.Collection
-import java.util.Map
-import java.util.SortedMap
-import java.util.TreeMap
-import org.eclipse.n4js.json.JSON.JSONDocument
-import org.eclipse.n4js.json.model.utils.JSONModelUtils
-import org.eclipse.n4js.packagejson.projectDescription.ProjectType
-import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType
-import org.eclipse.n4js.utils.ProjectDescriptionLoader
-
-import static com.google.common.base.Optional.fromNullable
-import static com.google.common.base.Preconditions.checkNotNull
-import org.eclipse.n4js.workspace.utils.N4JSPackageName
-
-/**
- * Convenient builder for creating the N4JS package.json compliant {@link JSONDocument} model
- * instances or file content.#
- *
- * Provides support for most supported N4JS-specific package.json properties.
- * See {@link ProjectDescriptionLoader} for details on all supported properties.
- */
-public class PackageJsonBuilder {
-
- /**
- * Creates a new builder instance with a default project type of {@link ProjectType#VALIDATION}.
- */
- public static def PackageJsonBuilder newBuilder() {
- return new PackageJsonBuilder(ProjectType.VALIDATION); // use project type 'validation'
- }
-
- /**
- * Creates a new builder instance without a default project type. Use this for creating a
- * plain package.json without an "n4js" section.
- */
- public static def PackageJsonBuilder newPlainBuilder() {
- return new PackageJsonBuilder(null);
- }
-
- private String projectName;
- private ProjectType type;
- private String version;
- private Boolean _private;
-
- private SortedMap dependencies;
- private SortedMap devDependencies;
-
- private String vendorId;
- private String vendorName;
-
- private String output;
-
- private Collection providedRLs;
- private Collection requiredRLs;
- private Collection testedProjects;
- private String extendedRE;
- private Collection implementedProjects;
- private String implementationId;
-
- private Map sourceContainers;
-
- private Collection workspaces;
-
- private new(ProjectType defaultProjectType) {
- type = defaultProjectType;
- providedRLs = newArrayList
- requiredRLs = newArrayList
- dependencies = new TreeMap();
- devDependencies = new TreeMap();
- implementedProjects = newArrayList
- testedProjects = newArrayList;
- sourceContainers = newHashMap;
- workspaces = newArrayList;
- }
-
- /**
- * Builds the N4JS package.json file contents for a project with the given name.
- *
- * @return the N4JS package.json file contents as a string.
- */
- def String build() {
- val document = this.buildModel();
- return JSONModelUtils.serializeJSON(document);
- }
-
- /**
- * Builds the N4JS package.json {@code JSONDocument} model representation.
- *
- * @return the N4JS package.json {@code JSONDocument} representation.
- */
- def JSONDocument buildModel() {
- return PackageJsonContentProvider.getModel(
- fromNullable(projectName),
- fromNullable(version),
- fromNullable(_private),
- workspaces,
- fromNullable(type),
- fromNullable(vendorId),
- fromNullable(vendorName),
- fromNullable(output),
- fromNullable(extendedRE),
- dependencies,
- devDependencies,
- providedRLs,
- requiredRLs,
- fromNullable(implementationId),
- implementedProjects,
- testedProjects,
- sourceContainers);
- }
-
- /**
- * Sets the project name and returns with the builder.
- *
- * @param type The N4JS project name.
- * @return the builder.
- */
- def PackageJsonBuilder withName(String name) {
- this.projectName = checkNotNull(name)
- return this;
- }
-
- /**
- * Sets the project version and returns with the builder.
- *
- * @param version The project version.
- * @return the builder.
- */
- def PackageJsonBuilder withVersion(String version) {
- this.version = checkNotNull(version)
- return this;
- }
-
- /**
- * Adds top-level property "private" to the package.json, with the given value.
- */
- def PackageJsonBuilder withPrivate(boolean _private) {
- this._private = _private;
- return this;
- }
-
- /**
- * Sets the project type and returns with the builder.
- *
- * @param type the N4JS project type.
- * @return the builder.
- */
- def PackageJsonBuilder withType(ProjectType type) {
- this.type = checkNotNull(type)
- return this;
- }
-
- /**
- * Sets the vendorId and returns with the builder.
- *
- * @param type The project's vendor ID.
- * @return the builder.
- */
- def PackageJsonBuilder withVendorId(String vendorId) {
- this.vendorId = vendorId;
- return this;
- }
-
- /**
- * Sets the vendor name and returns with the builder.
- *
- * @param type The project's vendor name.
- * @return the builder.
- */
- def PackageJsonBuilder withVendorName(String vendorName) {
- this.vendorName = vendorName;
- return this;
- }
-
- /**
- * Sets the output folder and returns with the builder.
- *
- * @param type The project's vendor name.
- * @return the builder.
- */
- def PackageJsonBuilder withOutput(String output) {
- this.output = output;
- return this;
- }
-
- /**
- * Adds a source container with the given folder specifier and type.
- *
- * @param type The source container type.
- * @param path The source container path.
- */
- def PackageJsonBuilder withSourceContainer(SourceContainerType type, String path) {
- this.sourceContainers.put(checkNotNull(type), checkNotNull(path));
- return this;
- }
-
- /**
- * Sets the extended runtime environment on the builder.
- * @param extendedRE the extended runtime environment. Optional. Can be {@code null}.
- * @return the builder.
- */
- def PackageJsonBuilder withExtendedRE(String extendedRE) {
- this.extendedRE = extendedRE
- return this;
- }
-
- /**
- * Adds a new provided runtime library to the current builder.
- * @param providedRL the provided runtime library to add to the current builder. Cannot be {@code null}.
- * @return the builder.
- */
- def PackageJsonBuilder withProvidedRL(String providedRL) {
- providedRLs.add(checkNotNull(providedRL))
- return this;
- }
-
- /**
- * Adds a new required runtime library to the current builder.
- *
- * This method will add the required runtime library both to the "requiredRuntimeLibraries" section,
- * as well as the "dependencies" section.
- *
- * @param requiredRL the required runtime library to add to the current builder. Cannot be {@code null}.
- * @return the builder.
- */
- def PackageJsonBuilder withRequiredRL(String requiredRL) {
- requiredRLs.add(checkNotNull(requiredRL))
- // also add to dependencies
- dependencies.put(requiredRL, "*")
- return this;
- }
-
- /**
- * Adds a new required runtime library with the given version constraint.
- *
- * This method will add the required runtime library both to the "requiredRuntimeLibraries" section,
- * as well as the "dependencies" section.
- *
- * @param requiredRL The project id of the required runtime library.
- * @param versionConstraint The version constraint to be used in the dependency section.
- */
- def PackageJsonBuilder withRequiredRL(String requiredRL, String versionConstraint) {
- requiredRLs.add(checkNotNull(requiredRL))
- // also add to dependencies
- dependencies.put(requiredRL, versionConstraint)
- return this;
- }
-
- /**
- * Adds a new project dependency to the current builder.
- * @param projectDependency the project dependency to add to the current builder. Cannot be {@code null}.
- * @return the builder.
- */
- def PackageJsonBuilder withDependency(N4JSPackageName projectDependency) {
- dependencies.put(checkNotNull(projectDependency).rawName, "*")
- return this;
- }
-
- /**
- * Adds a new project dependency to the current builder.
- *
- * @param projectDependency The project dependency to add to the current builder. Cannot be {@code null}.
- * @param versionConstraint The version constraint of the added project dependency.
- *
- * @return the builder.
- */
- def PackageJsonBuilder withDependency(N4JSPackageName projectDependency, String versionConstraint) {
- dependencies.put(checkNotNull(projectDependency).rawName, checkNotNull(versionConstraint))
- return this;
- }
-
- /**
- * Adds a new project devDependency to the current builder.
- * @param projectDevDependency the project devDependency to add to the current builder. Cannot be {@code null}.
- * @return the builder.
- */
- def PackageJsonBuilder withDevDependency(N4JSPackageName projectDevDependency) {
- devDependencies.put(checkNotNull(projectDevDependency).rawName, "*")
- return this;
- }
-
- /**
- * Adds a new project devDependency to the current builder.
- *
- * @param projectDevDependency The project devDependency to add to the current builder. Cannot be {@code null}.
- * @param versionConstraint The version constraint of the added project devDependency.
- *
- * @return the builder.
- */
- def PackageJsonBuilder withDevDependency(String projectDevDependency, String versionConstraint) {
- devDependencies.put(checkNotNull(projectDevDependency), checkNotNull(versionConstraint))
- return this;
- }
-
- /**
- * Adds the given project id to the list of tested projects.
- *
- * This method also adds the given tested project as project dependency.
- */
- def PackageJsonBuilder withTestedProject(String testedProject) {
- dependencies.put(checkNotNull(testedProject), "*");
- testedProjects.add(checkNotNull(testedProject));
- return this;
- }
-
- /**
- * Adds the given project id to the list of tested projects and to the list
- * of dependencies with the given version constraint.
- *
- * This method also adds the given tested project as project dependency.
- */
- def PackageJsonBuilder withTestedProject(String testedProject, String version) {
- dependencies.put(checkNotNull(testedProject), "*");
- testedProjects.add(checkNotNull(testedProject));
- return this;
- }
-
- /** Sets the implementation id to the current builder.
- * @param implementationId id for the implementations to choose from.
- * @return the builder.
- */
- def PackageJsonBuilder withImplementationId(String implementationId) {
- this.implementationId = checkNotNull(implementationId)
- return this;
- }
-
- /** Adds a project to the list of implemented Projects.
- * NOTE: also call {@link withImplementationId()}
- * @param project id of the implemented API
- * @return the builder.
- */
- def PackageJsonBuilder withImplementedProject(String implementationAPI) {
- implementedProjects.add(checkNotNull(implementationAPI))
- return this;
- }
-
- /** Adds top-level property "workspaces" to the package.json, using an array with the given strings as value. */
- def PackageJsonBuilder withWorkspaces(String... workspaces) {
- this.workspaces.addAll(workspaces);
- return this;
- }
-
- override toString() {
- return '!!! This is just a preview of the N4JS package.json file !!!\n' + this.build();
- }
-
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.java
new file mode 100644
index 0000000000..50206fa4ca
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.java
@@ -0,0 +1,241 @@
+/**
+ * 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.packagejson;
+
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEPENDENCIES;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEV_DEPENDENCIES;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.EXTENDED_RUNTIME_ENVIRONMENT;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTATION_ID;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTED_PROJECTS;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.N4JS;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.NAME;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.OUTPUT;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.PRIVATE;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROJECT_TYPE;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROVIDED_RUNTIME_LIBRARIES;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.REQUIRED_RUNTIME_LIBRARIES;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.SOURCES;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.TESTED_PROJECTS;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_ID;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_NAME;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.VERSION;
+import static org.eclipse.n4js.packagejson.PackageJsonProperties.WORKSPACES_ARRAY;
+import static org.eclipse.n4js.packagejson.PackageJsonUtils.getSourceContainerTypeStringRepresentation;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.groupBy;
+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.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+
+import org.eclipse.n4js.json.JSON.JSONArray;
+import org.eclipse.n4js.json.JSON.JSONDocument;
+import org.eclipse.n4js.json.JSON.JSONFactory;
+import org.eclipse.n4js.json.JSON.JSONObject;
+import org.eclipse.n4js.json.JSON.JSONStringLiteral;
+import org.eclipse.n4js.json.JSON.NameValuePair;
+import org.eclipse.n4js.json.model.utils.JSONModelUtils;
+import org.eclipse.n4js.packagejson.projectDescription.ProjectType;
+import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType;
+
+import com.google.common.base.Optional;
+
+/**
+ * Class for providing the content of N4JS-specific package.json files.
+ *
+ * Use {@link PackageJsonBuilder} for creating package.json model instances and file content.
+ */
+class PackageJsonContentProvider {
+
+ /**
+ * Creates and returns with the N4JS package.json {@link JSONDocument} representation based on the given arguments.
+ *
+ * @param projectName
+ * the N4JS project name of the project (cf. name).
+ * @param version
+ * The declared version of the project.
+ * @param type
+ * The type of the N4JS project.
+ * @param vendorId
+ * The vendorId to use.
+ * @param vendorName
+ * The name of the vendor as string.
+ * @param output
+ * The relative output folder location.
+ * @param extendedRE
+ * The optional extended runtime environment.
+ * @param dependencies
+ * A map of dependencies of the project (maps dependencies to their version constraints).
+ * @param providedRL
+ * An iterable of provided runtime libraries.
+ * @param requiredRL
+ * An iterable of required runtime libraries.
+ * @param implementationId
+ * The implementationId of the project.
+ * @param testedProjects
+ * A list of all projects that are being tested.
+ * @param sourceContainers
+ * A map of all source containers of the project.
+ *
+ * @return the N4JS package.json content as a string.
+ */
+ static JSONDocument getModel(
+ Optional projectName,
+ Optional version,
+ Optional _private,
+ Iterable workspaces,
+ Optional type,
+ Optional vendorId,
+ Optional vendorName,
+ Optional output,
+ Optional extendedRE,
+ SortedMap dependencies,
+ SortedMap devDependencies,
+ Iterable providedRL,
+ Iterable requiredRL,
+ Optional implementationId,
+ Iterable implementedProjects,
+ Iterable testedProjects,
+ Map sourceContainers) {
+ JSONObject root = JSONFactory.eINSTANCE.createJSONObject();
+
+ if (projectName.isPresent())
+ JSONModelUtils.addProperty(root, NAME.name, projectName.get());
+
+ if (version.isPresent())
+ JSONModelUtils.addProperty(root, VERSION.name, version.get());
+
+ if (_private.isPresent()) {
+ JSONModelUtils.addProperty(root, PRIVATE.name,
+ JSONModelUtils.createBooleanLiteral(_private.get()));
+ }
+
+ if (workspaces.iterator().hasNext()) {
+ JSONModelUtils.addProperty(root, WORKSPACES_ARRAY.name,
+ JSONModelUtils.createStringArray(workspaces));
+ }
+
+ // add "dependencies" section
+ if (!dependencies.isEmpty()) {
+ JSONObject dependenciesValue = createDependenciesValue(dependencies);
+ JSONModelUtils.addProperty(root, DEPENDENCIES.name, dependenciesValue);
+ }
+
+ // add "devDependencies" section
+ if (!devDependencies.isEmpty()) {
+ JSONObject devDependenciesValue = createDependenciesValue(devDependencies);
+ JSONModelUtils.addProperty(root, DEV_DEPENDENCIES.name, devDependenciesValue);
+ }
+
+ // create "n4js" section (will be added below iff it will be non-empty)
+ JSONObject n4jsRoot = JSONFactory.eINSTANCE.createJSONObject();
+
+ // project type
+ if (type.isPresent()) {
+ String projectTypeStr = PackageJsonUtils.getProjectTypeStringRepresentation(type.get());
+ JSONModelUtils.addProperty(n4jsRoot, PROJECT_TYPE.name, projectTypeStr);
+ }
+
+ // add vendor related properties
+ if (vendorId.isPresent())
+ JSONModelUtils.addProperty(n4jsRoot, VENDOR_ID.name, vendorId.get());
+ if (vendorName.isPresent())
+ JSONModelUtils.addProperty(n4jsRoot, VENDOR_NAME.name, vendorName.get());
+
+ // add sources section
+ if (!sourceContainers.isEmpty()) {
+ JSONObject sourcesSection = JSONFactory.eINSTANCE.createJSONObject();
+ JSONModelUtils.addProperty(n4jsRoot, SOURCES.name, sourcesSection);
+
+ // add sources sub-sections
+ List> srcConts = new ArrayList<>(sourceContainers.entrySet());
+ // sort by container type
+ Collections.sort(srcConts,
+ Comparator.comparing(e -> getSourceContainerTypeStringRepresentation(e.getKey())));
+ // group by source container type
+ Map>> srcContsByPath = groupBy(srcConts,
+ e -> e.getKey());
+
+ // add source container sub-section for each specified source container type
+ for (SourceContainerType sct : srcContsByPath.keySet()) {
+ List> paths = srcContsByPath.get(sct);
+ JSONArray typeSectionArray = JSONFactory.eINSTANCE.createJSONArray();
+ JSONModelUtils.addProperty(sourcesSection,
+ PackageJsonUtils.getSourceContainerTypeStringRepresentation(sct), typeSectionArray);
+ List pathLiterals = toList(
+ map(paths, pathEntry -> JSONModelUtils.createStringLiteral(pathEntry.getValue())));
+ typeSectionArray.getElements().addAll(pathLiterals);
+ }
+ }
+
+ // add output folder
+ if (output.isPresent())
+ JSONModelUtils.addProperty(n4jsRoot, OUTPUT.name, output.get());
+
+ // add provided and required runtime libraries if given
+ if (providedRL.iterator().hasNext()) {
+ JSONModelUtils.addProperty(n4jsRoot, PROVIDED_RUNTIME_LIBRARIES.name,
+ JSONModelUtils.createStringArray(providedRL));
+ }
+ if (requiredRL.iterator().hasNext()) {
+ JSONModelUtils.addProperty(n4jsRoot, REQUIRED_RUNTIME_LIBRARIES.name,
+ JSONModelUtils.createStringArray(requiredRL));
+ }
+
+ if (extendedRE.isPresent()) {
+ JSONModelUtils.addProperty(n4jsRoot, EXTENDED_RUNTIME_ENVIRONMENT.name,
+ extendedRE.get());
+ }
+ if (implementationId.isPresent()) {
+ JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTATION_ID.name,
+ implementationId.get());
+ }
+
+ if (implementedProjects.iterator().hasNext()) {
+ JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTED_PROJECTS.name,
+ JSONModelUtils.createStringArray(implementedProjects));
+ }
+
+ if (testedProjects.iterator().hasNext()) {
+ JSONModelUtils.addProperty(n4jsRoot, TESTED_PROJECTS.name,
+ JSONModelUtils.createStringArray(testedProjects));
+ }
+
+ // add "n4js" section (if non-empty)
+ if (!n4jsRoot.getNameValuePairs().isEmpty()) {
+ JSONModelUtils.addProperty(root, N4JS.name, n4jsRoot);
+ }
+
+ // finally serialize as JSONDocument
+ JSONDocument document = JSONFactory.eINSTANCE.createJSONDocument();
+ document.setContent(root);
+
+ return document;
+ }
+
+ private static JSONObject createDependenciesValue(Map dependencies) {
+ JSONObject dependenciesValue = JSONFactory.eINSTANCE.createJSONObject();
+
+ List map = toList(map(dependencies.entrySet(), e -> {
+ NameValuePair pair = JSONFactory.eINSTANCE.createNameValuePair();
+ pair.setName(e.getKey());
+ pair.setValue(JSONModelUtils.createStringLiteral(e.getValue()));
+ return pair;
+ }));
+ dependenciesValue.getNameValuePairs().addAll(map);
+ return dependenciesValue;
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.xtend
deleted file mode 100644
index 1b36e0aa57..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.xtend
+++ /dev/null
@@ -1,210 +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.packagejson
-
-import com.google.common.base.Optional
-import java.util.Map
-import java.util.SortedMap
-import org.eclipse.n4js.json.JSON.JSONArray
-import org.eclipse.n4js.json.JSON.JSONDocument
-import org.eclipse.n4js.json.JSON.JSONFactory
-import org.eclipse.n4js.json.JSON.JSONObject
-import org.eclipse.n4js.json.model.utils.JSONModelUtils
-import org.eclipse.n4js.packagejson.projectDescription.ProjectType
-import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType
-
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEPENDENCIES
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEV_DEPENDENCIES
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.EXTENDED_RUNTIME_ENVIRONMENT
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTATION_ID
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTED_PROJECTS
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.N4JS
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.NAME
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.OUTPUT
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.PRIVATE
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROJECT_TYPE
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROVIDED_RUNTIME_LIBRARIES
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.REQUIRED_RUNTIME_LIBRARIES
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.SOURCES
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.TESTED_PROJECTS
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_ID
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_NAME
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.VERSION
-import static org.eclipse.n4js.packagejson.PackageJsonProperties.WORKSPACES_ARRAY
-
-/**
- * Class for providing the content of N4JS-specific package.json files.
- *
- * Use {@link PackageJsonBuilder} for creating package.json model instances and file content.
- */
-package class PackageJsonContentProvider {
-
- /**
- * Creates and returns with the N4JS package.json {@link JSONDocument} representation
- * based on the given arguments.
- *
- * @param projectName the N4JS project name of the project (cf. name).
- * @param version The declared version of the project.
- * @param type The type of the N4JS project.
- * @param vendorId The vendorId to use.
- * @param vendorName The name of the vendor as string.
- * @param output The relative output folder location.
- * @param extendedRE The optional extended runtime environment.
- * @param dependencies A map of dependencies of the project (maps dependencies to their version constraints).
- * @param providedRL An iterable of provided runtime libraries.
- * @param requiredRL An iterable of required runtime libraries.
- * @param implementationId The implementationId of the project.
- * @param testedProject A list of all projects that are being tested.
- * @param sourceContainers A map of all source containers of the project.
- *
- * @return the N4JS package.json content as a string.
- */
- package static def JSONDocument getModel(
- Optional projectName,
- Optional version,
- Optional _private,
- Iterable workspaces,
- Optional type,
- Optional vendorId,
- Optional vendorName,
- Optional output,
- Optional extendedRE,
- SortedMap dependencies,
- SortedMap devDependencies,
- Iterable providedRL,
- Iterable requiredRL,
- Optional implementationId,
- Iterable implementedProjects,
- Iterable testedProjects,
- Map sourceContainers
- ) {
- val JSONObject root = JSONFactory.eINSTANCE.createJSONObject();
-
- if (projectName.present)
- JSONModelUtils.addProperty(root, NAME.name, projectName.get());
-
- if (version.present)
- JSONModelUtils.addProperty(root, VERSION.name, version.get());
-
- if (_private.present) {
- JSONModelUtils.addProperty(root, PRIVATE.name,
- JSONModelUtils.createBooleanLiteral(_private.get()));
- }
-
- if (!workspaces.empty) {
- JSONModelUtils.addProperty(root, WORKSPACES_ARRAY.name,
- JSONModelUtils.createStringArray(workspaces));
- }
-
- // add "dependencies" section
- if (!dependencies.empty) {
- val dependenciesValue = createDependenciesValue(dependencies);
- JSONModelUtils.addProperty(root, DEPENDENCIES.name, dependenciesValue);
- }
-
- // add "devDependencies" section
- if (!devDependencies.empty) {
- val devDependenciesValue = createDependenciesValue(devDependencies);
- JSONModelUtils.addProperty(root, DEV_DEPENDENCIES.name, devDependenciesValue);
- }
-
- // create "n4js" section (will be added below iff it will be non-empty)
- val JSONObject n4jsRoot = JSONFactory.eINSTANCE.createJSONObject();
-
- // project type
- if (type.present) {
- val projectTypeStr = PackageJsonUtils.getProjectTypeStringRepresentation(type.get());
- JSONModelUtils.addProperty(n4jsRoot, PROJECT_TYPE.name, projectTypeStr);
- }
-
- // add vendor related properties
- if (vendorId.present)
- JSONModelUtils.addProperty(n4jsRoot, VENDOR_ID.name, vendorId.get());
- if (vendorName.present)
- JSONModelUtils.addProperty(n4jsRoot, VENDOR_NAME.name, vendorName.get());
-
- // add sources section
- if (!sourceContainers.empty) {
- val JSONObject sourcesSection = JSONFactory.eINSTANCE.createJSONObject();
- JSONModelUtils.addProperty(n4jsRoot, SOURCES.name, sourcesSection);
-
- // add sources sub-sections
- sourceContainers.entrySet
- // sort by container type
- .sortBy[ e | PackageJsonUtils.getSourceContainerTypeStringRepresentation(e.key) ]
- // group by source container type
- .groupBy[ e | e.key ]
- // add source container sub-section for each specified source container type
- .forEach[containerType, paths|
- val JSONArray typeSectionArray = JSONFactory.eINSTANCE.createJSONArray();
- JSONModelUtils.addProperty(sourcesSection,
- PackageJsonUtils.getSourceContainerTypeStringRepresentation(containerType), typeSectionArray);
- val pathLiterals = paths.map[pathEntry | JSONModelUtils.createStringLiteral(pathEntry.value) ];
- typeSectionArray.getElements().addAll(pathLiterals);
- ];
- }
-
- // add output folder
- if (output.present)
- JSONModelUtils.addProperty(n4jsRoot, OUTPUT.name, output.get());
-
- // add provided and required runtime libraries if given
- if (!providedRL.empty) {
- JSONModelUtils.addProperty(n4jsRoot, PROVIDED_RUNTIME_LIBRARIES.name,
- JSONModelUtils.createStringArray(providedRL));
- }
- if (!requiredRL.empty) {
- JSONModelUtils.addProperty(n4jsRoot, REQUIRED_RUNTIME_LIBRARIES.name,
- JSONModelUtils.createStringArray(requiredRL));
- }
-
- if (extendedRE.isPresent) {
- JSONModelUtils.addProperty(n4jsRoot, EXTENDED_RUNTIME_ENVIRONMENT.name,
- extendedRE.get());
- }
- if (implementationId.isPresent) {
- JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTATION_ID.name,
- implementationId.get());
- }
-
- if (!implementedProjects.empty) {
- JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTED_PROJECTS.name,
- JSONModelUtils.createStringArray(implementedProjects));
- }
-
- if (!testedProjects.empty) {
- JSONModelUtils.addProperty(n4jsRoot, TESTED_PROJECTS.name,
- JSONModelUtils.createStringArray(testedProjects));
- }
-
- // add "n4js" section (if non-empty)
- if (!n4jsRoot.nameValuePairs.empty) {
- JSONModelUtils.addProperty(root, N4JS.name, n4jsRoot);
- }
-
- // finally serialize as JSONDocument
- val JSONDocument document = JSONFactory.eINSTANCE.createJSONDocument();
- document.setContent(root);
-
- return document;
- }
-
- private def static JSONObject createDependenciesValue(Map dependencies) {
- val JSONObject dependenciesValue = JSONFactory.eINSTANCE.createJSONObject();
- dependenciesValue.nameValuePairs.addAll(dependencies.entrySet.map [ e |
- val pair = JSONFactory.eINSTANCE.createNameValuePair();
- pair.name = e.key;
- pair.value = JSONModelUtils.createStringLiteral(e.value);
- return pair;
- ]);
- return dependenciesValue;
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.java
new file mode 100644
index 0000000000..20c34f159c
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.java
@@ -0,0 +1,563 @@
+/**
+ * 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.postprocessing;
+
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.addCancelIndicator;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isCanceled;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment;
+import static org.eclipse.n4js.utils.N4JSLanguageUtils.isASTNode;
+import static org.eclipse.n4js.utils.N4JSLanguageUtils.isIdentifiableSubtree;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull;
+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.toIterable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.n4js.AnnotationDefinition;
+import org.eclipse.n4js.n4JS.Annotation;
+import org.eclipse.n4js.n4JS.CatchBlock;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.ForStatement;
+import org.eclipse.n4js.n4JS.FormalParameter;
+import org.eclipse.n4js.n4JS.FunctionDefinition;
+import org.eclipse.n4js.n4JS.FunctionExpression;
+import org.eclipse.n4js.n4JS.IdentifierRef;
+import org.eclipse.n4js.n4JS.ImportDeclaration;
+import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName;
+import org.eclipse.n4js.n4JS.N4ClassifierDeclaration;
+import org.eclipse.n4js.n4JS.N4FieldDeclaration;
+import org.eclipse.n4js.n4JS.N4JSASTUtils;
+import org.eclipse.n4js.n4JS.N4JSPackage;
+import org.eclipse.n4js.n4JS.PropertyGetterDeclaration;
+import org.eclipse.n4js.n4JS.PropertyMethodDeclaration;
+import org.eclipse.n4js.n4JS.PropertyNameValuePair;
+import org.eclipse.n4js.n4JS.PropertySetterDeclaration;
+import org.eclipse.n4js.n4JS.Script;
+import org.eclipse.n4js.n4JS.SetterDeclaration;
+import org.eclipse.n4js.n4JS.ThisLiteral;
+import org.eclipse.n4js.n4JS.VariableDeclaration;
+import org.eclipse.n4js.n4JS.YieldExpression;
+import org.eclipse.n4js.resource.N4JSResource;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.types.SyntaxRelatedTElement;
+import org.eclipse.n4js.ts.types.TModule;
+import org.eclipse.n4js.ts.types.TVariable;
+import org.eclipse.n4js.ts.types.TypableElement;
+import org.eclipse.n4js.ts.types.TypesPackage;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.utils.EcoreUtilN4;
+import org.eclipse.n4js.utils.N4JSLanguageUtils;
+import org.eclipse.n4js.utils.StaticPolyfillHelper;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.util.CancelIndicator;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Main processor used during {@link N4JSPostProcessor post-processing} of N4JS resources. It controls the overall work
+ * flow of processing the AST, but does not do any actual work; instead, it delegates to the other processors:
+ *
+ * - {@link TypeProcessor}, which delegates further to
+ *
+ * - {@link PolyProcessor}, which delegates further to
+ *
+ * - {@link PolyProcessor_ArrayLiteral}
+ *
- {@link PolyProcessor_ObjectLiteral}
+ *
- {@link PolyProcessor_FunctionExpression}
+ *
- {@link PolyProcessor_CallExpression}
+ *
+ * - {@link DestructureProcessor}
+ *
+ * - {@code TypeExpectedProcessor} (coming soon!)
+ *
- {@link TypeDeferredProcessor}
+ *
+ */
+@Singleton
+public class ASTProcessor extends AbstractProcessor {
+
+ @Inject
+ private ComputedNameProcessor computedNameProcessor;
+ @Inject
+ private TypeProcessor typeProcessor;
+ @Inject
+ private TypeRefProcessor typeRefProcessor;
+ @Inject
+ private TypeDeferredProcessor typeDeferredProcessor;
+ @Inject
+ private CompileTimeExpressionProcessor compileTimeExpressionProcessor;
+ @Inject
+ private RuntimeDependencyProcessor runtimeDependencyProcessor;
+ @Inject
+ private StaticPolyfillHelper staticPolyfillHelper;
+
+ /**
+ * Entry point for processing of the entire AST of the given resource. Will throw IllegalStateException if called
+ * more than once per N4JSResource.
+ *
+ * This method performs some preparatory tasks (e.g., creating an instance of {@link ASTMetaInfoCache}) and ensures
+ * consistency by tracking the 'isProcessing' state with try/finally; for actual processing, this method delegates
+ * to method {@link #processAST(RuleEnvironment, Script, ASTMetaInfoCache)}.
+ *
+ * @param resource
+ * may not be null.
+ * @param cancelIndicator
+ * may be null.
+ */
+ public void processAST(N4JSResource resource, CancelIndicator cancelIndicator) {
+ if (resource == null)
+ throw new IllegalArgumentException("resource may not be null");
+
+ // the following is required, because typing may have been initiated by resolution of a proxy
+ // -> when traversing the AST, we will sooner or later try to resolve this same proxy, which would be
+ // interpreted as a cyclic proxy resolution by method LazyLinkingResource#getEObject(String,Triple)
+ resource.clearResolving();
+
+ log(0, "### processing resource: " + resource.getURI());
+
+ Script script = resource.getScript();
+ // we're during post-processing, so cache should be available now
+ ASTMetaInfoCache cache = resource.getASTMetaInfoCacheVerifyContext();
+ RuleEnvironment G = newRuleEnvironment(resource);
+ addCancelIndicator(G, cancelIndicator);
+ try {
+ processAST(G, script, cache);
+ } finally {
+ if (isCanceled(G)) {
+ log(0, "CANCELED by cancelIndicator");
+ }
+
+ if (isDEBUG_LOG_RESULT()) {
+ log(0, "### result for " + resource.getURI());
+ log(4, resource.getScript(), cache);
+ }
+ log(0, "### done: " + resource.getURI());
+ }
+ }
+
+ /**
+ * First method to actually perform processing of the AST. This method defines the various processing phases.
+ *
+ * There exists a single "main phase" where 95% of processing happens (entry point for this main phase is method
+ * {@link #processSubtree(RuleEnvironment, EObject, ASTMetaInfoCache, int)}), plus a number of smaller phases before
+ * and after that where some special handling is performed.
+ */
+ private void processAST(RuleEnvironment G, Script script, ASTMetaInfoCache cache) {
+ // phase 0: process compile-time expressions & computed property names (order is important)
+ for (Expression node : toIterable(filter(script.eAllContents(), Expression.class))) {
+ compileTimeExpressionProcessor.evaluateCompileTimeExpression(G, node, cache);
+ }
+ for (LiteralOrComputedPropertyName node : toIterable(
+ filter(script.eAllContents(), LiteralOrComputedPropertyName.class))) {
+ computedNameProcessor.processComputedPropertyName(node, cache);
+ }
+
+ // phase 1: main processing
+ processSubtree(G, script, cache, 0);
+ // phase 2: processing of postponed subtrees
+ EObject eObj;
+ while ((eObj = cache.postponedSubTrees.poll()) != null) {
+ // note: we need to allow adding more postponed subtrees inside this loop!
+ processSubtree(G, eObj, cache, 0);
+ }
+ // phase 3: store runtime and load-time dependencies in TModule
+ runtimeDependencyProcessor.storeDirectRuntimeDependenciesInTModule(script, cache);
+ }
+
+ /**
+ * Process given node and all of its direct and indirect children.
+ *
+ * @param node
+ * the root of the subtree to process; must be an AST node.
+ */
+ void processSubtree(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) {
+ assertTrueIfRigid(cache, "argument 'node' must be an AST node", isASTNode(node));
+
+ log(indentLevel, "processing: " + getObjectInfo(node));
+
+ checkCanceled(G);
+
+ // already done as part of a forward processing?
+ if (cache.forwardProcessedSubTrees.contains(node)) {
+ if (isDEBUG_LOG()) {
+ log(indentLevel, "(subtree already processed as a forward reference)");
+ if (node instanceof TypableElement) {
+ log(indentLevel, cache.getTypeFailSafe((TypableElement) node));
+ }
+ }
+ return;
+ }
+ if (cache.postponedSubTrees.contains(node)) {
+ // in case this happens, you can either:
+ // * not postpone this node, or
+ // * handle the postponed node later (not as part of a forward reference)
+ throw new IllegalStateException("eager processing of postponed subtree");
+ }
+
+ if (!cache.astNodesCurrentlyBeingTyped.add(node)) {
+ // this subtree is currently being processed
+ // (can happen, for example, if we are processing a member (e.g. field) and during that processing we
+ // encounter a reference to the containing class (e.g. in the initializer expression))
+ if (isDEBUG_LOG()) {
+ log(indentLevel, "(subtree currently in progress - skipping)");
+ }
+ return;
+ }
+
+ try {
+ // process node itself - part 1 (before child processing)
+ processNode_preChildren(G, node, cache);
+
+ // process the children
+ List children = childrenToBeProcessed(node);
+ for (EObject child : children) {
+ if (isPostponedNode(child)) {
+ // postpone
+ cache.postponedSubTrees.add(child);
+ } else {
+ // process now
+ processSubtree(G, child, cache, indentLevel + 1);
+ checkCanceled(G);
+ }
+ }
+
+ // process node itself - part 2 (after child processing)
+ processNode_postChildren(G, node, cache, indentLevel);
+
+ // we're done with this node, but make sure that all proxies have actually been resolved
+ // (this is important mainly for two reasons: (1) post-processing is often triggered by a call to
+ // N4JSResource#resolveLazyCrossReferences(CancelIndicator), so we have to guarantee that all lazy
+ // cross-references are actually resolved; (2) the type system may not resolve all proxies and some
+ // nodes are not typed at all (i.e. isTypableNode() returns false), so we have to enforce this here.
+
+ // We also perform all processing, related to outgoing references from the current node at this point.
+ resolveAndProcessReferencesInNode(node, cache);
+
+ } finally {
+ cache.astNodesCurrentlyBeingTyped.remove(node);
+ }
+ }
+
+ private boolean isPostponedNode(EObject node) {
+ return isPostponedInitializer(node)
+ || N4JSASTUtils.isBodyOfFunctionOrFieldAccessor(node);
+ }
+
+ /**
+ * Initializers are postponed iff:
+ *
+ * - Node is an initializer of a FormalParameter p,
+ * - and p is part of a Poly FunctionExpression f,
+ * - and p contains references to other FormalParameters of f, or f itself.
+ *
+ */
+ private boolean isPostponedInitializer(EObject node) {
+ boolean isPostponedInitializer = false;
+ EObject fp = node.eContainer();
+ if (fp instanceof FormalParameter) {
+ FormalParameter fpar = (FormalParameter) fp;
+ if (node instanceof Expression) {
+ if (fpar.isHasInitializerAssignment()) {
+ EObject funDefObj = fpar.eContainer();
+ // IdentifierRef in Initializers can cause cyclic dependencies
+ if (funDefObj instanceof FunctionExpression) {
+ FunctionExpression funDef = (FunctionExpression) funDefObj;
+ // Check if the initializer refers to other fpars
+ EList allFPars = funDef.getFpars();
+ List allRefs = EcoreUtilN4.getAllContentsOfTypeStopAt(fpar, IdentifierRef.class,
+ N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY);
+
+ for (IdentifierRef ir : allRefs) {
+ Object id = ir.getId();
+ if (id instanceof SyntaxRelatedTElement) {
+ id = ((SyntaxRelatedTElement) id)
+ .eGet(TypesPackage.eINSTANCE.getSyntaxRelatedTElement_AstElement(), false);
+ }
+ boolean idRefCausesCyclDep = allFPars.contains(id) // f(p, q=p) {}
+ || id instanceof VariableDeclaration
+ && ((VariableDeclaration) id).getExpression() == funDef; // f(p, q=f(1)) {}
+ if (idRefCausesCyclDep) {
+ isPostponedInitializer = true;
+ }
+ }
+ }
+ // In ObjectLiterals, the ThisLiteral in Initializers can cause cyclic dependencies
+ // TODO GH-1337 add support for spread operator
+ boolean thisLiteralCausesCyclDep =
+ // let o = { a:1, f(p=this.a) {} }
+ funDefObj instanceof PropertyMethodDeclaration
+ ||
+ // let o = {a:2, f: function(p=this.a) {}}
+ funDefObj instanceof FunctionExpression
+ && funDefObj.eContainer() instanceof PropertyNameValuePair;
+
+ if (thisLiteralCausesCyclDep) {
+ boolean containsThisLiteral = EcoreUtilN4.containsContentsOfTypeStopAt(fpar, ThisLiteral.class,
+ N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY);
+ if (containsThisLiteral) {
+ isPostponedInitializer = true;
+ }
+ }
+ // If this check is not sufficient, we have to add more checks here. Note: Setters never have
+ // initializers.
+ }
+ }
+ }
+ return isPostponedInitializer;
+ }
+
+ /**
+ * Forward-process given node and all of its direct and indirect children.
+ *
+ * Via this method, other processors can request a forward processing of some subtree. Does nothing if the given
+ * node was processed already, either as part of a forward reference or during normal processing.
+ *
+ * @return true
iff the forward processing is legal, false
otherwise.
+ */
+ boolean processSubtree_forwardReference(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache) {
+ assertTrueIfRigid(cache, "argument 'node' must be an AST node", isASTNode(node));
+
+ // is node a valid target for a forward reference (i.e. an identifiable subtree)?
+ boolean valid = isIdentifiableSubtree(node) || isExceptionCaseOfForwardReferencableSubtree(node);
+ if (!valid) {
+ XtextResource resource = (XtextResource) node.eResource();
+ if (resource != null) {
+ assertTrueIfRigid(cache,
+ "forward reference only allowed to identifiable subtrees; but was: " + node + " in\n" +
+ resource.getParseResult().getRootNode().getText(),
+ valid);
+ } else {
+ assertTrueIfRigid(cache, "forward reference only allowed to identifiable subtrees; but was: " + node,
+ valid);
+ }
+ }
+
+ TypeRef fromCache = cache.getTypeFailSafe(node);
+ if (fromCache != null) {
+ // already processed, nothing else to do
+ // note: this is not an error, we may have many forward references to the same identifiable subtree
+ return true;
+ }
+
+ if (cache.astNodesCurrentlyBeingTyped.contains(node)) {
+ // cyclic forward reference
+ // legal cases of a cyclic reference
+ // TODO GH-1337 add support for spread operator
+ boolean isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node);
+ if (isCyclicForwardReference && (node instanceof VariableDeclaration
+ || node instanceof N4ClassifierDeclaration
+ || node instanceof N4FieldDeclaration
+ || (node instanceof PropertyNameValuePair
+ && ((PropertyNameValuePair) node).getExpression() instanceof FunctionExpression)
+ || node instanceof PropertyGetterDeclaration || node instanceof PropertySetterDeclaration
+ || (node instanceof Expression && node.eContainer() instanceof YieldExpression))) {
+ return true;
+ }
+
+ // illegal cyclic node inference
+ String msg = "*#*#*#*#*#* illegal cyclic forward reference to " + getObjectInfo(node)
+ + " (resource: " + (node.eResource() == null ? "null" : node.eResource().getURI()) + ")";
+ logErr(msg);
+ return false;
+ } else if (isSemiCyclicForwardReferenceInForLoop(node, cache)) {
+ // semi-cyclic forward reference
+ // (this is deemed legal for the same reason why 'var x = 1+x;' is treated as a legal forward reference)
+ return true;
+ }
+
+ if (cache.forwardProcessedSubTrees.contains(node)) {
+ // we saw above that the cache does not contain anything for node, so this is an error
+ throw new IllegalStateException();
+ }
+
+ // actually perform the forward processing
+ log(0, "==START of identifiable sub-tree below " + getObjectInfo(node));
+ RuleEnvironment G_fresh = newRuleEnvironment(G); // use a new, empty environment here (but retain
+ // cancelIndicator!)
+ processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level
+ cache.forwardProcessedSubTrees.add(node);
+ log(0, "==END of identifiable sub-tree below " + getObjectInfo(node));
+
+ return true;
+ }
+
+ // ---------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Top-down processing of AST nodes happens here, i.e. this method will see all AST nodes in a top-down order.
+ */
+ private void processNode_preChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache) {
+ typeRefProcessor.handleTypeRefs(G, node, cache);
+
+ if (node instanceof FunctionDefinition) {
+ handleAsyncOrGeneratorFunctionDefinition(G, (FunctionDefinition) node);
+ }
+
+ typeDeferredProcessor.handleDeferredTypeRefs_preChildren(G, node, cache);
+ }
+
+ /**
+ * Bottom-up processing of AST nodes happens here, i.e. this method will see all AST nodes in a bottom-up order.
+ */
+ private void processNode_postChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) {
+
+ typeDeferredProcessor.handleDeferredTypeRefs_postChildren(G, node, cache);
+
+ typeProcessor.typeNode(G, node, cache, indentLevel);
+ /*
+ * references to other files via import statements NOTE: for all imports except bare imports, the following is
+ * unnecessary, because post-processing of the target resource would be triggered automatically as soon as type
+ * inference is performed on an element imported from the target resource. However, by doing this eagerly up
+ * front, the overall flow of post-processing across multiple resources is a bit easier to understand/predict.
+ * This does not lead to any additional processing being done (it's just done a bit earlier), except in case of
+ * unused imports.
+ */
+ if (node instanceof ImportDeclaration) {
+ TModule targetModule = ((ImportDeclaration) node).getModule();
+ if (targetModule != null && !targetModule.eIsProxy()) {
+ Resource targetResource = targetModule.eResource();
+ if (targetResource instanceof N4JSResource) {
+ // trigger post-processing of target resource
+ ((N4JSResource) targetResource).performPostProcessing(getCancelIndicator(G));
+ }
+ }
+ }
+
+ if (node instanceof Annotation) {
+ if (Objects.equals(((Annotation) node).getName(), AnnotationDefinition.STATIC_POLYFILL_AWARE.name)) {
+ N4JSResource resSPoly = staticPolyfillHelper.getStaticPolyfillResource(node.eResource());
+ if (resSPoly != null) {
+ // trigger post-processing of poly filler
+ resSPoly.performPostProcessing(getCancelIndicator(G));
+ }
+ }
+ }
+
+ runtimeDependencyProcessor.recordRuntimeReferencesInCache(node, cache);
+ }
+
+ // ---------------------------------------------------------------------------------------------------------------
+
+ /**
+ * This method returns the direct children of 'obj' that are to be processed, in the order in which they are to
+ * be processed. By default, all direct children must be processed and the order is insignificant, so in the
+ * default case this method simply returns {@link EObject#eContents()}. However, this method implements special
+ * handling for some exception cases where the processing order is significant.
+ */
+ private List childrenToBeProcessed(EObject obj) {
+ if (obj instanceof SetterDeclaration) {
+ // process formal parameter before body
+ return bringToFront(obj.eContents(), ((SetterDeclaration) obj).getFpar());
+ }
+ if (obj instanceof FunctionDefinition) {
+ // process formal parameters before body
+ return bringToFront(obj.eContents(), ((FunctionDefinition) obj).getFpars().toArray(new EObject[0]));
+ }
+ if (obj instanceof CatchBlock) {
+ // process catch variable before block
+ return bringToFront(obj.eContents(), ((CatchBlock) obj).getCatchVariable());
+ }
+ if (obj instanceof ForStatement) {
+ // process expression before varDeclOrBindings
+ return bringToFront(obj.eContents(), ((ForStatement) obj).getExpression());
+ }
+ // standard case: order is insignificant (so we simply use the order provided by EMF)
+ return obj.eContents();
+ }
+
+ // ---------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Normally, forward references are allowed only to {@link N4JSLanguageUtils#isIdentifiableSubtree(EObject)
+ * identifiable subtrees}. However, there are exception cases that are also allowed and this method returns
+ * true
for those cases.
+ */
+ private static boolean isExceptionCaseOfForwardReferencableSubtree(EObject astNode) {
+ return isExpressionInForOf(astNode);
+ }
+
+ private static boolean isExpressionInForOf(EObject astNode) {
+ return astNode instanceof Expression && astNode.eContainer() instanceof ForStatement
+ && ((ForStatement) astNode.eContainer()).isForOf()
+ && astNode.eContainingFeature() == N4JSPackage.eINSTANCE.getIterationStatement_Expression();
+ }
+
+ /**
+ * Returns true if we have a semi-cyclic reference to a variable declaration in a for in/of loop. For example:
+ *
+ *
+ * for(var x of foo(x)) {}
+ *
+ */
+ boolean isSemiCyclicForwardReferenceInForLoop(EObject node, ASTMetaInfoCache cache) {
+ if (node instanceof VariableDeclaration) {
+ EObject parent = node.eContainer();
+ if (parent instanceof ForStatement) {
+ ForStatement fs = (ForStatement) parent;
+ return (fs.isForIn() || fs.isForOf()) && cache.astNodesCurrentlyBeingTyped.contains(fs.getExpression());
+ }
+ }
+ return false;
+ }
+
+ private void resolveAndProcessReferencesInNode(EObject astNode, ASTMetaInfoCache cache) {
+ for (EReference eRef : astNode.eClass().getEAllReferences()) {
+ if (!eRef.isContainment() && !eRef.isContainer()) { // only cross-references have proxies (in our case)
+ Object node = astNode.eGet(eRef, true);
+
+ if (node instanceof EObject) {
+ recordReferencesToLocalVariables(eRef, astNode, (EObject) node, cache);
+ }
+ }
+ }
+ }
+
+ private void recordReferencesToLocalVariables(EReference reference, EObject sourceNode, EObject target,
+ ASTMetaInfoCache cache) {
+ // skip reference Variable#definedVariable (it does not constitute a usage of the variable)
+ if (reference == N4JSPackage.Literals.ABSTRACT_VARIABLE__DEFINED_VARIABLE) {
+ return;
+ }
+ // If target is still a proxy its resolution failed, therefore it should be skipped.
+ if (target.eIsProxy()) {
+ return;
+ }
+ // skip non-local references
+ if (sourceNode.eResource() != target.eResource()) {
+ return;
+ }
+ if (target instanceof TVariable) {
+ // don't record references to directly exported variables
+ if (((TVariable) target).isDirectlyExported()) {
+ return;
+ }
+
+ cache.storeLocalVariableReference((TVariable) target, sourceNode);
+ }
+ }
+
+ private List bringToFront(List l, EObject... elements) {
+ List result = new ArrayList<>(l);
+ List extends EObject> elemSanitized = toList(filterNull(Arrays.asList(elements)));
+ result.removeAll(elemSanitized);
+ result.addAll(0, elemSanitized);
+ return result;
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.xtend
deleted file mode 100644
index e214de2616..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.xtend
+++ /dev/null
@@ -1,540 +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.postprocessing
-
-import com.google.inject.Inject
-import com.google.inject.Singleton
-import java.util.ArrayList
-import java.util.List
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.emf.ecore.EReference
-import org.eclipse.n4js.AnnotationDefinition
-import org.eclipse.n4js.n4JS.Annotation
-import org.eclipse.n4js.n4JS.CatchBlock
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.ForStatement
-import org.eclipse.n4js.n4JS.FormalParameter
-import org.eclipse.n4js.n4JS.FunctionDefinition
-import org.eclipse.n4js.n4JS.FunctionExpression
-import org.eclipse.n4js.n4JS.IdentifierRef
-import org.eclipse.n4js.n4JS.ImportDeclaration
-import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName
-import org.eclipse.n4js.n4JS.N4ClassifierDeclaration
-import org.eclipse.n4js.n4JS.N4FieldDeclaration
-import org.eclipse.n4js.n4JS.N4JSASTUtils
-import org.eclipse.n4js.n4JS.N4JSPackage
-import org.eclipse.n4js.n4JS.PropertyGetterDeclaration
-import org.eclipse.n4js.n4JS.PropertyMethodDeclaration
-import org.eclipse.n4js.n4JS.PropertyNameValuePair
-import org.eclipse.n4js.n4JS.PropertySetterDeclaration
-import org.eclipse.n4js.n4JS.Script
-import org.eclipse.n4js.n4JS.SetterDeclaration
-import org.eclipse.n4js.n4JS.ThisLiteral
-import org.eclipse.n4js.n4JS.VariableDeclaration
-import org.eclipse.n4js.n4JS.YieldExpression
-import org.eclipse.n4js.resource.N4JSResource
-import org.eclipse.n4js.ts.types.SyntaxRelatedTElement
-import org.eclipse.n4js.ts.types.TVariable
-import org.eclipse.n4js.ts.types.TypableElement
-import org.eclipse.n4js.ts.types.TypesPackage
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.utils.EcoreUtilN4
-import org.eclipse.n4js.utils.N4JSLanguageUtils
-import org.eclipse.n4js.utils.StaticPolyfillHelper
-import org.eclipse.xtext.resource.XtextResource
-import org.eclipse.xtext.util.CancelIndicator
-
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.*
-
-/**
- * Main processor used during {@link N4JSPostProcessor post-processing} of N4JS resources. It controls the overall
- * work flow of processing the AST, but does not do any actual work; instead, it delegates to the other processors:
- *
- * - {@link TypeProcessor}, which delegates further to
- *
- * - {@link PolyProcessor}, which delegates further to
- *
- * - {@link PolyProcessor_ArrayLiteral}
- *
- {@link PolyProcessor_ObjectLiteral}
- *
- {@link PolyProcessor_FunctionExpression}
- *
- {@link PolyProcessor_CallExpression}
- *
- * - {@link DestructureProcessor}
- *
- * - {@code TypeExpectedProcessor} (coming soon!)
- *
- {@link TypeDeferredProcessor}
- *
- */
-@Singleton
-public class ASTProcessor extends AbstractProcessor {
-
- @Inject
- private ComputedNameProcessor computedNameProcessor;
- @Inject
- private TypeProcessor typeProcessor;
- @Inject
- private TypeRefProcessor typeRefProcessor;
- @Inject
- private TypeDeferredProcessor typeDeferredProcessor;
- @Inject
- private CompileTimeExpressionProcessor compileTimeExpressionProcessor;
- @Inject
- private RuntimeDependencyProcessor runtimeDependencyProcessor;
- @Inject
- private StaticPolyfillHelper staticPolyfillHelper
-
- /**
- * Entry point for processing of the entire AST of the given resource.
- * Will throw IllegalStateException if called more than once per N4JSResource.
- *
- * This method performs some preparatory tasks (e.g., creating an instance of {@link ASTMetaInfoCache}) and ensures
- * consistency by tracking the 'isProcessing' state with try/finally; for actual processing, this method delegates
- * to method {@link #processAST(RuleEnvironment, Script, ASTMetaInfoCache)}.
- *
- * @param resource may not be null.
- * @param cancelIndicator may be null.
- */
- def public void processAST(N4JSResource resource, CancelIndicator cancelIndicator) {
- if (resource === null)
- throw new IllegalArgumentException("resource may not be null");
-
- // the following is required, because typing may have been initiated by resolution of a proxy
- // -> when traversing the AST, we will sooner or later try to resolve this same proxy, which would be
- // interpreted as a cyclic proxy resolution by method LazyLinkingResource#getEObject(String,Triple)
- resource.clearResolving();
-
- log(0, "### processing resource: " + resource.URI);
-
- val script = resource.script;
- val cache = resource.getASTMetaInfoCacheVerifyContext(); // we're during post-processing, so cache should be available now
- val G = resource.newRuleEnvironment;
- G.addCancelIndicator(cancelIndicator);
- try {
- processAST(G, script, cache);
- } finally {
- if (G.canceled) {
- log(0, "CANCELED by cancelIndicator");
- }
-
- if (isDEBUG_LOG_RESULT) {
- log(0, "### result for " + resource.URI);
- log(4, resource.script, cache);
- }
- log(0, "### done: " + resource.URI);
- }
- }
-
- /**
- * First method to actually perform processing of the AST. This method defines the various processing phases.
- *
- * There exists a single "main phase" where 95% of processing happens (entry point for this main phase is method
- * {@link #processSubtree(RuleEnvironment, EObject, ASTMetaInfoCache, int)}), plus a number of smaller phases before
- * and after that where some special handling is performed.
- *
- * @param resource may not be null.
- * @param cancelIndicator may be null.
- */
- def private void processAST(RuleEnvironment G, Script script, ASTMetaInfoCache cache) {
- // phase 0: process compile-time expressions & computed property names (order is important)
- for(node : script.eAllContents.filter(Expression).toIterable) {
- compileTimeExpressionProcessor.evaluateCompileTimeExpression(G, node, cache, 0);
- }
- for(node : script.eAllContents.filter(LiteralOrComputedPropertyName).toIterable) {
- computedNameProcessor.processComputedPropertyName(G, node, cache, 0);
- }
-
- // phase 1: main processing
- processSubtree(G, script, cache, 0);
- // phase 2: processing of postponed subtrees
- var EObject eObj;
- while ((eObj = cache.postponedSubTrees.poll) !== null) {
- // note: we need to allow adding more postponed subtrees inside this loop!
- processSubtree(G, eObj, cache, 0);
- }
- // phase 3: store runtime and load-time dependencies in TModule
- runtimeDependencyProcessor.storeDirectRuntimeDependenciesInTModule(script, cache);
- }
-
- /**
- * Process given node and all of its direct and indirect children.
- *
- * @param node the root of the subtree to process; must be an AST node.
- */
- def package void processSubtree(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) {
- assertTrueIfRigid(cache, "argument 'node' must be an AST node", node.isASTNode);
-
- log(indentLevel, "processing: " + node.objectInfo);
-
- checkCanceled(G);
-
- // already done as part of a forward processing?
- if (cache.forwardProcessedSubTrees.contains(node)) {
- if (isDEBUG_LOG) {
- log(indentLevel, "(subtree already processed as a forward reference)");
- if(node instanceof TypableElement) {
- log(indentLevel, cache.getTypeFailSafe(node));
- }
- }
- return;
- }
- if (cache.postponedSubTrees.contains(node)) {
- // in case this happens, you can either:
- // * not postpone this node, or
- // * handle the postponed node later (not as part of a forward reference)
- throw new IllegalStateException("eager processing of postponed subtree");
- }
-
- if (!cache.astNodesCurrentlyBeingTyped.add(node)) {
- // this subtree is currently being processed
- // (can happen, for example, if we are processing a member (e.g. field) and during that processing we
- // encounter a reference to the containing class (e.g. in the initializer expression))
- if (isDEBUG_LOG) {
- log(indentLevel, "(subtree currently in progress - skipping)");
- }
- return;
- }
-
- try {
- // process node itself - part 1 (before child processing)
- processNode_preChildren(G, node, cache, indentLevel);
-
- // process the children
- val children = childrenToBeProcessed(G, node);
- for (child : children) {
- if (isPostponedNode(child)) {
- // postpone
- cache.postponedSubTrees.add(child);
- } else {
- // process now
- processSubtree(G, child, cache, indentLevel + 1);
- checkCanceled(G);
- }
- }
-
- // process node itself - part 2 (after child processing)
- processNode_postChildren(G, node, cache, indentLevel);
-
- // we're done with this node, but make sure that all proxies have actually been resolved
- // (this is important mainly for two reasons: (1) post-processing is often triggered by a call to
- // N4JSResource#resolveLazyCrossReferences(CancelIndicator), so we have to guarantee that all lazy
- // cross-references are actually resolved; (2) the type system may not resolve all proxies and some
- // nodes are not typed at all (i.e. isTypableNode() returns false), so we have to enforce this here.
-
- // We also perform all processing, related to outgoing references from the current node at this point.
- resolveAndProcessReferencesInNode(node, cache);
-
- } finally {
- cache.astNodesCurrentlyBeingTyped.remove(node);
- }
- }
-
- def private boolean isPostponedNode(EObject node) {
- return isPostponedInitializer(node)
- || N4JSASTUtils.isBodyOfFunctionOrFieldAccessor(node);
- }
-
- /**
- * Initializers are postponed iff:
- *
- * - Node is an initializer of a FormalParameter p,
- * - and p is part of a Poly FunctionExpression f,
- * - and p contains references to other FormalParameters of f, or f itself.
- *
- */
- def private boolean isPostponedInitializer(EObject node) {
- var boolean isPostponedInitializer = false;
- val fpar = node.eContainer;
- if (fpar instanceof FormalParameter) {
- if (node instanceof Expression) {
- if (fpar.hasInitializerAssignment) {
- val funDef = fpar.eContainer;
- // IdentifierRef in Initializers can cause cyclic dependencies
- if (funDef instanceof FunctionExpression) {
- // Check if the initializer refers to other fpars
- val allFPars = funDef.fpars;
- val allRefs = EcoreUtilN4.getAllContentsOfTypeStopAt(fpar, IdentifierRef, N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY);
-
- for (IdentifierRef ir : allRefs) {
- var Object id = ir.getId();
- if (id instanceof SyntaxRelatedTElement) {
- id = id.eGet(TypesPackage.eINSTANCE.syntaxRelatedTElement_AstElement, false);
- }
- val idRefCausesCyclDep =
- allFPars.contains(id) // f(p, q=p) {}
- || id instanceof VariableDeclaration && (id as VariableDeclaration).expression === funDef; // f(p, q=f(1)) {}
- if (idRefCausesCyclDep) {
- isPostponedInitializer = true;
- }
- }
- }
- // In ObjectLiterals, the ThisLiteral in Initializers can cause cyclic dependencies
- // TODO GH-1337 add support for spread operator
- val thisLiteralCausesCyclDep =
- funDef instanceof PropertyMethodDeclaration // let o = { a:1, f(p=this.a) {} }
- || funDef instanceof FunctionExpression && funDef.eContainer instanceof PropertyNameValuePair; // let o = {a:2, f: function(p=this.a) {}}
- if (thisLiteralCausesCyclDep) {
- val containsThisLiteral = EcoreUtilN4.containsContentsOfTypeStopAt(fpar, ThisLiteral, N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY);
- if (containsThisLiteral) {
- isPostponedInitializer = true;
- }
- }
- // If this check is not sufficient, we have to add more checks here. Note: Setters never have initializers.
- }
- }
- }
- return isPostponedInitializer;
- }
-
- /**
- * Forward-process given node and all of its direct and indirect children.
- *
- * Via this method, other processors can request a forward processing of some subtree. Does nothing if the given
- * node was processed already, either as part of a forward reference or during normal processing.
- *
- * @return true
iff the forward processing is legal, false
otherwise.
- */
- def package boolean processSubtree_forwardReference(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache) {
- assertTrueIfRigid(cache, "argument 'node' must be an AST node", node.isASTNode);
-
- // is node a valid target for a forward reference (i.e. an identifiable subtree)?
- val valid = node.isIdentifiableSubtree || node.isExceptionCaseOfForwardReferencableSubtree;
- if (!valid) {
- val resource = node.eResource as XtextResource
- if (resource !== null) {
- assertTrueIfRigid(cache,
- "forward reference only allowed to identifiable subtrees; but was: " + node + " in\n" +
- resource.parseResult.rootNode.text, valid)
- } else {
- assertTrueIfRigid(cache, "forward reference only allowed to identifiable subtrees; but was: " + node, valid);
- }
- }
-
- val fromCache = cache.getTypeFailSafe(node);
- if (fromCache !== null) {
- // already processed, nothing else to do
- // note: this is not an error, we may have many forward references to the same identifiable subtree
- return true;
- }
-
- if (cache.astNodesCurrentlyBeingTyped.contains(node)) {
- // cyclic forward reference
- // legal cases of a cyclic reference
- // TODO GH-1337 add support for spread operator
- val isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node);
- if (isCyclicForwardReference && (
- node instanceof VariableDeclaration
- || node instanceof N4ClassifierDeclaration
- || node instanceof N4FieldDeclaration
- || (node instanceof PropertyNameValuePair && (node as PropertyNameValuePair).expression instanceof FunctionExpression)
- || node instanceof PropertyGetterDeclaration || node instanceof PropertySetterDeclaration
- || (node instanceof Expression && node.eContainer instanceof YieldExpression)
- )) {
- return true;
- }
-
- // illegal cyclic node inference
- val msg = "*#*#*#*#*#* illegal cyclic forward reference to " + node.objectInfo + " (resource: "
- + node.eResource?.URI + ")";
- logErr(msg);
- return false;
- } else if (isSemiCyclicForwardReferenceInForLoop(node, cache)) {
- // semi-cyclic forward reference
- // (this is deemed legal for the same reason why 'var x = 1+x;' is treated as a legal forward reference)
- return true;
- }
-
- if (cache.forwardProcessedSubTrees.contains(node)) {
- // we saw above that the cache does not contain anything for node, so this is an error
- throw new IllegalStateException
- }
-
- // actually perform the forward processing
- log(0, "===START of identifiable sub-tree below " + node.objectInfo);
- val G_fresh = G.newRuleEnvironment; // use a new, empty environment here (but retain cancelIndicator!)
- processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level
- cache.forwardProcessedSubTrees.add(node);
- log(0, "===END of identifiable sub-tree below " + node.objectInfo);
-
- return true;
- }
-
-
- // ---------------------------------------------------------------------------------------------------------------
-
-
- /**
- * Top-down processing of AST nodes happens here, i.e. this method will see all AST nodes in a top-down order.
- */
- def private void processNode_preChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) {
-
- typeRefProcessor.handleTypeRefs(G, node, cache);
-
- if (node instanceof FunctionDefinition) {
- handleAsyncOrGeneratorFunctionDefinition(G, node, cache);
- }
-
- typeDeferredProcessor.handleDeferredTypeRefs_preChildren(G, node, cache);
- }
-
- /**
- * Bottom-up processing of AST nodes happens here, i.e. this method will see all AST nodes in a bottom-up order.
- */
- def private void processNode_postChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) {
-
- typeDeferredProcessor.handleDeferredTypeRefs_postChildren(G, node, cache);
-
- typeProcessor.typeNode(G, node, cache, indentLevel);
-
- // references to other files via import statements
- // NOTE: for all imports except bare imports, the following is unnecessary, because post-processing of the target
- // resource would be triggered automatically as soon as type inference is performed on an element imported from the
- // target resource. However, by doing this eagerly up front, the overall flow of post-processing across multiple
- // resources is a bit easier to understand/predict. This does not lead to any additional processing being done (it's
- // just done a bit earlier), except in case of unused imports.
- if (node instanceof ImportDeclaration) {
- val targetModule = node.module;
- if (targetModule !== null && !targetModule.eIsProxy) {
- val targetResource = targetModule.eResource;
- if (targetResource instanceof N4JSResource) {
- // trigger post-processing of target resource
- targetResource.performPostProcessing(G.cancelIndicator);
- }
- }
- }
-
- if (node instanceof Annotation) {
- if (node.name == AnnotationDefinition.STATIC_POLYFILL_AWARE.name) {
- val resSPoly = staticPolyfillHelper.getStaticPolyfillResource(node.eResource);
- if (resSPoly !== null) {
- // trigger post-processing of poly filler
- resSPoly.performPostProcessing(G.cancelIndicator);
- }
- }
- }
-
- runtimeDependencyProcessor.recordRuntimeReferencesInCache(node, cache);
- }
-
-
- // ---------------------------------------------------------------------------------------------------------------
-
-
- /**
- * This method returns the direct children of 'obj' that are to be processed, in the order in which they are to
- * be processed. By default, all direct children must be processed and the order is insignificant, so in the
- * default case this method simply returns {@link EObject#eContents()}. However, this method implements special
- * handling for some exception cases where the processing order is significant.
- */
- def private List childrenToBeProcessed(RuleEnvironment G, EObject obj) {
- return switch (obj) {
- SetterDeclaration: {
- // process formal parameter before body
- obj.eContents.bringToFront(obj.fpar)
- }
- FunctionDefinition: {
- // process formal parameters before body
- obj.eContents.bringToFront(obj.fpars)
- }
- CatchBlock: {
- // process catch variable before block
- obj.eContents.bringToFront(obj.catchVariable)
- }
- ForStatement: {
- // process expression before varDeclOrBindings
- obj.eContents.bringToFront(obj.expression)
- }
- default: {
- // standard case: order is insignificant (so we simply use the order provided by EMF)
- obj.eContents
- }
- };
- }
-
-
- // ---------------------------------------------------------------------------------------------------------------
-
-
- /**
- * Normally, forward references are allowed only to {@link N4JSLanguageUtils#isIdentifiableSubtree(EObject)
- * identifiable subtrees}. However, there are exception cases that are also allowed and this method returns
- * true
for those cases.
- */
- def private static boolean isExceptionCaseOfForwardReferencableSubtree(EObject astNode) {
- isExpressionInForOf(astNode)
- }
- def private static boolean isExpressionInForOf(EObject astNode) {
- astNode instanceof Expression && astNode.eContainer instanceof ForStatement
- && (astNode.eContainer as ForStatement).isForOf
- && astNode.eContainingFeature===N4JSPackage.eINSTANCE.iterationStatement_Expression;
- }
-
- /**
- * Returns true if we have a semi-cyclic reference to a variable declaration in a for in/of loop.
- * For example:
- *
- * for(var x of foo(x)) {}
- *
- */
- def package boolean isSemiCyclicForwardReferenceInForLoop(EObject node, ASTMetaInfoCache cache) {
- if (node instanceof VariableDeclaration) {
- val parent = node.eContainer;
- if (parent instanceof ForStatement) {
- return (parent.forIn || parent.forOf) && cache.astNodesCurrentlyBeingTyped.contains(parent.expression);
- }
- }
- return false;
- }
-
- def private void resolveAndProcessReferencesInNode(EObject astNode, ASTMetaInfoCache cache) {
- for(eRef : astNode.eClass.EAllReferences) {
- if(!eRef.isContainment && !eRef.isContainer) { // only cross-references have proxies (in our case)
- val node = astNode.eGet(eRef, true);
-
- if (node instanceof EObject) {
- recordReferencesToLocalVariables(eRef, astNode, node, cache);
- }
- }
- }
- }
-
- def private recordReferencesToLocalVariables(EReference reference, EObject sourceNode, EObject target, ASTMetaInfoCache cache) {
-
- // skip reference Variable#definedVariable (it does not constitute a usage of the variable)
- if (reference === N4JSPackage.Literals.ABSTRACT_VARIABLE__DEFINED_VARIABLE) {
- return;
- }
- // If target is still a proxy its resolution failed, therefore it should be skipped.
- if (target.eIsProxy) {
- return;
- }
- // skip non-local references
- if (sourceNode.eResource !== target.eResource) {
- return;
- }
- if (target instanceof TVariable) {
- // don't record references to directly exported variables
- if (target.directlyExported) {
- return;
- }
-
- cache.storeLocalVariableReference(target, sourceNode);
- }
- }
-
- def private List bringToFront(List l, T... elements) {
- val result = new ArrayList(l);
- val elemSanitized = elements.filterNull.toList;
- result.removeAll(elemSanitized);
- result.addAll(0, elemSanitized);
- return result;
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.java
new file mode 100644
index 0000000000..92a9142382
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.java
@@ -0,0 +1,336 @@
+/**
+ * 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.postprocessing;
+
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.wrap;
+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.IterableExtensions.toSet;
+import static org.eclipse.xtext.xbase.lib.IteratorExtensions.exists;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.n4JS.Argument;
+import org.eclipse.n4js.n4JS.ArrayElement;
+import org.eclipse.n4js.n4JS.ArrayLiteral;
+import org.eclipse.n4js.n4JS.ArrowFunction;
+import org.eclipse.n4js.n4JS.ConditionalExpression;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.FunctionDefinition;
+import org.eclipse.n4js.n4JS.FunctionExpression;
+import org.eclipse.n4js.n4JS.ObjectLiteral;
+import org.eclipse.n4js.n4JS.ParameterizedCallExpression;
+import org.eclipse.n4js.n4JS.PropertyAssignment;
+import org.eclipse.n4js.n4JS.PropertyAssignmentAnnotationList;
+import org.eclipse.n4js.n4JS.PropertyGetterDeclaration;
+import org.eclipse.n4js.n4JS.PropertyMethodDeclaration;
+import org.eclipse.n4js.n4JS.PropertyNameValuePair;
+import org.eclipse.n4js.n4JS.PropertySetterDeclaration;
+import org.eclipse.n4js.n4JS.PropertySpread;
+import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.types.InferenceVariable;
+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.ts.types.TypeVariable;
+import org.eclipse.n4js.types.utils.TypeUtils;
+import org.eclipse.n4js.typesystem.N4JSTypeSystem;
+import org.eclipse.n4js.typesystem.constraints.InferenceContext;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions;
+import org.eclipse.n4js.typesystem.utils.TypeSystemHelper;
+import org.eclipse.n4js.typesystem.utils.TypeSystemHelper.Callable;
+import org.eclipse.n4js.utils.N4JSLanguageUtils;
+
+import com.google.inject.Inject;
+
+/**
+ * Base for all poly processors. Contains some utility and convenience methods.
+ */
+abstract class AbstractPolyProcessor extends AbstractProcessor {
+
+ @Inject
+ private N4JSTypeSystem ts;
+ @Inject
+ private TypeSystemHelper tsh;
+
+ /**
+ * Convenience method for {@link #isPoly(Expression)} and {@link #isPoly(PropertyAssignment)}, accepting any type of
+ * EObject.
+ */
+ boolean isPoly(EObject obj) {
+ if (obj instanceof Expression) {
+ return isPoly((Expression) obj);
+ }
+ if (obj instanceof PropertyAssignment) {
+ return isPoly((PropertyAssignment) obj);
+ }
+ return false;
+ }
+
+ /**
+ * Tells whether the given expression is a poly expression, i.e. requires constraint-based type inference.
+ */
+ boolean isPoly(Expression obj) {
+ if (obj instanceof ParameterizedCallExpression) {
+ ParameterizedCallExpression pce = (ParameterizedCallExpression) obj;
+ // NOTE: in next line, we do not propagate the cancel indicator; however, this is not required, because
+ // all we do with the newly created rule environment is to type a backward(!) reference, so we can be
+ // sure that no significant processing will be triggered by the type judgment invocation below
+ RuleEnvironment G = newRuleEnvironment(pce);
+ TypeRef targetTypeRef = ts.type(G, pce.getTarget()); // this is a backward reference (because we type obj's
+ // child)
+ Callable callable = tsh.getCallableTypeRef(G, targetTypeRef);
+ if (callable != null && callable.getSignatureTypeRef().isPresent()) {
+ FunctionTypeExprOrRef signatureTypeRef = callable.getSignatureTypeRef().get();
+ return N4JSLanguageUtils.isPoly(signatureTypeRef, pce);
+ } else {
+ return false;
+ }
+ }
+ if (obj instanceof FunctionExpression) {
+ FunctionExpression fe = (FunctionExpression) obj;
+ return exists(fe.getFpars(), fpar -> fpar.getDeclaredTypeRefInAST() == null)
+ // type of 1 or more fpars is undeclared
+ || fe.getDeclaredReturnTypeRefInAST() == null; // return type is undeclared
+ // note: if the FunctionExpression is generic, this does *not* make it poly!
+ }
+ if (obj instanceof ArrayLiteral) {
+ return true;
+ }
+ if (obj instanceof ObjectLiteral) {
+ return exists(((ObjectLiteral) obj).getPropertyAssignments(), pa -> isPoly(pa));
+ }
+ if (obj instanceof ConditionalExpression) {
+ ConditionalExpression ce = (ConditionalExpression) obj;
+ boolean trueIsPoly = isPoly(ce.getTrueExpression());
+ boolean trueAllowsPoly = allowsPoly(ce.getTrueExpression());
+ boolean falseAllowsPoly = allowsPoly(ce.getFalseExpression());
+ boolean falseIsPoly = isPoly(ce.getFalseExpression());
+
+ return (trueIsPoly && falseAllowsPoly) || (falseIsPoly && trueAllowsPoly);
+ }
+
+ return false;
+
+ }
+
+ boolean allowsPoly(Expression obj) {
+ if (obj == null) {
+ return false;
+ }
+ RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(obj);
+ return N4JSLanguageUtils.isUndefinedLiteral(G, obj) || N4JSLanguageUtils.isNullLiteral(G, obj);
+ }
+
+ /**
+ * True iff the given PropertyAssignment is a poly "expression", i.e. requires constraint-based type inference.
+ */
+ private boolean isPoly(PropertyAssignment pa) {
+ if (pa instanceof PropertyNameValuePair) {
+ PropertyNameValuePair pnvp = (PropertyNameValuePair) pa;
+ // FIXME requiring pa.expression!=null is inconsistent!
+ return pnvp.getExpression() != null && pnvp.getDeclaredTypeRefInAST() == null;
+ }
+ if (pa instanceof PropertyGetterDeclaration) {
+ PropertyGetterDeclaration pgd = (PropertyGetterDeclaration) pa;
+ return pgd.getDeclaredTypeRefInAST() == null;
+ }
+ if (pa instanceof PropertySetterDeclaration) {
+ PropertySetterDeclaration psd = (PropertySetterDeclaration) pa;
+ return psd.getDeclaredTypeRefInAST() == null;
+ }
+ if (pa instanceof PropertyMethodDeclaration) {
+ return false;
+ }
+ if (pa instanceof PropertySpread) {
+ return false; // TODO GH-1337 add support for spread operator
+ }
+ if (pa instanceof PropertyAssignmentAnnotationList) {
+ return false;
+ }
+
+ throw new IllegalArgumentException("unsupported subclass of PropertyAssignment: " + pa.eClass().getName());
+ }
+
+ /**
+ * Convenience method for {@link #isRootPoly(Expression)}, accepting any type of EObject.
+ */
+ boolean isRootPoly(EObject obj) {
+ return (obj instanceof Expression) ? isRootPoly((Expression) obj) : false;
+ }
+
+ /**
+ * Tells whether the given expression is a root poly expression, i.e. it
+ *
+ * - is a {@link #isPoly(Expression) poly expression}, and
+ *
- represents the root of a tree of nested poly expressions which have to be inferred together within a single
+ * constraint system (this tree may have depth 0, i.e. consist only of the given expression).
+ *
+ */
+ boolean isRootPoly(Expression obj) {
+ if (isPoly(obj)) {
+ EObject p = getParentPolyCandidate(obj);
+ return p == null || !isPoly(p);
+ }
+ return false;
+ }
+
+ /**
+ * Given a poly expression, returns the parent expression that might be the parent poly expression. If the
+ * given expression is not poly, the return value is undefined.
+ */
+ private EObject getParentPolyCandidate(Expression poly) {
+ EObject directParent = poly == null ? null : poly.eContainer();
+ EObject grandParent = directParent == null ? null : directParent.eContainer();
+
+ if (directParent instanceof Argument) {
+ if (grandParent instanceof ParameterizedCallExpression && toSet(map(
+ ((ParameterizedCallExpression) grandParent).getArguments(), a -> a.getExpression()))
+ .contains(poly)) {
+ // TODO what about the target expression? i.e.: || directParent.target==poly) {
+ return grandParent;
+ }
+ } else if (directParent instanceof FunctionExpression) {
+ return null; // function expressions never have nested poly expressions (expression in the body are
+ // detached)
+ } else if (directParent instanceof ArrayElement) {
+ if (((ArrayElement) directParent).getExpression() == poly) {
+ return directParent.eContainer();// return the ArrayLiteral as parent (not the ArrayElement)
+ }
+ } else if (directParent instanceof PropertyNameValuePair) {
+ if (((PropertyNameValuePair) directParent).getExpression() == poly) {
+ return directParent;// return the PropertyNameValuePair as parent (not the ObjectLiteral)
+ }
+ } else if (directParent instanceof ConditionalExpression) {
+ return directParent;
+ } else if (directParent instanceof PropertyGetterDeclaration) {
+ return null;// getters never have nested poly expressions
+ } else if (directParent instanceof PropertySetterDeclaration) {
+ return null; // setters never have nested poly expressions
+ } else if (directParent instanceof PropertySpread) {
+ return null;// TODO GH-1337 add support for spread operator
+ }
+
+ return null;
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the type of a nested poly expression. The final type is returned, i.e. not the one created when preparing
+ * the constraint system that may contain inference variables.
+ *
+ * Because final types are created and stored in the typing cache in the onSuccess/onFailure lambdas and those
+ * lambdas of nested poly expressions are registered before those of outer expression, we can here simply read the
+ * nested poly expression's type from the cache.
+ */
+ protected TypeRef getFinalResultTypeOfNestedPolyExpression(Expression nestedPolyExpression) {
+ return ASTMetaInfoUtils.getTypeFailSafe(nestedPolyExpression);
+ }
+
+ protected TypeRef subst(TypeRef typeRef, RuleEnvironment G,
+ Map substitutions) {
+
+ return subst(typeRef, G, substitutions, false);
+ }
+
+ protected TypeRef subst(TypeRef typeRef, RuleEnvironment G,
+ Map substitutions, boolean reverse) {
+
+ RuleEnvironment Gx = wrap(G);
+ for (Entry e : substitutions.entrySet()) {
+ if (reverse) {
+ Gx.put(e.getValue(), TypeUtils.createTypeRef(e.getKey()));
+ } else {
+ Gx.put(e.getKey(), TypeUtils.createTypeRef(e.getValue()));
+ }
+ }
+
+ TypeRef typeRefSubst = ts.substTypeVariables(Gx, typeRef);
+ if (typeRefSubst == null) {
+ throw new IllegalArgumentException("substitution failed");
+ }
+ return typeRefSubst;
+ }
+
+ protected TypeRef applySolution(TypeRef typeRef, RuleEnvironment G, Map solution) {
+ if (typeRef == null || solution == null || solution.isEmpty()) {
+ return typeRef; // note: returning 'null' if typeRef==null (broken AST, etc.)
+ }
+ RuleEnvironment Gx = wrap(G);
+ for (Entry e : solution.entrySet()) {
+ Gx.put(e.getKey(), e.getValue());
+ }
+ TypeRef typeRefSubst = ts.substTypeVariables(Gx, typeRef);
+ if (typeRefSubst == null) {
+ throw new IllegalArgumentException("substitution failed");
+ }
+ return typeRefSubst;
+ }
+
+ protected Map createPseudoSolution(InferenceContext infCtx,
+ TypeRef defaultTypeRef) {
+
+ Map pseudoSolution = new HashMap<>();
+ for (InferenceVariable iv : infCtx.getInferenceVariables()) {
+ pseudoSolution.put(iv, defaultTypeRef); // map all inference variables to the default
+ }
+ return pseudoSolution;
+ }
+
+ // FIXME move to a better place
+ protected boolean isReturningValue(FunctionDefinition fun) {
+ return (fun.getBody() != null && exists(fun.getBody().getAllReturnStatements(), s -> s.getExpression() != null))
+ || ((fun instanceof ArrowFunction) ? ((ArrowFunction) fun).isSingleExprImplicitReturn() : false);
+ // TODO except call to void function!!
+ }
+
+ protected TypeRef getTypeOfMember(TMember m) {
+ if (m instanceof TField) {
+ return ((TField) m).getTypeRef();
+ } else if (m instanceof TGetter) {
+ return ((TGetter) m).getTypeRef();
+ } else if (m instanceof TSetter) {
+ return ((TSetter) m).getFpar().getTypeRef();
+ } else if (m instanceof TMethod) {
+ throw new IllegalArgumentException("this method should not be used for TMethod");
+ } else {
+ String clsName = m == null ? null : m.eClass() == null ? null : m.eClass().getName();
+ throw new IllegalArgumentException("unknown subtype of TMember: " + clsName);
+ }
+ }
+
+ protected void setTypeOfMember(TMember m, TypeRef type) {
+ if (m instanceof TField) {
+ ((TField) m).setTypeRef(type);
+ } else if (m instanceof TGetter) {
+ ((TGetter) m).setTypeRef(type);
+ } else if (m instanceof TSetter) {
+ TSetter tst = (TSetter) m;
+ if (tst.getFpar() != null) {
+ tst.getFpar().setTypeRef(type);
+ }
+ } else if (m instanceof TMethod) {
+ throw new IllegalArgumentException("this method should not be used for TMethod");
+ } else {
+ String clsName = m == null ? null : m.eClass() == null ? null : m.eClass().getName();
+ throw new IllegalArgumentException("unknown subtype of TMember: " + clsName);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.xtend
deleted file mode 100644
index 8d3b99d5f3..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.xtend
+++ /dev/null
@@ -1,289 +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.postprocessing
-
-import com.google.inject.Inject
-import java.util.Map
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.n4js.n4JS.Argument
-import org.eclipse.n4js.n4JS.ArrayElement
-import org.eclipse.n4js.n4JS.ArrayLiteral
-import org.eclipse.n4js.n4JS.ArrowFunction
-import org.eclipse.n4js.n4JS.ConditionalExpression
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.FunctionDefinition
-import org.eclipse.n4js.n4JS.FunctionExpression
-import org.eclipse.n4js.n4JS.ObjectLiteral
-import org.eclipse.n4js.n4JS.ParameterizedCallExpression
-import org.eclipse.n4js.n4JS.PropertyAssignment
-import org.eclipse.n4js.n4JS.PropertyAssignmentAnnotationList
-import org.eclipse.n4js.n4JS.PropertyGetterDeclaration
-import org.eclipse.n4js.n4JS.PropertyMethodDeclaration
-import org.eclipse.n4js.n4JS.PropertyNameValuePair
-import org.eclipse.n4js.n4JS.PropertySetterDeclaration
-import org.eclipse.n4js.n4JS.PropertySpread
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-import org.eclipse.n4js.ts.types.InferenceVariable
-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.ts.types.TypeVariable
-import org.eclipse.n4js.types.utils.TypeUtils
-import org.eclipse.n4js.typesystem.N4JSTypeSystem
-import org.eclipse.n4js.typesystem.constraints.InferenceContext
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions
-import org.eclipse.n4js.typesystem.utils.TypeSystemHelper
-import org.eclipse.n4js.utils.N4JSLanguageUtils
-
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-
-/**
- * Base for all poly processors. Contains some utility and convenience methods.
- */
-package abstract class AbstractPolyProcessor extends AbstractProcessor {
-
- @Inject
- private N4JSTypeSystem ts;
- @Inject
- private TypeSystemHelper tsh;
-
- /**
- * Convenience method for {@link #isPoly(Expression)} and {@link #isPoly(PropertyAssignment)}, accepting any type of
- * EObject.
- */
- def boolean isPoly(EObject obj) {
- return switch (obj) {
- Expression: obj.isPoly
- PropertyAssignment: obj.isPoly
- default: false
- }
- }
-
- /**
- * Tells whether the given expression is a poly expression, i.e. requires constraint-based type inference.
- */
- def boolean isPoly(Expression obj) {
- return switch (obj) {
- ParameterizedCallExpression: {
- // NOTE: in next line, we do not propagate the cancel indicator; however, this is not required, because
- // all we do with the newly created rule environment is to type a backward(!) reference, so we can be
- // sure that no significant processing will be triggered by the type judgment invocation below
- val G = obj.newRuleEnvironment;
- val TypeRef targetTypeRef = ts.type(G, obj.target); // this is a backward reference (because we type obj's child)
- val callable = tsh.getCallableTypeRef(G, targetTypeRef);
- if (callable !== null && callable.signatureTypeRef.present) {
- val signatureTypeRef = callable.signatureTypeRef.get();
- N4JSLanguageUtils.isPoly(signatureTypeRef, obj)
- } else {
- false
- }
- }
- FunctionExpression:
- obj.fpars.exists[declaredTypeRefInAST === null] // type of 1 or more fpars is undeclared
- || obj.declaredReturnTypeRefInAST === null // return type is undeclared
- // note: if the FunctionExpression is generic, this does *not* make it poly!
- ArrayLiteral:
- true
- ObjectLiteral:
- obj.propertyAssignments.exists[isPoly]
- ConditionalExpression: {
- val boolean trueIsPoly = isPoly(obj.trueExpression);
- val boolean trueAllowsPoly = allowsPoly(obj.trueExpression);
- val boolean falseAllowsPoly = allowsPoly(obj.falseExpression);
- val boolean falseIsPoly = isPoly(obj.falseExpression);
-
- (trueIsPoly && falseAllowsPoly) || (falseIsPoly && trueAllowsPoly)
- }
- default:
- false
- }
- }
-
- def boolean allowsPoly(Expression obj) {
- if (obj === null) {
- return false;
- }
- val RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(obj);
- return N4JSLanguageUtils.isUndefinedLiteral(G, obj) || N4JSLanguageUtils.isNullLiteral(G, obj);
- }
-
- /**
- * Tells whether the given PropertyAssignment is a poly "expression", i.e. requires constraint-based type inference.
- */
- def private boolean isPoly(PropertyAssignment pa) {
- switch (pa) {
- PropertyNameValuePair:
- pa.expression !== null && pa.declaredTypeRefInAST === null // FIXME requiring pa.expression!==null is inconsistent!
- PropertyGetterDeclaration:
- pa.declaredTypeRefInAST === null
- PropertySetterDeclaration:
- pa.declaredTypeRefInAST === null
- PropertyMethodDeclaration:
- false
- PropertySpread:
- false // TODO GH-1337 add support for spread operator
- PropertyAssignmentAnnotationList:
- false
- default:
- throw new IllegalArgumentException("unsupported subclass of PropertyAssignment: " + pa.eClass.name)
- }
- }
-
- /**
- * Convenience method for {@link #isRootPoly(Expression)}, accepting any type of EObject.
- */
- def boolean isRootPoly(EObject obj) {
- if (obj instanceof Expression) obj.isRootPoly else false
- }
-
- /**
- * Tells whether the given expression is a root poly expression, i.e. it
- *
- * - is a {@link #isPoly(Expression) poly expression}, and
- *
- represents the root of a tree of nested poly expressions which have to be inferred together within a single
- * constraint system (this tree may have depth 0, i.e. consist only of the given expression).
- *
- */
- def boolean isRootPoly(Expression obj) {
- if (isPoly(obj)) {
- val p = getParentPolyCandidate(obj);
- return p === null || !isPoly(p);
- }
- return false;
- }
-
- /**
- * Given a poly expression, returns the parent expression that might be the parent poly expression.
- * If the given expression is not poly, the return value is undefined.
- */
- def private EObject getParentPolyCandidate(Expression poly) {
- val directParent = poly?.eContainer;
- val grandParent = directParent?.eContainer;
- return switch (directParent) {
- Argument case grandParent instanceof ParameterizedCallExpression &&
- (grandParent as ParameterizedCallExpression).arguments.map[expression].contains(poly): // TODO what about the target expression? i.e.: || directParent.target===poly
- grandParent
- FunctionExpression:
- null // function expressions never have nested poly expressions (expression in the body are detached)
- ArrayElement case directParent.expression === poly:
- directParent.eContainer as ArrayLiteral // return the ArrayLiteral as parent (not the ArrayElement)
- PropertyNameValuePair case directParent.expression === poly:
- directParent // return the PropertyNameValuePair as parent (not the ObjectLiteral)
- ConditionalExpression:
- directParent
- PropertyGetterDeclaration:
- null // getters never have nested poly expressions
- PropertySetterDeclaration:
- null // setters never have nested poly expressions
- PropertySpread:
- null // TODO GH-1337 add support for spread operator
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------------------------------------
-
-
- /**
- * Returns the type of a nested poly expression. The final type is returned, i.e. not the one created when preparing
- * the constraint system that may contain inference variables.
- *
- * Because final types are created and stored in the typing cache in the onSuccess/onFailure lambdas and those
- * lambdas of nested poly expressions are registered before those of outer expression, we can here simply read the
- * nested poly expression's type from the cache.
- */
- def protected TypeRef getFinalResultTypeOfNestedPolyExpression(Expression nestedPolyExpression) {
- return ASTMetaInfoUtils.getTypeFailSafe(nestedPolyExpression);
- }
-
- def protected TypeRef subst(TypeRef typeRef, RuleEnvironment G,
- Map substitutions) {
-
- subst(typeRef, G, substitutions, false)
- }
-
- def protected TypeRef subst(TypeRef typeRef, RuleEnvironment G,
- Map substitutions, boolean reverse) {
-
- val Gx = G.wrap;
- substitutions.entrySet.forEach [ e |
- if (reverse)
- Gx.put(e.value, TypeUtils.createTypeRef(e.key))
- else
- Gx.put(e.key, TypeUtils.createTypeRef(e.value))
- ];
- val typeRefSubst = ts.substTypeVariables(Gx, typeRef);
- if (typeRefSubst === null)
- throw new IllegalArgumentException("substitution failed");
- return typeRefSubst;
- }
-
- def protected TypeRef applySolution(TypeRef typeRef, RuleEnvironment G, Map solution) {
- if (typeRef === null || solution === null || solution.empty) {
- return typeRef; // note: returning 'null' if typeRef==null (broken AST, etc.)
- }
- val Gx = G.wrap;
- solution.entrySet.forEach[e|Gx.put(e.key, e.value)];
- val typeRefSubst = ts.substTypeVariables(Gx, typeRef);
- if (typeRefSubst === null)
- throw new IllegalArgumentException("substitution failed");
- return typeRefSubst;
- }
-
- def protected Map createPseudoSolution(InferenceContext infCtx,
- TypeRef defaultTypeRef) {
-
- val pseudoSolution = newHashMap;
- for (iv : infCtx.getInferenceVariables) {
- pseudoSolution.put(iv, defaultTypeRef); // map all inference variables to the default
- }
- return pseudoSolution;
- }
-
- // FIXME move to a better place
- def protected boolean isReturningValue(FunctionDefinition fun) {
- return (fun.body !== null && fun.body.allReturnStatements.exists[expression !== null]) ||
- (if (fun instanceof ArrowFunction) fun.singleExprImplicitReturn else false); // TODO except call to void function!!
- }
-
- def protected TypeRef getTypeOfMember(TMember m) {
- switch (m) {
- TField:
- m.typeRef
- TGetter:
- m.typeRef
- TSetter:
- m?.fpar.typeRef
- TMethod:
- throw new IllegalArgumentException("this method should not be used for TMethod")
- default:
- throw new IllegalArgumentException("unknown subtype of TMember: " + m?.eClass?.name)
- }
- }
-
- def protected void setTypeOfMember(TMember m, TypeRef type) {
- switch (m) {
- TField:
- m.typeRef = type
- TGetter:
- m.typeRef = type
- TSetter:
- if (m.fpar !== null) m.fpar.typeRef = type
- TMethod:
- throw new IllegalArgumentException("this method should not be used for TMethod")
- default:
- throw new IllegalArgumentException("unknown subtype of TMember: " + m?.eClass?.name)
- }
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.java
new file mode 100644
index 0000000000..b1724b5eab
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.java
@@ -0,0 +1,270 @@
+/**
+ * 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.postprocessing;
+
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getBuiltInTypeScope;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator;
+import static org.eclipse.n4js.utils.N4JSLanguageUtils.isASTNode;
+import static org.eclipse.n4js.utils.N4JSLanguageUtils.isTypableNode;
+
+import java.util.function.BooleanSupplier;
+
+import org.apache.log4j.Logger;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.n4JS.FunctionDefinition;
+import org.eclipse.n4js.n4JS.IdentifierRef;
+import org.eclipse.n4js.n4JS.NamedElement;
+import org.eclipse.n4js.resource.N4JSResource;
+import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope;
+import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef;
+import org.eclipse.n4js.ts.typeRefs.TypeArgument;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory;
+import org.eclipse.n4js.ts.types.IdentifiableElement;
+import org.eclipse.n4js.ts.types.TFunction;
+import org.eclipse.n4js.ts.types.TStructMember;
+import org.eclipse.n4js.ts.types.TypableElement;
+import org.eclipse.n4js.ts.types.Type;
+import org.eclipse.n4js.types.utils.TypeUtils;
+import org.eclipse.n4js.typesystem.N4JSTypeSystem;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.typesystem.utils.TypeSystemHelper;
+import org.eclipse.n4js.utils.EcoreUtilN4;
+import org.eclipse.n4js.utils.N4JSLanguageUtils;
+import org.eclipse.n4js.utils.UtilN4;
+import org.eclipse.xtext.nodemodel.ICompositeNode;
+import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
+import org.eclipse.xtext.service.OperationCanceledManager;
+import org.eclipse.xtext.util.CancelIndicator;
+
+import com.google.common.base.Throwables;
+import com.google.inject.Inject;
+
+/**
+ * Provides some common base functionality used across all processors (e.g. logging). See {@link ASTProcessor} for more
+ * details on processors and post-processing of {@link N4JSResource}s.
+ */
+abstract class AbstractProcessor {
+
+ private static Logger LOG = Logger.getLogger(AbstractProcessor.class);
+
+ private static boolean DEBUG_LOG = false;
+ private static boolean DEBUG_LOG_RESULT = false;
+ private static boolean DEBUG_RIGID = false; // if true, more consistency checks are performed and exceptions thrown
+ // if wrong
+
+ @Inject
+ private N4JSTypeSystem ts;
+ @Inject
+ private TypeSystemHelper tsh;
+ @Inject
+ private OperationCanceledManager operationCanceledManager;
+
+ /**
+ * Convenience method. Same as {@link OperationCanceledManager#checkCanceled(CancelIndicator)}, using the cancel
+ * indicator of the given rule environment.
+ */
+ protected void checkCanceled(RuleEnvironment G) {
+ operationCanceledManager.checkCanceled(getCancelIndicator(G));
+ }
+
+ /**
+ * Processors can call this method to directly invoke the 'type' judgment, i.e. invoke method
+ * {@code TypeJudgment#apply()} via facade method
+ * {@link N4JSTypeSystem#use_type_judgment_from_PostProcessors(RuleEnvironment, TypableElement)
+ * use_type_judgment_from_PostProcessors()}. Normally, this should only be required by {@link TypeProcessor}, so use
+ * this sparingly (however, sometimes it can be helpful to avoid duplication of logic).
+ */
+ protected TypeRef invokeTypeJudgmentToInferType(RuleEnvironment G, TypableElement elem) {
+ if (elem.eIsProxy()) {
+ return TypeRefsFactory.eINSTANCE.createUnknownTypeRef();
+ }
+ // special case:
+ // TStructMembers are special in that they may be types (in case of TStructMethod) and appear as AST nodes
+ // -> if we are dealing with an AST node, make sure to use the definedMember in the TModule
+ TStructMember definedMember = (elem instanceof TStructMember)
+ ? ((TStructMember) elem).getDefinedMember()
+ : null;
+ if (definedMember != null && isASTNode(elem)) {
+ return invokeTypeJudgmentToInferType(G, definedMember);
+ }
+ return ts.use_type_judgment_from_PostProcessors(G, elem);
+ }
+
+ /**
+ * Some special handling for async and/or generator functions (including methods): we have to wrap their inner
+ * return type R
into a {@code Promise}, {@code Generator}, or
+ * {@code AsyncGenerator} and use that as their actual, outer return type. This means for async and/or
+ * generator functions, the types builder will create a TFunction
with the inner return type and during
+ * post-processing this method will change that return type to a
+ * Promise
/Generator
/AsyncGenerator
(only the return type of the TFunction in
+ * the types model is changed; the declared return type in the AST remains unchanged).
+ *
+ * In addition, a return type of void
will be replaced by undefined
, i.e. will produce an
+ * outer return type of Promise<undefined,?>
, Generator<undefined,undefined,TNext>
,
+ * etc. This will be taken care of by utility methods
+ * {@link TypeUtils#createPromiseTypeRef(BuiltInTypeScope,TypeArgument,TypeArgument)} and
+ * {@link TypeUtils#createGeneratorTypeRef(BuiltInTypeScope,FunctionDefinition)}, respectively.
+ *
+ * NOTES:
+ *
+ * - normally, this wrapping could easily be done in the types builder, but because we have to check if the inner
+ * return type is
void
we need to resolve proxies, which is not allowed in the types builder.
+ *
+ */
+ protected void handleAsyncOrGeneratorFunctionDefinition(RuleEnvironment G, FunctionDefinition funDef) {
+ boolean isAsync = funDef.isAsync();
+ boolean isGenerator = funDef.isGenerator();
+ if (isAsync || isGenerator) {
+ Type tFunction = funDef.getDefinedType();
+ if (tFunction instanceof TFunction) {
+ TFunction tFun = (TFunction) tFunction;
+ TypeRef innerReturnTypeRef = tFun.getReturnTypeRef();
+ if (innerReturnTypeRef != null && !(innerReturnTypeRef instanceof DeferredTypeRef)) {
+ // we took 'innerReturnTypeRef' from the TModule (not the AST), so normally we would not have to
+ // invoke #resolveTypeAliases() here; however, since this code is running before TypeAliasProcessor,
+ // we still have to invoke #resolveTypeAliases():
+ TypeRef innerReturnTypeRefResolved = tsh.resolveTypeAliases(G, innerReturnTypeRef);
+ TypeRef innerReturnTypeRefResolvedUB = ts.upperBoundWithReopenAndResolveTypeVars(G,
+ innerReturnTypeRefResolved);
+ BuiltInTypeScope scope = getBuiltInTypeScope(G);
+ boolean needsRewrite = !N4JSLanguageUtils.hasExpectedSpecialReturnType(innerReturnTypeRefResolvedUB,
+ funDef, scope);
+ if (needsRewrite) {
+ TypeRef outerReturnTypeRef = (!isGenerator)
+ ? TypeUtils.createPromiseTypeRef(scope, innerReturnTypeRef, null)
+ :
+ // note: this method handles the choice Generator vs. AsyncGenerator
+ TypeUtils.createGeneratorTypeRef(scope, funDef);
+ EcoreUtilN4.doWithDeliver(false, () -> tFun.setReturnTypeRef(outerReturnTypeRef), tFun);
+ }
+ }
+ }
+ }
+ }
+
+ protected static String getObjectInfo(EObject obj) {
+ if (obj == null) {
+ return "";
+ } else if (obj instanceof IdentifierRef) {
+ ICompositeNode node = NodeModelUtils.findActualNodeFor(obj);
+ if (node == null) {
+ return "";
+ } else {
+ return "IdentifierRef \"" + NodeModelUtils.getTokenText(node) + "\"";
+ }
+ } else {
+ String name = getName(obj);
+ if (name != null) {
+ return obj.eClass().getName() + " \"" + name + "\"";
+ } else {
+ return obj.eClass().getName();
+ }
+ }
+ }
+
+ protected static String getName(EObject obj) {
+ if (obj instanceof NamedElement) {
+ return ((NamedElement) obj).getName();
+ }
+ if (obj instanceof IdentifiableElement) {
+ return ((IdentifiableElement) obj).getName();
+ }
+ return null;
+ }
+
+ protected static void log(int indentLevel, TypeRef result) {
+ if (!isDEBUG_LOG()) {
+ return;
+ }
+ log(indentLevel, result.getTypeRefAsString());
+ }
+
+ protected static void log(int indentLevel, EObject astNode, ASTMetaInfoCache cache) {
+ if (!isDEBUG_LOG())
+ return;
+ if (isTypableNode(astNode)) {
+ TypeRef result = cache.getTypeFailSafe((TypableElement) astNode);
+ String resultStr = (result != null) ? result.getTypeRefAsString() : "*** MISSING ***";
+ log(indentLevel, getObjectInfo(astNode) + " " + resultStr);
+ } else {
+ log(indentLevel, getObjectInfo(astNode));
+ }
+ for (EObject childNode : astNode.eContents()) {
+ log(indentLevel + 1, childNode, cache);
+ }
+ }
+
+ protected static void log(int indentLevel, String msg) {
+ if (!isDEBUG_LOG()) {
+ return;
+ }
+ System.out.println(indent(indentLevel) + msg);
+ }
+
+ protected static void logErr(String msg) {
+ // always log errors, even if !isDEBUG_LOG()
+ System.out.flush();
+ System.err.println(msg);
+ System.err.flush();
+ LOG.error(msg);
+ }
+
+ protected static Throwable logException(String msg, Throwable th) {
+ // always log exceptions, even if !isDEBUG_LOG()
+ th.printStackTrace(); // enforce dumping all exceptions to stderr
+ // GH-2002: TEMPORARY DEBUG LOGGING
+ // Only passing the exception to Logger#error(String,Throwable) does not emit the stack trace of the caught
+ // exception in all logger configurations; we therefore include the stack trace in the main message:
+ LOG.error(msg + "\n" + Throwables.getStackTraceAsString(th), th);
+ return th;
+ }
+
+ protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, BooleanSupplier check) {
+ if (isDEBUG_RIGID()) {
+ assertTrueIfRigid(cache, message, check.getAsBoolean());
+ }
+ }
+
+ protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, boolean actual) {
+ if (isDEBUG_RIGID() && !actual) {
+ Error e = new Error(message);
+ if (!cache.hasBrokenAST()) {
+ // make sure we see this exception on the console, even if it gets caught somewhere
+ UtilN4.reportError(e);
+ }
+ Throwables.throwIfUnchecked(e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ // using a method to read field DEBUG_LOG to get rid of Xtend's "Constant condition is always true|false." warnings
+ protected static boolean isDEBUG_LOG() {
+ return DEBUG_LOG;
+ }
+
+ protected static boolean isDEBUG_LOG_RESULT() {
+ return DEBUG_LOG_RESULT;
+ }
+
+ protected static boolean isDEBUG_RIGID() {
+ return DEBUG_RIGID;
+ }
+
+ protected static String indent(int indentLevel) {
+ String res = "";
+ for (int i = 0; i < indentLevel; i++) {
+ res += " ";
+ }
+ return res;
+
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.xtend
deleted file mode 100644
index 158e1d1d2a..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.xtend
+++ /dev/null
@@ -1,247 +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.postprocessing
-
-import com.google.common.base.Throwables
-import com.google.inject.Inject
-import java.util.function.BooleanSupplier
-import org.apache.log4j.Logger
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.n4js.n4JS.FunctionDefinition
-import org.eclipse.n4js.n4JS.IdentifierRef
-import org.eclipse.n4js.n4JS.NamedElement
-import org.eclipse.n4js.resource.N4JSResource
-import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory
-import org.eclipse.n4js.ts.types.IdentifiableElement
-import org.eclipse.n4js.ts.types.TFunction
-import org.eclipse.n4js.ts.types.TStructMember
-import org.eclipse.n4js.ts.types.TypableElement
-import org.eclipse.n4js.types.utils.TypeUtils
-import org.eclipse.n4js.typesystem.N4JSTypeSystem
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.typesystem.utils.TypeSystemHelper
-import org.eclipse.n4js.utils.EcoreUtilN4
-import org.eclipse.n4js.utils.N4JSLanguageUtils
-import org.eclipse.n4js.utils.UtilN4
-import org.eclipse.xtext.nodemodel.util.NodeModelUtils
-import org.eclipse.xtext.service.OperationCanceledManager
-
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.*
-
-/**
- * Provides some common base functionality used across all processors (e.g. logging). See {@link ASTProcessor} for more
- * details on processors and post-processing of {@link N4JSResource}s.
- */
-package abstract class AbstractProcessor {
-
- val private static Logger LOG = Logger.getLogger(AbstractProcessor);
-
- val private static DEBUG_LOG = false;
- val private static DEBUG_LOG_RESULT = false;
- val private static DEBUG_RIGID = false; // if true, more consistency checks are performed and exceptions thrown if wrong
-
- @Inject
- private N4JSTypeSystem ts;
- @Inject
- private TypeSystemHelper tsh;
- @Inject
- private OperationCanceledManager operationCanceledManager;
-
-
- /**
- * Convenience method. Same as {@link OperationCanceledManager#checkCanceled(CancelIndicator)}, using the cancel
- * indicator of the given rule environment.
- */
- def protected void checkCanceled(RuleEnvironment G) {
- operationCanceledManager.checkCanceled(G.cancelIndicator);
- }
-
-
- /**
- * Processors can call this method to directly invoke the 'type' judgment, i.e. invoke method {@code TypeJudgment#apply()}
- * via facade method {@link N4JSTypeSystem#use_type_judgment_from_PostProcessors(RuleEnvironment, TypableElement)
- * use_type_judgment_from_PostProcessors()}. Normally, this should only be required by {@link TypeProcessor}, so use
- * this sparingly (however, sometimes it can be helpful to avoid duplication of logic).
- */
- def protected TypeRef invokeTypeJudgmentToInferType(RuleEnvironment G, TypableElement elem) {
- if (elem.eIsProxy) {
- return TypeRefsFactory.eINSTANCE.createUnknownTypeRef;
- }
- // special case:
- // TStructMembers are special in that they may be types (in case of TStructMethod) and appear as AST nodes
- // -> if we are dealing with an AST node, make sure to use the definedMember in the TModule
- val definedMember = if (elem instanceof TStructMember) elem.definedMember;
- if (definedMember !== null && elem.isASTNode) {
- return invokeTypeJudgmentToInferType(G, definedMember);
- }
- return ts.use_type_judgment_from_PostProcessors(G, elem);
- }
-
-
- /**
- * Some special handling for async and/or generator functions (including methods): we have to wrap their inner return type
- * R
into a {@code Promise}, {@code Generator}, or {@code AsyncGenerator} and use
- * that as their actual, outer return type. This means for async and/or generator functions, the types builder will create
- * a TFunction
with the inner return type and during post-processing this method will change that return type
- * to a Promise
/Generator
/AsyncGenerator
(only the return type of the TFunction in
- * the types model is changed; the declared return type in the AST remains unchanged).
- *
- * In addition, a return type of void
will be replaced by undefined
, i.e. will produce an outer
- * return type of Promise<undefined,?>
, Generator<undefined,undefined,TNext>
, etc. This will
- * be taken care of by utility methods {@link TypeUtils#createPromiseTypeRef(BuiltInTypeScope,TypeArgument,TypeArgument)}
- * and {@link TypeUtils#createGeneratorTypeRef(BuiltInTypeScope,FunctionDefinition)}, respectively.
- *
- * NOTES:
- *
- * - normally, this wrapping could easily be done in the types builder, but because we have to check if the inner
- * return type is
void
we need to resolve proxies, which is not allowed in the types builder.
- *
- */
- def protected void handleAsyncOrGeneratorFunctionDefinition(RuleEnvironment G, FunctionDefinition funDef, ASTMetaInfoCache cache) {
- val isAsync = funDef.isAsync;
- val isGenerator = funDef.isGenerator;
- if(isAsync || isGenerator) {
- val tFunction = funDef.definedType;
- if(tFunction instanceof TFunction) {
- val innerReturnTypeRef = tFunction.returnTypeRef;
- if (innerReturnTypeRef !== null && !(innerReturnTypeRef instanceof DeferredTypeRef)) {
- // we took 'innerReturnTypeRef' from the TModule (not the AST), so normally we would not have to
- // invoke #resolveTypeAliases() here; however, since this code is running before TypeAliasProcessor,
- // we still have to invoke #resolveTypeAliases():
- val innerReturnTypeRefResolved = tsh.resolveTypeAliases(G, innerReturnTypeRef);
- val innerReturnTypeRefResolvedUB = ts.upperBoundWithReopenAndResolveTypeVars(G, innerReturnTypeRefResolved);
- val scope = G.builtInTypeScope;
- val needsRewrite = !N4JSLanguageUtils.hasExpectedSpecialReturnType(innerReturnTypeRefResolvedUB, funDef, scope);
- if (needsRewrite) {
- val outerReturnTypeRef = if (!isGenerator) {
- TypeUtils.createPromiseTypeRef(scope, innerReturnTypeRef, null);
- } else {
- TypeUtils.createGeneratorTypeRef(scope, funDef); // note: this method handles the choice Generator vs. AsyncGenerator
- };
- EcoreUtilN4.doWithDeliver(false, [
- tFunction.returnTypeRef = outerReturnTypeRef;
- ], tFunction);
- }
- }
- }
- }
- }
-
- def protected static String getObjectInfo(EObject obj) {
- if (obj === null) {
- ""
- } else if (obj instanceof IdentifierRef) {
- val node = NodeModelUtils.findActualNodeFor(obj);
- if (node === null) {
- ""
- } else {
- "IdentifierRef \"" + NodeModelUtils.getTokenText(node) + "\""
- }
- } else {
- val name = obj.name;
- if (name !== null) {
- obj.eClass.name + " \"" + name + "\""
- } else {
- obj.eClass.name
- }
- }
- }
-
- def protected static String getName(EObject obj) {
- switch (obj) {
- NamedElement: obj.name
- IdentifiableElement: obj.name
- }
- }
-
- def protected static void log(int indentLevel, TypeRef result) {
- if (!isDEBUG_LOG)
- return;
- log(indentLevel, result.typeRefAsString);
- }
-
- def protected static void log(int indentLevel, EObject astNode, ASTMetaInfoCache cache) {
- if (!isDEBUG_LOG)
- return;
- if (astNode.isTypableNode) {
- val result = cache.getTypeFailSafe(astNode as TypableElement);
- val resultStr = if (result !== null) result.typeRefAsString else "*** MISSING ***";
- log(indentLevel, astNode.objectInfo + " " + resultStr);
- } else {
- log(indentLevel, astNode.objectInfo);
- }
- for (childNode : astNode.eContents) {
- log(indentLevel + 1, childNode, cache);
- }
- }
-
- def protected static void log(int indentLevel, String msg) {
- if (!isDEBUG_LOG)
- return;
- println(indent(indentLevel) + msg);
- }
-
- def protected static void logErr(String msg) {
- // always log errors, even if !isDEBUG_LOG()
- System.out.flush();
- System.err.println(msg);
- System.err.flush();
- LOG.error(msg);
- }
-
- def protected static Throwable logException(String msg, Throwable th) {
- // always log exceptions, even if !isDEBUG_LOG()
- th.printStackTrace // enforce dumping all exceptions to stderr
- // GH-2002: TEMPORARY DEBUG LOGGING
- // Only passing the exception to Logger#error(String,Throwable) does not emit the stack trace of the caught
- // exception in all logger configurations; we therefore include the stack trace in the main message:
- LOG.error(msg + "\n" + Throwables.getStackTraceAsString(th), th);
- return th;
- }
-
- def protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, BooleanSupplier check) {
- if (isDEBUG_RIGID) {
- assertTrueIfRigid(cache, message, check.asBoolean);
- }
- }
-
- def protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, boolean actual) {
- if (isDEBUG_RIGID && !actual) {
- val e = new Error(message);
- if(!cache.hasBrokenAST) {
- // make sure we see this exception on the console, even if it gets caught somewhere
- UtilN4.reportError(e);
- }
- Throwables.throwIfUnchecked(e);
- throw new RuntimeException(e);
- }
- }
-
- // using a method to read field DEBUG_LOG to get rid of Xtend's "Constant condition is always true|false." warnings
- def protected static boolean isDEBUG_LOG() {
- return DEBUG_LOG;
- }
-
- def protected static boolean isDEBUG_LOG_RESULT() {
- return DEBUG_LOG_RESULT;
- }
-
- def protected static boolean isDEBUG_RIGID() {
- return DEBUG_RIGID;
- }
-
- def protected static String indent(int indentLevel) {
- (0 ..< indentLevel).map[" "].join
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.java
new file mode 100644
index 0000000000..8aae96c70f
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.java
@@ -0,0 +1,72 @@
+/**
+ * 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.postprocessing;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.compileTime.CompileTimeEvaluator;
+import org.eclipse.n4js.compileTime.CompileTimeValue;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.N4FieldDeclaration;
+import org.eclipse.n4js.n4JS.VariableDeclaration;
+import org.eclipse.n4js.ts.types.TConstableElement;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.utils.EcoreUtilN4;
+import org.eclipse.n4js.utils.N4JSLanguageUtils;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Processing of compile-time expressions.
+ *
+ * All expressions for which {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)} returns
+ * true
are evaluated and the resulting {@link CompileTimeValue} is stored in the cache. In some cases, the
+ * value is also stored in the TModule.
+ */
+@Singleton
+public class CompileTimeExpressionProcessor {
+
+ @Inject
+ private CompileTimeEvaluator compileTimeEvaluator;
+
+ /**
+ * If the given AST node is an expression that is directly processed as a compile-time expression (cf.
+ * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}, this method will evaluate the
+ * expression and store the evaluation result in the given cache; otherwise, this method will do nothing.
+ */
+ public void evaluateCompileTimeExpression(RuleEnvironment G, Expression astNode, ASTMetaInfoCache cache) {
+ if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(astNode)) {
+ CompileTimeValue value = compileTimeEvaluator.evaluateCompileTimeExpression(G, astNode);
+ cache.storeCompileTimeValue(astNode, value);
+
+ // in some cases, we have to store the compile-time value in the TModule:
+ EObject parent = astNode.eContainer();
+ if (parent instanceof VariableDeclaration) {
+ VariableDeclaration vd = (VariableDeclaration) parent;
+ if (vd.isDirectlyExported()) {
+ storeValueInTModule(vd.getDefinedVariable(), value);
+ }
+ } else if (parent instanceof N4FieldDeclaration) {
+ N4FieldDeclaration fd = (N4FieldDeclaration) parent;
+ storeValueInTModule(fd.getDefinedField(), value);
+ }
+ }
+ }
+
+ private void storeValueInTModule(TConstableElement elem, CompileTimeValue value) {
+ if (elem != null && elem.isConst()) {
+ String valueStr = CompileTimeValue.serialize(value);
+ EcoreUtilN4.doWithDeliver(false, () -> {
+ elem.setCompileTimeValue(valueStr);
+ }, elem);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.xtend
deleted file mode 100644
index 131327ad52..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.xtend
+++ /dev/null
@@ -1,70 +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.postprocessing
-
-import com.google.inject.Inject
-import com.google.inject.Singleton
-import org.eclipse.n4js.compileTime.CompileTimeEvaluator
-import org.eclipse.n4js.compileTime.CompileTimeValue
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.N4FieldDeclaration
-import org.eclipse.n4js.n4JS.VariableDeclaration
-import org.eclipse.n4js.ts.types.TConstableElement
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.utils.EcoreUtilN4
-import org.eclipse.n4js.utils.N4JSLanguageUtils
-
-/**
- * Processing of compile-time expressions.
- *
- * All expressions for which {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)} returns
- * true
are evaluated and the resulting {@link CompileTimeValue} is stored in the cache. In some cases,
- * the value is also stored in the TModule.
- */
-@Singleton
-class CompileTimeExpressionProcessor {
-
- @Inject
- private CompileTimeEvaluator compileTimeEvaluator;
-
- /**
- * If the given AST node is an expression that is directly processed as a compile-time expression (cf.
- * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}, this method will evaluate the
- * expression and store the evaluation result in the given cache; otherwise, this method will do nothing.
- */
- def public void evaluateCompileTimeExpression(RuleEnvironment G, Expression astNode, ASTMetaInfoCache cache,
- int indentLevel) {
-
- if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(astNode)) {
- val value = compileTimeEvaluator.evaluateCompileTimeExpression(G, astNode);
- cache.storeCompileTimeValue(astNode, value);
-
- // in some cases, we have to store the compile-time value in the TModule:
- val parent = astNode.eContainer;
- if (parent instanceof VariableDeclaration) {
- if (parent.directlyExported) {
- storeValueInTModule(G, parent.definedVariable, value);
- }
- } else if (parent instanceof N4FieldDeclaration) {
- storeValueInTModule(G, parent.definedField, value);
- }
- }
- }
-
- def private void storeValueInTModule(RuleEnvironment G, TConstableElement elem, CompileTimeValue value) {
- if (elem !== null && elem.const) {
- val valueStr = CompileTimeValue.serialize(value);
- EcoreUtilN4.doWithDeliver(false, [
- elem.compileTimeValue = valueStr;
- ], elem);
- }
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.java
new file mode 100644
index 0000000000..b8429a6d55
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.java
@@ -0,0 +1,127 @@
+/**
+ * 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.postprocessing;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.n4js.compileTime.CompileTimeValue;
+import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName;
+import org.eclipse.n4js.n4JS.N4FieldDeclaration;
+import org.eclipse.n4js.n4JS.N4GetterDeclaration;
+import org.eclipse.n4js.n4JS.N4JSASTUtils;
+import org.eclipse.n4js.n4JS.N4SetterDeclaration;
+import org.eclipse.n4js.n4JS.PropertyGetterDeclaration;
+import org.eclipse.n4js.n4JS.PropertyNameValuePair;
+import org.eclipse.n4js.n4JS.PropertySetterDeclaration;
+import org.eclipse.n4js.n4JS.PropertySpread;
+import org.eclipse.n4js.n4JS.TypeDefiningElement;
+import org.eclipse.n4js.ts.types.IdentifiableElement;
+import org.eclipse.n4js.ts.types.SyntaxRelatedTElement;
+import org.eclipse.n4js.utils.EcoreUtilN4;
+import org.eclipse.n4js.utils.N4JSLanguageUtils;
+
+import com.google.inject.Singleton;
+
+/**
+ * Processing of {@link LiteralOrComputedPropertyName}s that have a computed property name, mainly setting property
+ * {@link LiteralOrComputedPropertyName#getComputedName() 'computedName'}.
+ *
+ * For details, see
+ * {@link ComputedNameProcessor#processComputedPropertyName( LiteralOrComputedPropertyName, ASTMetaInfoCache )}.
+ */
+@Singleton
+public class ComputedNameProcessor {
+
+ /**
+ * If the given 'nameDecl' has a computed property name, this method will
+ *
+ * - obtain its expression's compile-time value from the cache (the actual evaluation of the expression happened
+ * in {@link CompileTimeExpressionProcessor}),
+ *
- store this name in 'nameDecl' (for later use), and
+ *
- store this name in the corresponding TModule element (if such a TModule element exists).
+ *
+ *
+ * In case the compile-time value of the expression is invalid (i.e. the expression is not a valid compile-time
+ * expression) no actual name will be stored in 'nameDecl' and the corresponding TModule element will be removed
+ * from the TModule, entirely (if such a TModule element exists).
+ */
+ public void processComputedPropertyName(LiteralOrComputedPropertyName nameDecl, ASTMetaInfoCache cache) {
+ if (nameDecl.hasComputedPropertyName()) {
+ // obtain compile-time value of expression
+ CompileTimeValue value = cache.getCompileTimeValue(nameDecl.getExpression());
+ // derive a property name from the value
+ String name = N4JSLanguageUtils.derivePropertyNameFromCompileTimeValue(value);
+ if (name != null) {
+ // cache the computed name in the LiteralOrComputedPropertyName AST node
+ EcoreUtilN4.doWithDeliver(false, () -> {
+ nameDecl.setComputedName(name);
+ nameDecl.setComputedSymbol(value instanceof CompileTimeValue.ValueSymbol);
+ }, nameDecl);
+ // set the computed name in the types model element
+ EObject owner = nameDecl.eContainer();
+ EObject typeElem = N4JSASTUtils.getCorrespondingTypeModelElement(owner);
+ if (typeElem instanceof IdentifiableElement) {
+ EcoreUtilN4.doWithDeliver(false, () -> {
+ ((IdentifiableElement) typeElem).setName(name);
+ }, typeElem);
+ }
+ } else {
+ // invalid name expression (i.e. not a constant expression)
+ // -> remove the types model element from the TModule
+ // (note: we have to do this for consistency with how the types builder handles elements that are
+ // unnamed (usually due to a broken AST): in those cases, the types builder does not create a TModule
+ // element)
+ EObject owner = nameDecl.eContainer();
+ discardTypeModelElement(owner);
+ }
+ }
+ }
+
+ /**
+ * Discards the types model element corresponding to the given AST node. Throws exception if given AST node does not
+ * have a corresponding types model element.
+ */
+ private void discardTypeModelElement(EObject astNode) {
+ EObject elem = N4JSASTUtils.getCorrespondingTypeModelElement(astNode);
+
+ EcoreUtilN4.doWithDeliver(false, () -> {
+ if (astNode instanceof TypeDefiningElement) {
+ ((TypeDefiningElement) astNode).setDefinedType(null);
+ } else if (astNode instanceof N4FieldDeclaration) {
+ ((N4FieldDeclaration) astNode).setDefinedField(null);
+ } else if (astNode instanceof N4GetterDeclaration) {
+ ((N4GetterDeclaration) astNode).setDefinedGetter(null);
+ } else if (astNode instanceof N4SetterDeclaration) {
+ ((N4SetterDeclaration) astNode).setDefinedSetter(null);
+ } else if (astNode instanceof PropertyNameValuePair) {
+ ((PropertyNameValuePair) astNode).setDefinedField(null);
+ } else if (astNode instanceof PropertyGetterDeclaration) {
+ ((PropertyGetterDeclaration) astNode).setDefinedGetter(null);
+ } else if (astNode instanceof PropertySetterDeclaration) {
+ ((PropertySetterDeclaration) astNode).setDefinedSetter(null);
+ // note: PropertyMethodDeclaration is a TypeDefiningElement (handled above)
+ } else if (astNode instanceof PropertySpread) {
+ // nothing to discard in this case
+ } else {
+ throw new UnsupportedOperationException("switch case missing for: " + astNode);
+ }
+ }, astNode);
+
+ if (elem instanceof SyntaxRelatedTElement) {
+ ((SyntaxRelatedTElement) elem).setAstElement(null);
+ }
+ if (elem != null) {
+ EcoreUtilN4.doWithDeliver(false, () -> {
+ EcoreUtil.remove(elem);
+ }, elem.eContainer());
+ }
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.xtend
deleted file mode 100644
index 4bb8100700..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.xtend
+++ /dev/null
@@ -1,131 +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.postprocessing
-
-import com.google.inject.Singleton
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.n4js.compileTime.CompileTimeValue
-import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName
-import org.eclipse.n4js.n4JS.N4FieldDeclaration
-import org.eclipse.n4js.n4JS.N4GetterDeclaration
-import org.eclipse.n4js.n4JS.N4JSASTUtils
-import org.eclipse.n4js.n4JS.N4SetterDeclaration
-import org.eclipse.n4js.n4JS.PropertyGetterDeclaration
-import org.eclipse.n4js.n4JS.PropertyNameValuePair
-import org.eclipse.n4js.n4JS.PropertySetterDeclaration
-import org.eclipse.n4js.n4JS.PropertySpread
-import org.eclipse.n4js.n4JS.TypeDefiningElement
-import org.eclipse.n4js.ts.types.IdentifiableElement
-import org.eclipse.n4js.ts.types.SyntaxRelatedTElement
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.utils.EcoreUtilN4
-import org.eclipse.n4js.utils.N4JSLanguageUtils
-import org.eclipse.xtext.EcoreUtil2
-
-/**
- * Processing of {@link LiteralOrComputedPropertyName}s that have a computed property name, mainly setting property
- * {@link LiteralOrComputedPropertyName#getComputedName() 'computedName'}.
- *
- * For details, see {@link ComputedNameProcessor#processComputedPropertyName(RuleEnvironment, LiteralOrComputedPropertyName, ASTMetaInfoCache, int)}.
- */
-@Singleton
-class ComputedNameProcessor {
-
- /**
- * If the given 'nameDecl' has a computed property name, this method will
- *
- * - obtain its expression's compile-time value from the cache (the actual evaluation of the expression happened
- * in {@link CompileTimeExpressionProcessor}),
- *
- derive the actual property name from that value (cf. {@link #getPropertyNameFromExpression(RuleEnvironment, Expression, ASTMetaInfoCache)}),
- *
- store this name in 'nameDecl' (for later use), and
- *
- store this name in the corresponding TModule element (if such a TModule element exists).
- *
- *
- * In case the compile-time value of the expression is invalid (i.e. the expression is not a valid compile-time
- * expression) no actual name will be stored in 'nameDecl' and the corresponding TModule element will be removed
- * from the TModule, entirely (if such a TModule element exists).
- */
- def public void processComputedPropertyName(RuleEnvironment G, LiteralOrComputedPropertyName nameDecl,
- ASTMetaInfoCache cache, int indentLevel) {
-
- if (nameDecl.hasComputedPropertyName) {
- // obtain compile-time value of expression
- val value = cache.getCompileTimeValue(nameDecl.expression);
- // derive a property name from the value
- val name = N4JSLanguageUtils.derivePropertyNameFromCompileTimeValue(value);
- if (name !== null) {
- // cache the computed name in the LiteralOrComputedPropertyName AST node
- EcoreUtilN4.doWithDeliver(false, [
- nameDecl.computedName = name;
- nameDecl.computedSymbol = value instanceof CompileTimeValue.ValueSymbol;
- ], nameDecl);
- // set the computed name in the types model element
- val owner = nameDecl.eContainer;
- val typeElem = N4JSASTUtils.getCorrespondingTypeModelElement(owner);
- if (typeElem instanceof IdentifiableElement) {
- EcoreUtilN4.doWithDeliver(false, [
- typeElem.name = name;
- ], typeElem);
- }
- } else {
- // invalid name expression (i.e. not a constant expression)
- // -> remove the types model element from the TModule
- // (note: we have to do this for consistency with how the types builder handles elements that are
- // unnamed (usually due to a broken AST): in those cases, the types builder does not create a TModule
- // element)
- val owner = nameDecl.eContainer;
- discardTypeModelElement(owner);
- }
- }
- }
-
- /**
- * Discards the types model element corresponding to the given AST node. Throws exception if given AST node does not
- * have a corresponding types model element.
- */
- def private void discardTypeModelElement(EObject astNode) {
- val elem = N4JSASTUtils.getCorrespondingTypeModelElement(astNode);
-
- EcoreUtilN4.doWithDeliver(false, [
- switch (astNode) {
- TypeDefiningElement:
- astNode.definedType = null
- N4FieldDeclaration:
- astNode.definedField = null
- N4GetterDeclaration:
- astNode.definedGetter = null
- N4SetterDeclaration:
- astNode.definedSetter = null
- PropertyNameValuePair:
- astNode.definedField = null
- PropertyGetterDeclaration:
- astNode.definedGetter = null
- PropertySetterDeclaration:
- astNode.definedSetter = null
- // note: PropertyMethodDeclaration is a TypeDefiningElement (handled above)
- PropertySpread: {
- // nothing to discard in this case
- }
- default:
- throw new UnsupportedOperationException("switch case missing for: " + astNode)
- };
- ], astNode);
-
- if (elem instanceof SyntaxRelatedTElement) {
- elem.astElement = null;
- }
- if (elem !== null) {
- EcoreUtilN4.doWithDeliver(false, [
- EcoreUtil2.remove(elem);
- ], elem.eContainer);
- }
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.java
new file mode 100644
index 0000000000..dd88121cc0
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.java
@@ -0,0 +1,143 @@
+/**
+ * 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.postprocessing;
+
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.anyTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.undefinedTypeRef;
+import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter;
+import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.n4js.n4JS.ArrayElement;
+import org.eclipse.n4js.n4JS.ArrayLiteral;
+import org.eclipse.n4js.n4JS.AssignmentExpression;
+import org.eclipse.n4js.n4JS.BindingElement;
+import org.eclipse.n4js.n4JS.DestructureUtils;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.ForStatement;
+import org.eclipse.n4js.n4JS.ObjectLiteral;
+import org.eclipse.n4js.n4JS.PropertyAssignment;
+import org.eclipse.n4js.n4JS.PropertyNameValuePair;
+import org.eclipse.n4js.n4JS.VariableBinding;
+import org.eclipse.n4js.n4JS.VariableDeclaration;
+import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.types.TypableElement;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions;
+import org.eclipse.n4js.utils.EcoreUtilN4;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Deals with destructuring patterns during post processing of an N4JS resource (only the destructuring pattern; the
+ * value to be destructured is handled normally by the other processors).
+ *
+ * TODO clean up handling of destructuring patterns during AST traversal, IDE-1714
+ */
+@Singleton
+class DestructureProcessor extends AbstractProcessor {
+
+ @Inject
+ private ASTProcessor astProcessor;
+ @Inject
+ private PolyProcessor polyProcessor;
+
+ /**
+ * Temporary handling of destructuring patterns while typing the AST.
+ */
+ void typeDestructuringPattern(RuleEnvironment G, EObject node, ASTMetaInfoCache cache) {
+ // ArrayLiteral or ObjectLiteral, but plays role of a destructuring pattern
+ // -> does not really have a type, but use UnknownTypeRef to avoid having
+ // to deal with this special case whenever asking for type of an expression
+ cache.storeType((TypableElement) node, undefinedTypeRef(G));
+ // for object literals, some additional hacks are required ...
+ if (node instanceof ObjectLiteral) {
+ ObjectLiteral olit = (ObjectLiteral) node;
+ // poly expressions in property name/value pairs expect to be processed as part of the outer poly expression
+ // -> invoke poly processor for them
+ // TODO GH-1337 add support for spread operator
+ for (PropertyAssignment pa : olit.getPropertyAssignments()) {
+ if (pa instanceof PropertyNameValuePair) {
+ Expression expr = ((PropertyNameValuePair) pa).getExpression();
+ if (expr != null && polyProcessor.isResponsibleFor(expr) && !polyProcessor.isEntryPoint(expr)) {
+ polyProcessor.inferType(G, expr, cache);
+ }
+ }
+ }
+ // the defined type of the object literal may still have some DeferredTypeRefs -> remove them
+ for (DeferredTypeRef dtr : toIterable(filter(
+ olit.getDefinedType().eAllContents(), DeferredTypeRef.class))) {
+
+ EcoreUtilN4.doWithDeliver(false, () -> EcoreUtil.replace(dtr, undefinedTypeRef(G)), dtr.eContainer());
+ }
+ // add types for property assignments
+ for (PropertyAssignment pa : olit.getPropertyAssignments()) {
+ cache.storeType(pa, undefinedTypeRef(G));
+ }
+ }
+ // here we basically turn off the fail-fast approach within the destructuring pattern
+ for (EObject elem : toIterable(node.eAllContents())) {
+ if (elem instanceof ObjectLiteral || elem instanceof PropertyAssignment
+ || elem instanceof ArrayLiteral || elem instanceof ArrayElement) {
+
+ if (cache.getTypeFailSafe((TypableElement) elem) == null) {
+ cache.storeType((TypableElement) elem, undefinedTypeRef(G));
+ }
+ }
+ }
+ }
+
+ /**
+ * Temporary handling of forward references within destructuring patterns.
+ */
+ TypeRef handleForwardReferenceWhileTypingDestructuringPattern(RuleEnvironment G, TypableElement node,
+ ASTMetaInfoCache cache) {
+
+ EObject parent = node.eContainer();
+ boolean isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node);
+ if (isCyclicForwardReference) {
+ if (parent instanceof VariableBinding && ((VariableBinding) parent).getExpression() == node) {
+ // we get here when typing the second 'b' in 'var [a,b] = [0,b,2];'
+ return anyTypeRef(G);
+ } else if (parent instanceof ForStatement && ((ForStatement) parent).getExpression() == node) {
+ // we get here when typing the second 'a' in 'for(var [a] of [[a]]) {}'
+ return anyTypeRef(G);
+ }
+ }
+
+ log(0, "===START of other identifiable sub-tree");
+ RuleEnvironment G_fresh = RuleEnvironmentExtensions.wrap(G); // don't use a new, empty environment here
+ // (required for recursion guards)
+ astProcessor.processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level
+ cache.forwardProcessedSubTrees.add(node);
+ log(0, "===END of other identifiable sub-tree");
+ return cache.getType(G, node);
+ }
+
+ boolean isForwardReferenceWhileTypingDestructuringPattern(EObject obj) {
+ if (obj instanceof Expression) {
+ EObject parent = obj.eContainer();
+ if (parent instanceof ForStatement) {
+ return DestructureUtils.isTopOfDestructuringForStatement(parent);
+ }
+ if (parent instanceof AssignmentExpression) {
+ return DestructureUtils.isTopOfDestructuringAssignment(parent);
+ }
+ return parent instanceof VariableBinding
+ || parent instanceof BindingElement
+ || (parent instanceof VariableDeclaration && parent.eContainer() instanceof BindingElement);
+ }
+ return false;
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.xtend
deleted file mode 100644
index cbfde29496..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.xtend
+++ /dev/null
@@ -1,137 +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.postprocessing
-
-import com.google.inject.Inject
-import com.google.inject.Singleton
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.emf.ecore.util.EcoreUtil
-import org.eclipse.n4js.n4JS.ArrayElement
-import org.eclipse.n4js.n4JS.ArrayLiteral
-import org.eclipse.n4js.n4JS.AssignmentExpression
-import org.eclipse.n4js.n4JS.BindingElement
-import org.eclipse.n4js.n4JS.DestructureUtils
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.ForStatement
-import org.eclipse.n4js.n4JS.ObjectLiteral
-import org.eclipse.n4js.n4JS.PropertyAssignment
-import org.eclipse.n4js.n4JS.PropertyNameValuePair
-import org.eclipse.n4js.n4JS.VariableBinding
-import org.eclipse.n4js.n4JS.VariableDeclaration
-import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-import org.eclipse.n4js.ts.types.TypableElement
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions
-import org.eclipse.n4js.utils.EcoreUtilN4
-
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-
-/**
- * Deals with destructuring patterns during post processing of an N4JS resource (only the destructuring pattern;
- * the value to be destructured is handled normally by the other processors).
- *
- * TODO clean up handling of destructuring patterns during AST traversal, IDE-1714
- */
-@Singleton
-package class DestructureProcessor extends AbstractProcessor {
-
- @Inject
- private ASTProcessor astProcessor;
- @Inject
- private PolyProcessor polyProcessor;
-
- /**
- * Temporary handling of destructuring patterns while typing the AST.
- */
- def void typeDestructuringPattern(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) {
- // ArrayLiteral or ObjectLiteral, but plays role of a destructuring pattern
- // -> does not really have a type, but use UnknownTypeRef to avoid having
- // to deal with this special case whenever asking for type of an expression
- cache.storeType(node as TypableElement, G.undefinedTypeRef);
- // for object literals, some additional hacks are required ...
- if (node instanceof ObjectLiteral) {
- // poly expressions in property name/value pairs expect to be processed as part of the outer poly expression
- // -> invoke poly processor for them
- // TODO GH-1337 add support for spread operator
- node.propertyAssignments //
- .filter(PropertyNameValuePair) //
- .map[expression] //
- .filterNull //
- .filter[polyProcessor.isResponsibleFor(it) && !polyProcessor.isEntryPoint(it)] //
- .forEach [
- polyProcessor.inferType(G, it, cache);
- ];
- // the defined type of the object literal may still have some DeferredTypeRefs -> remove them
- node.definedType.eAllContents.filter(DeferredTypeRef).forEach [ dtr |
- EcoreUtilN4.doWithDeliver(false, [
- EcoreUtil.replace(dtr, G.undefinedTypeRef);
- ], dtr.eContainer);
- ]
- // add types for property assignments
- node.propertyAssignments.forEach [
- cache.storeType(it, G.undefinedTypeRef);
- ]
- }
- // here we basically turn off the fail-fast approach within the destructuring pattern
- node.eAllContents //
- .filter[
- it instanceof ObjectLiteral || it instanceof PropertyAssignment
- || it instanceof ArrayLiteral || it instanceof ArrayElement
- ] //
- .filter[cache.getTypeFailSafe(it as TypableElement)===null] //
- .forEach[
- cache.storeType(it as TypableElement, G.undefinedTypeRef);
- ];
- }
-
- /**
- * Temporary handling of forward references within destructuring patterns.
- */
- def TypeRef handleForwardReferenceWhileTypingDestructuringPattern(RuleEnvironment G, TypableElement node,
- ASTMetaInfoCache cache) {
-
- val parent = node.eContainer();
- val isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node);
- if(isCyclicForwardReference) {
- if(parent instanceof VariableBinding && (parent as VariableBinding).expression===node) {
- // we get here when typing the second 'b' in 'var [a,b] = [0,b,2];'
- return G.anyTypeRef;
- } else if(parent instanceof ForStatement && (parent as ForStatement).expression===node) {
- // we get here when typing the second 'a' in 'for(var [a] of [[a]]) {}'
- return G.anyTypeRef;
- }
- }
-
- log(0, "===START of other identifiable sub-tree");
- val G_fresh = RuleEnvironmentExtensions.wrap(G); // don't use a new, empty environment here (required for recursion guards)
- astProcessor.processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level
- cache.forwardProcessedSubTrees.add(node);
- log(0, "===END of other identifiable sub-tree");
- return cache.getType(G, node);
- }
-
- def boolean isForwardReferenceWhileTypingDestructuringPattern(EObject obj) {
- if (obj instanceof Expression) {
- val parent = obj.eContainer;
- if (parent instanceof ForStatement) {
- return DestructureUtils.isTopOfDestructuringForStatement(parent);
- }
- if (parent instanceof AssignmentExpression) {
- return DestructureUtils.isTopOfDestructuringAssignment(parent)
- }
- return parent instanceof VariableBinding
- || parent instanceof BindingElement
- || (parent instanceof VariableDeclaration && parent.eContainer instanceof BindingElement)
- }
- return false;
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.java
new file mode 100644
index 0000000000..2d07c2ade6
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.java
@@ -0,0 +1,267 @@
+/**
+ * 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.postprocessing;
+
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.n4JS.Argument;
+import org.eclipse.n4js.n4JS.ArrayElement;
+import org.eclipse.n4js.n4JS.ArrayLiteral;
+import org.eclipse.n4js.n4JS.ConditionalExpression;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.FormalParameter;
+import org.eclipse.n4js.n4JS.FunctionExpression;
+import org.eclipse.n4js.n4JS.ObjectLiteral;
+import org.eclipse.n4js.n4JS.ParameterizedCallExpression;
+import org.eclipse.n4js.n4JS.PropertyAssignment;
+import org.eclipse.n4js.n4JS.PropertyMethodDeclaration;
+import org.eclipse.n4js.n4JS.RelationalExpression;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.types.TypableElement;
+import org.eclipse.n4js.ts.types.util.Variance;
+import org.eclipse.n4js.types.utils.TypeUtils;
+import org.eclipse.n4js.typesystem.N4JSTypeSystem;
+import org.eclipse.n4js.typesystem.constraints.InferenceContext;
+import org.eclipse.n4js.typesystem.constraints.TypeConstraint;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.typesystem.utils.TypeSystemHelper;
+import org.eclipse.n4js.utils.DeclMergingHelper;
+import org.eclipse.n4js.utils.DestructureHelper;
+import org.eclipse.n4js.validation.JavaScriptVariantHelper;
+import org.eclipse.xtext.service.OperationCanceledManager;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * The main poly processor responsible for typing poly expressions using a constraint-based approach.
+ *
+ * It tells other processors which AST nodes it is responsible for (see
+ * {@link PolyProcessor#isResponsibleFor(TypableElement) isResponsibleFor()}) and which AST nodes are an entry point to
+ * constraint-based type inference (see {@link PolyProcessor#isEntryPoint(TypableElement) isEntryPoint()}). For those
+ * "entry points" method {@link PolyProcessor#inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()} should
+ * be invoked by the other processors (mainly the TypeProcessor).
+ */
+@Singleton
+class PolyProcessor extends AbstractPolyProcessor {
+
+ @Inject
+ private PolyProcessor_ArrayLiteral arrayLiteralProcessor;
+ @Inject
+ private PolyProcessor_ObjectLiteral objectLiteralProcessor;
+ @Inject
+ private PolyProcessor_FunctionExpression functionExpressionProcessor;
+ @Inject
+ private PolyProcessor_CallExpression callExpressionProcessor;
+
+ @Inject
+ private N4JSTypeSystem ts;
+ @Inject
+ private TypeSystemHelper tsh;
+ @Inject
+ private DeclMergingHelper declMergingHelper;
+
+ @Inject
+ private DestructureHelper destructureHelper;
+
+ @Inject
+ private OperationCanceledManager operationCanceledManager;
+
+ @Inject
+ private JavaScriptVariantHelper jsVariantHelper;
+
+ // ################################################################################################################
+
+ /**
+ * Tells if the given AST node's type should be inferred through constraint-based type inference. In that case, no
+ * other processor is allowed to add a type for this node to the {@link ASTMetaInfoCache}!
+ */
+ boolean isResponsibleFor(TypableElement astNode) {
+ EObject parent = astNode.eContainer();
+ return isPoly(astNode)
+ || (astNode instanceof Argument && parent instanceof ParameterizedCallExpression && isPoly(parent))
+ || (astNode instanceof FormalParameter && parent instanceof FunctionExpression && isPoly(parent))
+ || (astNode instanceof FormalParameter && parent instanceof PropertyMethodDeclaration && isPoly(parent))
+ || (astNode instanceof ArrayElement && parent instanceof ArrayLiteral && isPoly(parent))
+ || (astNode instanceof PropertyAssignment && parent instanceof ObjectLiteral && isPoly(parent));
+ // note in previous line:
+ // even if the PropertyAssignment itself is NOT poly, we claim responsibility for it if the containing
+ // ObjectLiteral is poly
+ }
+
+ /**
+ * Tells if the given AST node is an entry point to constraint-based type inference. In that case, and only in that
+ * case, method {@link #inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()} must be invoked for this
+ * AST node.
+ */
+ boolean isEntryPoint(TypableElement astNode) {
+ return isRootPoly(astNode);
+ }
+
+ // ################################################################################################################
+
+ /**
+ * Main method for inferring the type of poly expressions, i.e. for constraint-based type inference. It should be
+ * invoked for all AST nodes for which method {@link #isEntryPoint(TypableElement) isEntryPoint()} returns
+ * true
(and only for such nodes!). This will ensure that this method will be called for all roots of
+ * trees of nested poly expressions (including such trees that only consist of a root without children) but not for
+ * the nested children.
+ *
+ * This method, together with its delegates, is responsible for adding to the cache types for all the following AST
+ * nodes:
+ *
+ * - the given root poly expression
rootPoly
,
+ * - all nested child poly expressions,
+ *
- some nested elements that aren't expressions but closely belong to one of the above expressions, e.g. formal
+ * parameters contained in a function expression (see code of {@link #isResponsibleFor(TypableElement)} for which
+ * elements are included here).
+ *
+ *
+ * The overall process of constraint-based type inference is as follows:
+ *
+ * - create a new, empty {@link InferenceContext} called
IC
.
+ * - invoke method {@link #processExpr(RuleEnvironment,Expression,TypeRef,InferenceContext,ASTMetaInfoCache)
+ * #processExpr()} for the given root poly expression and all its direct and indirect child poly expressions. This
+ * will ...
+ *
+ * - add to
IC
(i) inference variables for all types to be inferred and (ii) appropriate constraints
+ * derived from the poly expressions and their relations.
+ * - register
onSolved
handlers to IC
(see below what these handlers are doing).
+ *
+ * - solve the entire constraint system, i.e. invoke {@link InferenceContext#solve()} on
IC
.
+ * - once solution is done (no matter if successful or failed)
IC
will automatically trigger the
+ * onSolved
handlers:
+ *
+ * - in the success case, the handlers will use the solution in
IC
to add types to the cache for the
+ * given root poly expression and all its nested child poly expressions (and also for contained, typable elements
+ * such as fpars of function expressions).
+ * - in the failure case, the handlers will add fall-back types to the cache.
+ *
+ *
+ */
+ void inferType(RuleEnvironment G, Expression rootPoly, ASTMetaInfoCache cache) {
+ // create a new constraint system
+ InferenceContext infCtx = new InferenceContext(ts, tsh, declMergingHelper, operationCanceledManager,
+ getCancelIndicator(G), G);
+
+ // in plain JS files, we want to avoid searching for a solution (to avoid performance problems in some JS files
+ // with extremely large array/object literals) but to avoid having to deal with this case with additional code,
+ // we still build a constraint system as usual (TEMPORARAY HACK)
+ // TODO find proper way to deal with extremely large array/object literals
+ if (jsVariantHelper.doomTypeInference(rootPoly)) {
+ infCtx.addConstraint(TypeConstraint.FALSE);
+ }
+
+ TypeRef expectedTypeOfPoly = destructureHelper.calculateExpectedType(rootPoly, G, infCtx);
+ // we have to pass the expected type to the #getType() method, so retrieve it first
+ // (until the expectedType judgment is integrated into AST traversal, we have to invoke this judgment here;
+ // in case of not-well-behaving expectedType rules, we use 'null' as expected type, i.e. no expectation)
+ // TODO integrate expectedType judgment into AST traversal and remove #isProblematicCaseOfExpectedType()
+ TypeRef expectedTypeRef = null;
+ if (expectedTypeOfPoly != null) {
+ expectedTypeRef = expectedTypeOfPoly;
+ } else if (!isProblematicCaseOfExpectedType(rootPoly)) {
+ expectedTypeRef = ts.expectedType(G, rootPoly.eContainer(), rootPoly);
+ }
+
+ // call #processExpr() (this will recursively call #processExpr() on nested expressions, even if non-poly)
+ TypeRef typeRef = processExpr(G, rootPoly, expectedTypeRef, infCtx, cache);
+
+ // add constraint to ensure that type of 'rootPoly' is subtype of its expected type
+ if (!TypeUtils.isVoid(typeRef)) {
+ if (expectedTypeRef != null) {
+ infCtx.addConstraint(0, typeRef, expectedTypeRef, Variance.CO);
+ }
+ }
+
+ // compute solution
+ // (note: we're not actually interested in the solution, here; we just want to make sure to trigger the
+ // onSolved handlers registered by the #process*() methods of the other poly processors; see responsibilities of
+ // #processExpr(RuleEnvironment, Expression, TypeRef, InferenceContext, ASTMetaInfoCache)
+ infCtx.solve();
+ }
+
+ /**
+ * Key method for handling poly expressions.
+ *
+ * It has the following responsibilities:
+ *
+ * - if given expression is non-poly: simply return its type
+ * (note: in this case this method won't process nested expressions in any way).
+ * - if given expression is poly:
+ *
+ * - introduce a new inference variable to the given inference context for each type to be inferred for the given
+ * poly expression (usually only 1, but may be several, e.g. for a function expression we introduce an inference
+ * variable for the return type and each fpar),
+ *
- add appropriate constraints to the given inference context,
+ *
- recursively invoke this method for nested expressions (no matter if poly or non-poly).
+ *
- register to the given inference context an
onSolved
handler that will - after the inference
+ * context will have been solved - add all required final types for 'expr' and its non-expression children
+ * (e.g. fpars) to the typing cache.
+ * - return temporary type of the given expression
expr
.
+ *
+ *
+ * IMPORTANT: the "temporary" type may contain inference variables; the "final" types must be proper, i.e. must not
+ * contain any inference variables!
+ */
+ protected TypeRef processExpr(RuleEnvironment G, Expression expr, TypeRef expectedTypeRef,
+ InferenceContext infCtx, ASTMetaInfoCache cache) {
+
+ if (isPoly(expr)) {
+ // poly -> delegate this to the appropriate, specific PolyProcessor
+ if (expr instanceof ArrayLiteral) {
+ return arrayLiteralProcessor.processArrayLiteral(G, (ArrayLiteral) expr, expectedTypeRef, infCtx,
+ cache);
+ } else if (expr instanceof ObjectLiteral) {
+ return objectLiteralProcessor.processObjectLiteral(G, (ObjectLiteral) expr, expectedTypeRef, infCtx,
+ cache);
+ } else if (expr instanceof FunctionExpression) {
+ return functionExpressionProcessor.processFunctionExpression(G, (FunctionExpression) expr,
+ expectedTypeRef, infCtx, cache);
+ } else if (expr instanceof ParameterizedCallExpression) {
+ return callExpressionProcessor.processCallExpression(G, (ParameterizedCallExpression) expr,
+ expectedTypeRef, infCtx, cache);
+ } else if (expr instanceof ConditionalExpression) {
+ ConditionalExpression ce = (ConditionalExpression) expr;
+ if (isPoly(ce.getTrueExpression())) {
+ TypeRef typeRef = processExpr(G, ce.getTrueExpression(), expectedTypeRef, infCtx, cache);
+ // store a copy of the inferred type also at the conditional expression node
+ infCtx.onSolved(solution -> cache.storeType(ce,
+ TypeUtils.copy(cache.getTypeFailSafe(ce.getTrueExpression()))));
+ return typeRef;
+ } else if (isPoly(ce.getFalseExpression())) {
+ TypeRef typeRef = processExpr(G, ce.getFalseExpression(), expectedTypeRef, infCtx, cache);
+ // store a copy of the inferred type also at the conditional expression node
+ infCtx.onSolved(solution -> cache.storeType(ce,
+ TypeUtils.copy(cache.getTypeFailSafe(ce.getFalseExpression()))));
+ return typeRef;
+ } else {
+ throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr);
+ }
+ }
+ throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr);
+ } else {
+ // not poly -> directly infer type via type system
+ TypeRef result = ts.type(G, expr);
+ // do *not* store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache!)
+ return result;
+ }
+ }
+
+ /**
+ * Returns true if we are not allowed to ask for the expected type of 'node', because this would lead to illegal
+ * forward references (temporary).
+ */
+ private boolean isProblematicCaseOfExpectedType(EObject node) {
+ return node != null && node.eContainer() instanceof RelationalExpression;
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.xtend
deleted file mode 100644
index 6d5ebba220..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.xtend
+++ /dev/null
@@ -1,260 +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.postprocessing
-
-import com.google.inject.Inject
-import com.google.inject.Singleton
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.n4js.n4JS.Argument
-import org.eclipse.n4js.n4JS.ArrayElement
-import org.eclipse.n4js.n4JS.ArrayLiteral
-import org.eclipse.n4js.n4JS.ConditionalExpression
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.FormalParameter
-import org.eclipse.n4js.n4JS.FunctionExpression
-import org.eclipse.n4js.n4JS.ObjectLiteral
-import org.eclipse.n4js.n4JS.ParameterizedCallExpression
-import org.eclipse.n4js.n4JS.PropertyAssignment
-import org.eclipse.n4js.n4JS.PropertyMethodDeclaration
-import org.eclipse.n4js.n4JS.RelationalExpression
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-import org.eclipse.n4js.ts.types.TypableElement
-import org.eclipse.n4js.ts.types.util.Variance
-import org.eclipse.n4js.types.utils.TypeUtils
-import org.eclipse.n4js.typesystem.N4JSTypeSystem
-import org.eclipse.n4js.typesystem.constraints.InferenceContext
-import org.eclipse.n4js.typesystem.constraints.TypeConstraint
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.typesystem.utils.TypeSystemHelper
-import org.eclipse.n4js.utils.DeclMergingHelper
-import org.eclipse.n4js.utils.DestructureHelper
-import org.eclipse.n4js.validation.JavaScriptVariantHelper
-import org.eclipse.xtext.service.OperationCanceledManager
-
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-
-/**
- * The main poly processor responsible for typing poly expressions using a constraint-based approach.
- *
- * It tells other processors which AST nodes it is responsible for (see {@link PolyProcessor#isResponsibleFor(TypableElement) isResponsibleFor()})
- * and which AST nodes are an entry point to constraint-based type inference (see {@link PolyProcessor#isEntryPoint(TypableElement) isEntryPoint()}).
- * For those "entry points" method {@link PolyProcessor#inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()}
- * should be invoked by the other processors (mainly the TypeProcessor).
- */
-@Singleton
-package class PolyProcessor extends AbstractPolyProcessor {
-
- @Inject
- private PolyProcessor_ArrayLiteral arrayLiteralProcessor;
- @Inject
- private PolyProcessor_ObjectLiteral objectLiteralProcessor;
- @Inject
- private PolyProcessor_FunctionExpression functionExpressionProcessor;
- @Inject
- private PolyProcessor_CallExpression callExpressionProcessor;
-
- @Inject
- private N4JSTypeSystem ts;
- @Inject
- private TypeSystemHelper tsh;
- @Inject
- private DeclMergingHelper declMergingHelper;
-
- @Inject
- private DestructureHelper destructureHelper;
-
- @Inject
- private OperationCanceledManager operationCanceledManager;
-
- @Inject
- private JavaScriptVariantHelper jsVariantHelper;
-
- // ################################################################################################################
-
-
- /**
- * Tells if the given AST node's type should be inferred through constraint-based type inference. In that case,
- * no other processor is allowed to add a type for this node to the {@link ASTMetaInfoCache}!
- */
- def package boolean isResponsibleFor(TypableElement astNode) {
- astNode.isPoly
- || (astNode instanceof Argument && astNode.eContainer instanceof ParameterizedCallExpression && astNode.eContainer.isPoly)
- || (astNode instanceof FormalParameter && astNode.eContainer instanceof FunctionExpression && astNode.eContainer.isPoly)
- || (astNode instanceof FormalParameter && astNode.eContainer instanceof PropertyMethodDeclaration && astNode.eContainer.isPoly)
- || (astNode instanceof ArrayElement && astNode.eContainer instanceof ArrayLiteral && astNode.eContainer.isPoly)
- || (astNode instanceof PropertyAssignment && astNode.eContainer instanceof ObjectLiteral && astNode.eContainer.isPoly)
- // note in previous line:
- // even if the PropertyAssignment itself is NOT poly, we claim responsibility for it if the containing ObjectLiteral is poly
- }
-
- /**
- * Tells if the given AST node is an entry point to constraint-based type inference. In that case, and only in that
- * case, method {@link #inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()} must be invoked for this
- * AST node.
- */
- def package boolean isEntryPoint(TypableElement astNode) {
- astNode.isRootPoly
- }
-
-
- // ################################################################################################################
-
-
- /**
- * Main method for inferring the type of poly expressions, i.e. for constraint-based type inference. It should be
- * invoked for all AST nodes for which method {@link #isEntryPoint(TypableElement) isEntryPoint()}
- * returns true
(and only for such nodes!). This will ensure that this method will be called for all
- * roots of trees of nested poly expressions (including such trees that only consist of a root without children) but
- * not for the nested children.
- *
- * This method, together with its delegates, is responsible for adding to the cache types for all the following
- * AST nodes:
- *
- * - the given root poly expression
rootPoly
,
- * - all nested child poly expressions,
- *
- some nested elements that aren't expressions but closely belong to one of the above expressions, e.g. formal
- * parameters contained in a function expression (see code of {@link #isResponsibleFor(TypableElement)} for which
- * elements are included here).
- *
- *
- * The overall process of constraint-based type inference is as follows:
- *
- * - create a new, empty {@link InferenceContext} called
IC
.
- * - invoke method {@link #processExpr(RuleEnvironment,Expression,TypeRef,InferenceContext,ASTMetaInfoCache) #processExpr()}
- * for the given root poly expression and all its direct and indirect child poly expressions. This will ...
- *
- * - add to
IC
(i) inference variables for all types to be inferred and (ii) appropriate
- * constraints derived from the poly expressions and their relations.
- * - register
onSolved
handlers to IC
(see below what these handlers are doing).
- *
- * - solve the entire constraint system, i.e. invoke {@link #solve()} on
IC
.
- * - once solution is done (no matter if successful or failed)
IC
will automatically trigger the
- * onSolved
handlers:
- *
- * - in the success case, the handlers will use the solution in
IC
to add types to the cache for
- * the given root poly expression and all its nested child poly expressions (and also for contained, typable
- * elements such as fpars of function expressions).
- * - in the failure case, the handlers will add fall-back types to the cache.
- *
- *
- */
- def package void inferType(RuleEnvironment G, Expression rootPoly, ASTMetaInfoCache cache) {
- // create a new constraint system
- val InferenceContext infCtx = new InferenceContext(ts, tsh, declMergingHelper, operationCanceledManager, G.cancelIndicator, G);
-
- // in plain JS files, we want to avoid searching for a solution (to avoid performance problems in some JS files
- // with extremely large array/object literals) but to avoid having to deal with this case with additional code,
- // we still build a constraint system as usual (TEMPORARAY HACK)
- // TODO find proper way to deal with extremely large array/object literals
- if (jsVariantHelper.doomTypeInference(rootPoly)) {
- infCtx.addConstraint(TypeConstraint.FALSE);
- }
-
- val expectedTypeOfPoly = destructureHelper.calculateExpectedType(rootPoly, G, infCtx);
- // we have to pass the expected type to the #getType() method, so retrieve it first
- // (until the expectedType judgment is integrated into AST traversal, we have to invoke this judgment here;
- // in case of not-well-behaving expectedType rules, we use 'null' as expected type, i.e. no expectation)
- // TODO integrate expectedType judgment into AST traversal and remove #isProblematicCaseOfExpectedType()
- val expectedTypeRef = if (expectedTypeOfPoly !== null) {
- expectedTypeOfPoly
- } else if (!rootPoly.isProblematicCaseOfExpectedType) {
- ts.expectedType(G, rootPoly.eContainer(), rootPoly);
- };
-
- // call #processExpr() (this will recursively call #processExpr() on nested expressions, even if non-poly)
- val typeRef = processExpr(G, rootPoly, expectedTypeRef, infCtx, cache);
-
- // add constraint to ensure that type of 'rootPoly' is subtype of its expected type
- if (!TypeUtils.isVoid(typeRef)) {
- if (expectedTypeRef !== null) {
- infCtx.addConstraint(0, typeRef, expectedTypeRef, Variance.CO);
- }
- }
-
- // compute solution
- // (note: we're not actually interested in the solution, here; we just want to make sure to trigger the
- // onSolved handlers registered by the #process*() methods of the other poly processors; see responsibilities of
- // #processExpr(RuleEnvironment, Expression, TypeRef, InferenceContext, ASTMetaInfoCache)
- infCtx.solve;
- }
-
-
-
- /**
- * Key method for handling poly expressions.
- *
- * It has the following responsibilities:
- *
- * - if given expression is non-poly: simply return its type
- * (note: in this case this method won't process nested expressions in any way).
- * - if given expression is poly:
- *
- * - introduce a new inference variable to the given inference context for each type to be inferred for the
- * given poly expression (usually only 1, but may be several, e.g. for a function expression we introduce an
- * inference variable for the return type and each fpar),
- *
- add appropriate constraints to the given inference context,
- *
- recursively invoke this method for nested expressions (no matter if poly or non-poly).
- *
- register to the given inference context an
onSolved
handler that will - after the inference
- * context will have been solved - add all required final types for 'expr' and its non-expression
- * children (e.g. fpars) to the typing cache.
- * - return temporary type of the given expression
expr
.
- *
- *
- * IMPORTANT: the "temporary" type may contain inference variables; the "final" types must be proper, i.e. must not
- * contain any inference variables!
- */
- def protected TypeRef processExpr(RuleEnvironment G, Expression expr, TypeRef expectedTypeRef,
- InferenceContext infCtx, ASTMetaInfoCache cache) {
-
- if (isPoly(expr)) {
- // poly -> delegate this to the appropriate, specific PolyProcessor
- return switch(expr) {
- ArrayLiteral:
- arrayLiteralProcessor.processArrayLiteral(G, expr, expectedTypeRef, infCtx, cache)
- ObjectLiteral:
- objectLiteralProcessor.processObjectLiteral(G, expr, expectedTypeRef, infCtx, cache)
- FunctionExpression:
- functionExpressionProcessor.processFunctionExpression(G, expr, expectedTypeRef, infCtx, cache)
- ParameterizedCallExpression:
- callExpressionProcessor.processCallExpression(G, expr, expectedTypeRef, infCtx, cache)
- ConditionalExpression:
- if (isPoly(expr.trueExpression)) {
- val TypeRef typeRef = processExpr(G, expr.trueExpression, expectedTypeRef, infCtx, cache);
- // store a copy of the inferred type also at the conditional expression node
- infCtx.onSolved([ solution | cache.storeType(expr, TypeUtils.copy(cache.getTypeFailSafe(expr.trueExpression))) ]);
- typeRef;
- } else if (isPoly(expr.falseExpression)) {
- val TypeRef typeRef = processExpr(G, expr.falseExpression, expectedTypeRef, infCtx, cache);
- // store a copy of the inferred type also at the conditional expression node
- infCtx.onSolved([ solution | cache.storeType(expr, TypeUtils.copy(cache.getTypeFailSafe(expr.falseExpression))) ]);
- typeRef;
- } else {
- throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr)
- }
- default:
- throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr)
- };
- } else {
- // not poly -> directly infer type via type system
- val result = ts.type(G, expr);
- // do *not* store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache!)
- return result;
- }
- }
-
- /**
- * Returns true if we are not allowed to ask for the expected type of 'node', because this would lead to illegal
- * forward references (temporary).
- */
- def private boolean isProblematicCaseOfExpectedType(EObject node) {
- return node?.eContainer instanceof RelationalExpression
- }
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.java
new file mode 100644
index 0000000000..9462748ec2
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.java
@@ -0,0 +1,400 @@
+/**
+ * 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.postprocessing;
+
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.anyTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayNType;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayNTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayType;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isArrayN;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isIterableN;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.iterableType;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.iterableTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.stringType;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.stringTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.undefinedTypeRef;
+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.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.n4js.n4JS.ArrayElement;
+import org.eclipse.n4js.n4JS.ArrayLiteral;
+import org.eclipse.n4js.n4JS.ArrayPadding;
+import org.eclipse.n4js.n4JS.DestructureUtils;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope;
+import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory;
+import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression;
+import org.eclipse.n4js.ts.types.InferenceVariable;
+import org.eclipse.n4js.ts.types.TClass;
+import org.eclipse.n4js.ts.types.TInterface;
+import org.eclipse.n4js.ts.types.Type;
+import org.eclipse.n4js.ts.types.TypeVariable;
+import org.eclipse.n4js.ts.types.util.Variance;
+import org.eclipse.n4js.types.utils.TypeUtils;
+import org.eclipse.n4js.typesystem.N4JSTypeSystem;
+import org.eclipse.n4js.typesystem.constraints.InferenceContext;
+import org.eclipse.n4js.typesystem.utils.RuleEnvironment;
+import org.eclipse.n4js.typesystem.utils.TypeSystemHelper;
+import org.eclipse.n4js.utils.N4JSLanguageUtils;
+import org.eclipse.xtext.xbase.lib.IterableExtensions;
+
+import com.google.common.base.Optional;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * {@link PolyProcessor} delegates here for processing array literals.
+ *
+ * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache)
+ * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)
+ */
+@Singleton
+class PolyProcessor_ArrayLiteral extends AbstractPolyProcessor {
+ @Inject
+ private PolyProcessor polyProcessor;
+ @Inject
+ private N4JSTypeSystem ts;
+ @Inject
+ private TypeSystemHelper tsh;
+
+ /**
+ * BEFORE CHANGING THIS METHOD, READ THIS:
+ * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)}
+ */
+ TypeRef processArrayLiteral(RuleEnvironment G, ArrayLiteral arrLit, TypeRef expectedTypeRef,
+ InferenceContext infCtx, ASTMetaInfoCache cache) {
+
+ // note: we do not have the case !arrLit.isPoly here, as in the other poly processors
+ // (array literals are always poly, because they cannot be explicitly typed in N4JS)
+
+ int numOfElems = arrLit.getElements().size();
+
+ // we have to analyze the type expectation:
+ // 1. we have to know up-front whether we aim for an actual type of Array/Iterable or for ArrayN/IterableN
+ // 2. we have to know if we have concrete expectations for the element type(s)
+ // To do so, we prepare a helper variable 'expectedElemTypeRefs'
+ List expectedElemTypeRefs = getExpectedElemTypeRefs(G, expectedTypeRef);
+
+ // hack: faking an expectation of ArrayN<...> here
+ // TODO instead we should get such an expectation in these cases from expectedType judgment!
+ boolean isValueToBeDestructured = DestructureUtils.isArrayOrObjectLiteralBeingDestructured(arrLit);
+ if (isValueToBeDestructured) {
+ while (expectedElemTypeRefs.size() < numOfElems)
+ expectedElemTypeRefs.add(anyTypeRef(G));
+ }
+
+ // performance tweak:
+ boolean haveUsableExpectedType = !expectedElemTypeRefs.isEmpty();
+ if (!haveUsableExpectedType && !TypeUtils.isInferenceVariable(expectedTypeRef)) {
+ // no type expectation or some entirely wrong type expectation (i.e. other than Array, ArrayN)
+ // -> just derive type from elements (and do not introduce a new inference variable for this ArrayLiteral!)
+ List elemTypeRefs = new ArrayList<>();
+ List nonNullElems = toList(
+ IterableExtensions.filter(arrLit.getElements(), ae -> ae.getExpression() != null));
+ for (ArrayElement arrElem : nonNullElems) {
+ var arrElemTypeRef = polyProcessor.processExpr(G, arrElem.getExpression(), null, infCtx, cache);
+ arrElemTypeRef = ts.upperBoundWithReopen(G, arrElemTypeRef);
+ if (arrElem.isSpread()) {
+ // more than one in case of ArrayN; none in case of invalid value after spread operator
+ elemTypeRefs.addAll(extractSpreadTypeRefs(G, arrElemTypeRef));
+ } else {
+ elemTypeRefs.add(arrElemTypeRef);
+ }
+ }
+
+ infCtx.onSolved(solution -> handleOnSolvedPerformanceTweak(G, cache, arrLit, expectedElemTypeRefs));
+
+ TypeRef unionOfElemTypes = (!elemTypeRefs.isEmpty())
+ ? tsh.createUnionType(G, elemTypeRefs.toArray(new TypeRef[0]))
+ : anyTypeRef(G);
+ return arrayTypeRef(G, unionOfElemTypes);
+ }
+
+ int resultLen = getResultLength(arrLit, expectedElemTypeRefs);
+ TypeVariable[] resultInfVars = infCtx.newInferenceVariables(resultLen);
+
+ processElements(G, cache, infCtx, arrLit, resultLen, resultInfVars);
+
+ TypeRef resultTypeRef = getResultTypeRef(G, resultLen, resultInfVars);
+
+ // register onSolved handlers to add final types to cache (i.e. may not contain inference variables)
+ infCtx.onSolved(solution -> handleOnSolved(G, cache, arrLit, expectedElemTypeRefs, resultTypeRef, solution));
+
+ // return temporary type of arrLit (i.e. may contain inference variables)
+ return resultTypeRef;
+ }
+
+ /**
+ * The return value is as follows:
+ *
+ * - #[ T ] for an expectedTypeRef of the form Array<T> or Iterable<T>,
+ * - #[ T1, T2, ..., TN ] for an expectedTypeRef of the form ArrayN<T1,T2,...,TN>,
+ * - #[] for any other kind of expectedTypeRef
+ *
+ */
+ private List getExpectedElemTypeRefs(RuleEnvironment G, TypeRef expectedTypeRef) {
+ if (expectedTypeRef != null) {
+ List candidateTypeRefs = (expectedTypeRef instanceof UnionTypeExpression)
+ ? ((UnionTypeExpression) expectedTypeRef).getTypeRefs()
+ : List.of(expectedTypeRef);
+ TInterface iterableType = iterableType(G);
+ TClass arrayType = arrayType(G);
+ for (TypeRef candidateTypeRef : candidateTypeRefs) {
+ Type declType = candidateTypeRef.getDeclaredType();
+ if (declType == iterableType
+ || declType == arrayType
+ || isIterableN(G, declType)
+ || isArrayN(G, declType)) {
+ List extractedTypeRefs = tsh.extractIterableElementTypes(G, candidateTypeRef);
+ if (extractedTypeRefs.size() > 0) {
+ return extractedTypeRefs; // will have len>1 iff expectation is IterableN
+ }
+ }
+ }
+ }
+ return new ArrayList<>(); // no or invalid type expectation
+ }
+
+ /**
+ * Makes a best effort for building a type in case something went awry. It's only non-trivial in case we have an
+ * expectation of IterableN.
+ */
+ private TypeRef buildFallbackTypeForArrayLiteral(boolean isArrayN, int resultLen,
+ List elemTypeRefsWithLiteralTypes, List expectedElemTypeRefs, RuleEnvironment G) {
+
+ List elemTypeRefs = toList(map(
+ elemTypeRefsWithLiteralTypes, elem -> N4JSLanguageUtils.getLiteralTypeBase(G, elem)));
+
+ if (isArrayN) {
+ TypeRef[] typeArgs = new TypeRef[resultLen];
+ for (var i = 0; i < resultLen; i++) {
+ boolean isLastElem = i == (resultLen - 1);
+ TypeRef typeRef = null;
+ if (isLastElem && elemTypeRefs.size() > resultLen) {
+ // special case:
+ // we are at the last element AND we actually have more elements than we expect elements
+ // -> have to check all remaining elements against the last expectation!
+ List allRemainingElementTypeRefs = new ArrayList<>();
+ TypeRef currExpectedElemTypeRef = expectedElemTypeRefs.get(i);
+
+ // if all remaining elements are a subtype of the last expectation, then use expectation, otherwise
+ // form union
+ boolean allMatch = true;
+ for (var j = i; j < elemTypeRefs.size(); j++) {
+
+ TypeRef currElementTypeRef = elemTypeRefs.get(j);
+ allRemainingElementTypeRefs.add(currElementTypeRef);
+
+ if (allMatch) { // don't try further subtype checks if already failed
+ boolean actualIsSubtypeOfExpected = ts.subtypeSucceeded(G, currElementTypeRef,
+ currExpectedElemTypeRef);
+ if (!actualIsSubtypeOfExpected) {
+ allMatch = false;
+ }
+ }
+ }
+ if (allMatch) {
+ // use expected type
+ typeRef = currExpectedElemTypeRef;
+ } else {
+ // use actual types (will lead to follow-up errors caught by validations)
+ typeRef = tsh.createUnionType(G, allRemainingElementTypeRefs.toArray(new TypeRef[0]));
+ }
+ } else {
+ TypeRef currElemTypeRef = elemTypeRefs.get(i);
+ TypeRef currExpectedElemTypeRef = expectedElemTypeRefs.get(i);
+ boolean actualIsSubtypeOfExpected = ts.subtypeSucceeded(G, currElemTypeRef,
+ currExpectedElemTypeRef);
+ if (actualIsSubtypeOfExpected) {
+ // use expected type
+ typeRef = currExpectedElemTypeRef;
+ } else {
+ // use actual type (will lead to follow-up errors caught by validations)
+ typeRef = currElemTypeRef;
+ }
+ }
+ typeArgs[i] = typeRef;
+ }
+
+ if (elemTypeRefs.size() > resultLen) {
+ // replace last entry in 'typeArgs' with union of all remaining in elemTypeRefs
+ TypeRef[] remaining = Arrays.copyOfRange(elemTypeRefs.toArray(new TypeRef[0]), resultLen - 1,
+ elemTypeRefs.size());
+
+ typeArgs[resultLen - 1] = tsh.createUnionType(G, remaining);
+ }
+
+ return arrayNTypeRef(G, resultLen, typeArgs);
+ } else {
+ TypeRef unionOfElemTypes = (!elemTypeRefs.isEmpty())
+ ? tsh.createUnionType(G, elemTypeRefs.toArray(new TypeRef[0]))
+ : anyTypeRef(G);
+ return arrayTypeRef(G, unionOfElemTypes);
+ }
+ }
+
+ /**
+ * choose correct number of type arguments in our to-be-created resultTypeRef (always 1 for Array or Iterable
+ * but N for ArrayN<..>, e.g. 3 for Array3)
+ */
+ private int getResultLength(ArrayLiteral arrLit, List expectedElemTypeRefs) {
+ int numOfElems = arrLit.getElements().size();
+ int lenA = Math.min(
+ expectedElemTypeRefs.size(), // use number of type arguments provided by type expectation as a basis
+ numOfElems // ... but never more than we have elements in the array literal
+ );
+
+ int lenB = Math.min(
+ lenA,
+ BuiltInTypeScope.ITERABLE_N__MAX_LEN // ... and never more than the max. allowed number of type
+ // arguments for ArrayN
+ );
+
+ int resultLen = Math.max(
+ lenB,
+ 1 // ... but at least 1 (even if numOfElems is 0, for example)
+ );
+ return resultLen;
+ }
+
+ /**
+ * Creates temporary type (i.e. may contain inference variables):
+ *
+ * - Array (where T is a new inference variable) or
+ * - ArrayN (where T1,...TN are new inference variables, N>=2)
+ *
+ */
+ private TypeRef getResultTypeRef(RuleEnvironment G, int resultLen, TypeVariable[] resultInfVars) {
+ boolean isArrayN = resultLen >= 2;
+ TClass declaredType = (isArrayN) ? arrayNType(G, resultLen) : arrayType(G);
+ List typeArgs = toList(
+ map(Arrays.asList(resultInfVars), v -> TypeUtils.createTypeRef(v)));
+ TypeRef resultTypeRef = TypeUtils.createTypeRef(declaredType, typeArgs.toArray(new ParameterizedTypeRef[0]));
+ return resultTypeRef;
+ }
+
+ /**
+ * for each array element, add a constraint to ensure that its corresponding infVar in result type will be a super
+ * type of the array element's expression
+ */
+ private void processElements(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx,
+ ArrayLiteral arrLit,
+ int resultLen, TypeVariable[] resultInfVars) {
+ int numOfElems = arrLit.getElements().size();
+ for (var idxElem = 0; idxElem < numOfElems; idxElem++) {
+ ArrayElement currElem = arrLit.getElements().get(idxElem);
+ if (currElem == null || currElem.getExpression() == null) {
+ // currElem is null, or has no expression (broken AST), or is an ArrayPadding element
+ // -> ignore (no constraint to add)
+ } else {
+ // currElem is a valid ArrayElement with an expression
+ // -> add constraint currElemTypeRef <: Ti (Ti being the corresponding inf. variable in resultTypeRef)
+ int idxResult = Math.min(idxElem, resultLen - 1);
+ TypeVariable currResultInfVar = resultInfVars[idxResult];
+ TypeRef currResultInfVarTypeRef = TypeUtils.createTypeRef(currResultInfVar);
+ TypeRef currExpectedTypeRef = (currElem.isSpread())
+ ? iterableTypeRef(G, TypeUtils.createWildcardExtends(currResultInfVarTypeRef))
+ : currResultInfVarTypeRef;
+ TypeRef currElemTypeRef = polyProcessor.processExpr(G, currElem.getExpression(), currExpectedTypeRef,
+ infCtx, cache);
+ infCtx.addConstraint(currElemTypeRef, currExpectedTypeRef, Variance.CO);
+ }
+ }
+ }
+
+ /**
+ * Writes final types to cache.
+ */
+ private void handleOnSolvedPerformanceTweak(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit,
+ List expectedElemTypeRefs) {
+ List betterElemTypeRefs = storeTypesOfArrayElements(G, cache, arrLit);
+ TypeRef fallbackTypeRef = buildFallbackTypeForArrayLiteral(false, 1, betterElemTypeRefs, expectedElemTypeRefs,
+ G);
+ cache.storeType(arrLit, fallbackTypeRef);
+ }
+
+ /**
+ * Writes final types to cache.
+ */
+ private void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit,
+ List expectedElemTypeRefs, TypeRef resultTypeRef,
+ Optional