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: + * + */ + 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 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 interior(EObject eo, Procedure1 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 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 interior( EObject eo, Procedure1 init ){
-		throw new IllegalStateException("Method should not be called.")
-	}
-
-}
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 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 semiReplacements = toList(
+					filter(conflicting, tr -> tr instanceof InsertSemiBase));
+			List 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 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;
-	}
-}