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: + *
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+ * object.interior, hrf->hrf.indent() + *+ * + * by + * + *
+ * object.interiorBUGFIX(, hrf->hrf.indent(),document); + * + *+ * + */ + void interiorBUGFIX(EObject object, Procedure1 super IHiddenRegionFormatter> init, + IFormattableDocument document) { + + IEObjectRegion objRegion = getTextRegionAccess().regionForEObject(object); + if (objRegion != null) { + IHiddenRegion previous = objRegion.getPreviousHiddenRegion(); + IHiddenRegion next = objRegion.getNextHiddenRegion(); + if (previous != null && next != null && previous != next) { + ISemanticRegion nsr = previous.getNextSemanticRegion(); + ISemanticRegion psr = next.getPreviousSemanticRegion(); + if (nsr != psr) { // standard + // case + document.interior(nsr, psr, init); + } else { + // former error-case: + // there is no interior --> don't do anything! + // + // applying to the next HiddenRegion is a bad idea, + // since it could wrongly indent a multiline-comment (c.f. GHOLD-260) + } + } + } + } + + /** + * Dummy method to prevent accidentally calling interior - extension method form document. You should call + * interiorBUGFIX instead ! + */ + @SuppressWarnings("unused") + Procedure1 super IFormattableDocument> interior(EObject eo, Procedure1 super IHiddenRegionFormatter> init) { + throw new IllegalStateException("Method should not be called."); + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend deleted file mode 100644 index 9ca9a39a56..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend +++ /dev/null @@ -1,1441 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.formatting2; - -import com.google.inject.Inject -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.AbstractAnnotationList -import org.eclipse.n4js.n4JS.AbstractCaseClause -import org.eclipse.n4js.n4JS.AdditiveExpression -import org.eclipse.n4js.n4JS.AnnotableExpression -import org.eclipse.n4js.n4JS.AnnotableN4MemberDeclaration -import org.eclipse.n4js.n4JS.AnnotablePropertyAssignment -import org.eclipse.n4js.n4JS.AnnotableScriptElement -import org.eclipse.n4js.n4JS.Annotation -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.AssignmentExpression -import org.eclipse.n4js.n4JS.AwaitExpression -import org.eclipse.n4js.n4JS.BinaryBitwiseExpression -import org.eclipse.n4js.n4JS.BinaryLogicalExpression -import org.eclipse.n4js.n4JS.BindingPattern -import org.eclipse.n4js.n4JS.Block -import org.eclipse.n4js.n4JS.BooleanLiteral -import org.eclipse.n4js.n4JS.CastExpression -import org.eclipse.n4js.n4JS.CatchBlock -import org.eclipse.n4js.n4JS.CommaExpression -import org.eclipse.n4js.n4JS.ConditionalExpression -import org.eclipse.n4js.n4JS.EqualityExpression -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ExpressionStatement -import org.eclipse.n4js.n4JS.FieldAccessor -import org.eclipse.n4js.n4JS.FinallyBlock -import org.eclipse.n4js.n4JS.ForStatement -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.n4JS.FunctionDeclaration -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.n4JS.GenericDeclaration -import org.eclipse.n4js.n4JS.GetterDeclaration -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.IfStatement -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.IndexedAccessExpression -import org.eclipse.n4js.n4JS.IntLiteral -import org.eclipse.n4js.n4JS.JSXElement -import org.eclipse.n4js.n4JS.MultiplicativeExpression -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4EnumDeclaration -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.N4SetterDeclaration -import org.eclipse.n4js.n4JS.N4TypeVariable -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.NewExpression -import org.eclipse.n4js.n4JS.NullLiteral -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression -import org.eclipse.n4js.n4JS.ParenExpression -import org.eclipse.n4js.n4JS.PostfixExpression -import org.eclipse.n4js.n4JS.PromisifyExpression -import org.eclipse.n4js.n4JS.RegularExpressionLiteral -import org.eclipse.n4js.n4JS.RelationalExpression -import org.eclipse.n4js.n4JS.ReturnStatement -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.ShiftExpression -import org.eclipse.n4js.n4JS.StringLiteral -import org.eclipse.n4js.n4JS.SuperLiteral -import org.eclipse.n4js.n4JS.SwitchStatement -import org.eclipse.n4js.n4JS.TaggedTemplateString -import org.eclipse.n4js.n4JS.TemplateLiteral -import org.eclipse.n4js.n4JS.TemplateSegment -import org.eclipse.n4js.n4JS.ThisLiteral -import org.eclipse.n4js.n4JS.ThrowStatement -import org.eclipse.n4js.n4JS.UnaryExpression -import org.eclipse.n4js.n4JS.UnaryOperator -import org.eclipse.n4js.n4JS.VariableBinding -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.n4JS.YieldExpression -import org.eclipse.n4js.services.N4JSGrammarAccess -import org.eclipse.n4js.ts.typeRefs.IntersectionTypeExpression -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.StaticBaseTypeRef -import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef -import org.eclipse.n4js.ts.typeRefs.ThisTypeRef -import org.eclipse.n4js.ts.typeRefs.ThisTypeRefStructural -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage -import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TStructMember -import org.eclipse.n4js.ts.types.TypesPackage -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.Keyword -import org.eclipse.xtext.formatting2.IAutowrapFormatter -import org.eclipse.xtext.formatting2.IFormattableDocument -import org.eclipse.xtext.formatting2.IHiddenRegionFormatter -import org.eclipse.xtext.formatting2.IHiddenRegionFormatting -import org.eclipse.xtext.formatting2.ITextReplacer -import org.eclipse.xtext.formatting2.internal.SinglelineCodeCommentReplacer -import org.eclipse.xtext.formatting2.internal.SinglelineDocCommentReplacer -import org.eclipse.xtext.formatting2.regionaccess.IComment -import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion -import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion -import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion -import org.eclipse.xtext.formatting2.regionaccess.ITextSegment -import org.eclipse.xtext.xbase.lib.Procedures.Procedure1 -import org.eclipse.xtext.xtext.generator.parser.antlr.splitting.simpleExpressions.NumberLiteral - -import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.* -import static org.eclipse.n4js.formatting2.N4JSGenericFormatter.* - -class N4JSFormatter extends TypeExpressionsFormatter { - - /** Debug switch */ - private static var debug = false; - - @Inject extension N4JSGrammarAccess - - /** PRIO_4 = -7 - still very low. - * Standard priorities in the formatter are: - *
true
do line-wrapping
- * @param isFirstenAnnotation if this is the first annotation in a sequence ( used with line-wrapping in front of '@')
- */
- private def configureAnnotation(Annotation it, extension IFormattableDocument document, boolean withLineWraps, boolean isFirstAnnotation ){
- // configure arguments
- val parens = it.regionFor.keywordPairs("(",")").head;
- if( parens !== null ) {
- parens=>[
- it.key.prepend[noSpace].append[noSpace];
- it.value.prepend[noSpace].append[if( withLineWraps ) {noSpace; newLines=1;} else {oneSpace; newLines=0;}];
- it.interior[indent];
- // line break before "@":
- if( withLineWraps && !isFirstAnnotation ) {
- it.key.previousSemanticRegion.previousSemanticRegion.prepend[newLines = 1];
- }
- ];
- it.configureCommas(document);
- }
-
- // Configure @-Syntax
- // Special case here: for "@XY" we can either get "@" or "XY" as the first semantic element
- it.semanticRegions.head =>[
- if( it.grammarElement instanceof Keyword) {
- // assume '@'
- it.append[ noSpace; newLines=0 ];
- } else {
- it.prepend[ // for "@Final" "Final" will be the first semantic region in case of exported classes,
- noSpace; newLines=0
- ];
- }
- ];
- }
-
- private def dispatch void configureAnnotations(Object semEObject, extension IFormattableDocument document) {
- // no annotations to be configured.
- }
-
- private def dispatch void configureAnnotations(Void x, extension IFormattableDocument document) {
- // no annotations to be configured.
- }
-
- private def void configureAnnotationsInLine(FormalParameter fpar, extension IFormattableDocument document) {
- if( fpar.annotations.isEmpty ) return;
- // (@x @y("") bogus a:typ)
- fpar.annotations.head=>[
- it.configureAnnotation(document,false,true);
- prepend[noSpace;newLines=0;autowrap;];
- ]
- fpar.annotations.tail.forEach[
- configureAnnotation(document,false,false);
- prepend[oneSpace;newLines=0;autowrap;];
- ]
- fpar.annotations.last.append[oneSpace;newLines=0;autowrap;];
- }
-
-
- /** only script-level annotations '@@' */
- private def void formatScriptAnnotations(Script script, extension IFormattableDocument document) {
- if( script.annotations.isEmpty ) return;
-
- if (script.annotations.head.previousHiddenRegion.containsComment) {
- script.annotations.head.prepend[noSpace;];
- } else {
- script.annotations.head.prepend[noSpace;newLines=0;];
- }
- script.annotations.last.append[setNewLines(2,2,2)];
-
- script.annotations.forEach[it,idx|
- if( idx !==0 ) it.prepend[newLines=1;noSpace];
- it.semanticRegions.head=>[ // its an '@@'
- append[noSpace;newLines=0]
- ]
- ]
-
- }
-
-
-
-
- public override ITextReplacer createCommentReplacer(IComment comment) {
- // Overridden to distinguish between JSDOC-style, standard ML, formatter-off ML-comment.
- val EObject grammarElement = comment.getGrammarElement();
- if (grammarElement instanceof AbstractRule) {
- val String ruleName = (/*(AbstractRule)*/ grammarElement).getName();
- if (ruleName.startsWith("ML")) {
- val cText = comment.text;
- if (cText.startsWith("/**") && !cText.startsWith("/***")) { // JSDOC
- return new N4MultilineCommentReplacer(comment, '*');
- } else if (cText.startsWith("/*-")) { // Turn-off formatting.
- return new OffMultilineCommentReplacer(comment, !comment.isNotFirstInLine);
- } else { // All other
- return new FixedMultilineCommentReplacer(comment);
- }
- }
- if (ruleName.startsWith("SL")) {
- if (comment.isNotFirstInLine) {
- return new SinglelineDocCommentReplacer(comment, "//");
- } else {
- return new SinglelineCodeCommentReplacer(comment, "//");
- }
- }
- }
-
- // fall back to super-impl.
- super.createCommentReplacer(comment);
- }
-
- private static def boolean isNotFirstInLine(IComment comment) {
- val lineRegion = comment.getLineRegions().get(0);
-
- return !comment.contains( lineRegion.offset );
- }
-
-
- public override createTextReplacerMerger(){
- return new IndentHandlingTextReplaceMerger(this);
- }
-
- /** DEBUG-helper */
- private def static String containmentStructure(EObject eo) {
- val name = eo.class.simpleName;
- if( eo.eContainer !== null )
- return '''«eo.eContainer.containmentStructure».«eo.eContainingFeature.name»-> «name»'''
- return name
- }
-
- /** HELPER to avoid Warnings in code, since @SuppressWarnings("unused") is not active in xtend code.*/
- def suppressUnusedWarnings(Object ... e)
- {
- PRIO_4;
- }
-
- /**
- * Simple tracker that only gives exactly one time the value {@code true}
- * when calling {@link StateTrack#shouldDoThenDone()}
- */
- private final static class StateTrack {
- private boolean done=false;
-
- /**
- * This method returns {@code true} exactly on it's first invocation. Proceeding calls always return {@code false}.
- *
- * @return Returns {@code true} if not done, immediately switches {@link #done} to {@code true};
- * returns {@code false} if already done.
- */
- def boolean shouldDoThenDone(){
- val ret = !done;
- done = true;
- return ret;
- }
- }
-
-
- /****************************************************************************************************************
- *
- * Type Expression
- *
- ***************************************************************************************************************/
- def dispatch void format(UnionTypeExpression ute, extension IFormattableDocument document) {
- ute.regionFor.keywords("|").forEach[ surround[oneSpace;newLines=0].prepend[autowrap;highPriority] ];
- ute.typeRefs.forEach[format];
- // OLD syntax:
- val kwUnion = ute.regionFor.keyword("union");
- if( kwUnion !== null ) {
- kwUnion.prepend[oneSpace].append[oneSpace;newLines=0].nextSemanticRegion/*'{'*/.append[oneSpace;newLines=0];
- ute.semanticRegions.last/*'}'*/.prepend[oneSpace;newLines=0];
- }
- }
-
- def dispatch void format(IntersectionTypeExpression ite, extension IFormattableDocument document) {
- ite.regionFor.keywords("&").forEach[surround[oneSpace;newLines=0].prepend[autowrap;highPriority]];
- ite.typeRefs.forEach[format];
- // OLD syntax
- val kwInersection = ite.regionFor.keyword("intersection");
- if( kwInersection !== null ) {
- kwInersection.prepend[oneSpace].append[oneSpace;newLines=0].nextSemanticRegion/*'{'*/.append[oneSpace;newLines=0];
- ite.semanticRegions.last/*'}'*/.prepend[oneSpace;newLines=0];
- }
- }
-
- def dispatch void format( TStructMember tsm, extension IFormattableDocument document) {
- if(tsm instanceof TField) {
- tsm.configureOptionality(document);
- } else if(tsm instanceof org.eclipse.n4js.ts.types.FieldAccessor) {
- tsm.configureGetSetKeyword(document);
- tsm.configureOptionality(document);
-
- val parenPair = tsm.regionFor.keywordPairs("(",")").head;
- parenPair.key.prepend[noSpace; newLines = 0].append[noSpace];
- }
- // get, set, method, field
- tsm.eContents.forEach[format;];
- // TODO format TStruct* more thoroughly
- // (note: added some TStructMember formatting while working on IDE-2405, but it is still incomplete!)
- }
-
-
-
- def private void configureUndefModifier( StaticBaseTypeRef sbtr, extension IFormattableDocument document){
- // UndefModifier "?"
- sbtr.regionFor.feature(TypeRefsPackage.Literals.TYPE_REF__FOLLOWED_BY_QUESTION_MARK).prepend[noSpace;newLines=0;];
- }
-
- def dispatch void format( ThisTypeRef ttr, extension IFormattableDocument document) {
- ttr.configureUndefModifier(document);
- if( ttr instanceof ThisTypeRefStructural) {
- ttr.interiorBUGFIX([indent],document)
- configureStructuralAdditions(ttr,document);
- ttr.eContents.forEach[
- format;
- ]
- }
- }
-
- def dispatch void format( ParameterizedTypeRef ptr, extension IFormattableDocument document) {
- ptr.interiorBUGFIX([indent],document); //ptr.interior[indent];
- ptr.configureUndefModifier(document);
-
- // Union / Intersection
- ptr.regionFor.keywords("&","|").forEach[ surround[oneSpace;newLines=0].append[autowrap;highPriority]];
- // Short-Hand Syntax for Arrays
- if( ptr.isArrayTypeExpression ) {
- ptr.regionFor.keyword("[").append[noSpace];
- ptr.regionFor.keyword("]").append[noSpace];
- }
- // Short-Hand Syntax for IterableN
- if( ptr.isArrayNTypeExpression ) {
- ptr.regionFor.keyword("[").append[noSpace];
- ptr.regionFor.keyword("]").append[noSpace];
- }
- ptr.formatTypeArguments(document);
-
- // ParameterizedTypeRefStructural :
- configureStructuralAdditions(ptr,document);
-
- // generically format content:
- ptr.eContents.forEach[format]
- }
-
- /** used for "~X with {}" except for the 'X' part. */
- def void configureStructuralAdditions( TypeRef ptr, extension IFormattableDocument document) {
- val semRegTypingStrategy = ptr.regionFor.ruleCallTo(typingStrategyUseSiteOperatorRule);
- if( semRegTypingStrategy!== null) {
- semRegTypingStrategy.prepend[oneSpace].append[noSpace;newLines=0;];
-
- // declaredType
- semRegTypingStrategy.nextSemanticRegion.append[];
-
- val kwWith = ptr.regionFor.keyword("with");
- if( kwWith !== null ) {
- kwWith.surround[oneSpace;newLines=0;autowrap];
- val bracesPair = ptr.regionFor.keywordPairs("{","}").head;
- bracesPair.key.append[noSpace;newLines=0;autowrap];
- bracesPair.value.prepend[noSpace;newLines=0;autowrap];
- //ptr.regionFor.keywords(",",";").forEach[ prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap] ]
- ptr.regionFor.keywords(";").forEach[ prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap;lowPriority] ]
- ((ptr as StructuralTypeRef).astStructuralMembers.tail
- .forEach[ regionForEObject.previousHiddenRegion.set[oneSpace;newLines=0;autowrap]]
- );
- }
- }
- }
-
- /** formats type argument section including outside border. */
- def void formatTypeArguments(ParameterizedTypeRef semObject, extension IFormattableDocument document) {
- if( semObject.declaredTypeArgs.isEmpty ) return;
- // to "<":
- semObject.regionFor.keyword("<").append[noSpace].prepend[noSpace; newLines=0; lowPriority];
- semObject.regionFor.keyword(">").prepend[noSpace].append[noSpace; newLines=0; lowPriority];
- for( typeArg: semObject.declaredTypeArgs ){
- typeArg.append[noSpace].immediatelyFollowing.keyword(",").append[oneSpace];
- typeArg.format(document);
- }
-
- }
-
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Configure Methods
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- def private void configureGetSetKeyword(FieldAccessor fieldAccessor, extension IFormattableDocument document) {
- val kw = if(fieldAccessor instanceof GetterDeclaration) "get" else "set";
- fieldAccessor.regionFor.keyword(kw).prepend[oneSpace].append[oneSpace; newLines=0; autowrap];
- }
- def private void configureGetSetKeyword(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, extension IFormattableDocument document) {
- val kw = if(tFieldAccessor instanceof TGetter) "get" else "set";
- tFieldAccessor.regionFor.keyword(kw).prepend[oneSpace].append[oneSpace; newLines=0; autowrap];
- }
-
- def private void configureOptionality(N4FieldDeclaration fieldDecl, extension IFormattableDocument document) {
- fieldDecl.regionFor.feature(N4JSPackage.Literals.N4_FIELD_DECLARATION__DECLARED_OPTIONAL).prepend[noSpace;newLines=0;];
- }
- def private void configureOptionality(FieldAccessor fieldAccessor, extension IFormattableDocument document) {
- fieldAccessor.regionFor.feature(N4JSPackage.Literals.FIELD_ACCESSOR__DECLARED_OPTIONAL).prepend[noSpace;newLines=0;];
- }
- def private void configureOptionality(TField tField, extension IFormattableDocument document) {
- tField.regionFor.feature(TypesPackage.Literals.TFIELD__OPTIONAL).prepend[noSpace;newLines=0;];
- }
- def private void configureOptionality(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, extension IFormattableDocument document) {
- tFieldAccessor.regionFor.feature(TypesPackage.Literals.FIELD_ACCESSOR__OPTIONAL).prepend[noSpace;newLines=0;];
- }
-
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Bug-workarounds
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /** Temporarily used method to replace document.interior(EObject, Procedure1) to prevent wrong indentations.
- *
- * Main pattern replace document-extension method call:
- * - * object.interior[indent] - *- * by - *
- * object.interiorBUGFIX([indent],document); - *- * - */ - def void interiorBUGFIX(EObject object, Procedure1 super IHiddenRegionFormatter> init,IFormattableDocument document ){ - val IEObjectRegion objRegion = getTextRegionAccess().regionForEObject(object); - if (objRegion !== null) { - val IHiddenRegion previous = objRegion.getPreviousHiddenRegion(); - val IHiddenRegion next = objRegion.getNextHiddenRegion(); - if (previous !== null && next !== null && previous != next) { - val nsr = previous.getNextSemanticRegion(); - val psr = next.getPreviousSemanticRegion(); - if( nsr != psr ) - { // standard case - document.interior(nsr, psr , init); - } else { - // former error-case: - // there is no interior --> don't do anything! - // - // applying to the next HiddenRegion is a bad idea, - // since it could wrongly indent a multiline-comment (c.f. GHOLD-260) - } - } - } - } - - - /** Dummy method to prevent accidentally calling interior - extension method form document. You should call interiorBUGFIX instead ! */ - def Procedure1 super IFormattableDocument> interior( EObject eo, Procedure1 super IHiddenRegionFormatter> init ){ - throw new IllegalStateException("Method should not be called.") - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java new file mode 100644 index 0000000000..17560c88f9 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java @@ -0,0 +1,368 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.formatting2; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.groupBy; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.sortBy; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.services.N4JSGrammarAccess; +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.formatting2.AbstractFormatter2; +import org.eclipse.xtext.formatting2.IFormattableDocument; +import org.eclipse.xtext.formatting2.IHiddenRegionFormatting; +import org.eclipse.xtext.formatting2.ITextReplacer; +import org.eclipse.xtext.formatting2.ITextReplacerContext; +import org.eclipse.xtext.formatting2.internal.HiddenRegionReplacer; +import org.eclipse.xtext.formatting2.internal.TextReplacerMerger; +import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion; +import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions; +import org.eclipse.xtext.formatting2.regionaccess.ITextSegment; +import org.eclipse.xtext.xbase.lib.Pair; + +/** + * + */ +@SuppressWarnings("restriction") +@FinalFieldsConstructor +class N4JSGenericFormatter { + + N4JSGrammarAccess grammarAccess; + ITextRegionExtensions trExtensions; + + public static int PRIO_1 = -10; + public static int PRIO_2 = -9; + public static int PRIO_3 = -8; + + public N4JSGenericFormatter(N4JSGrammarAccess grammarAccess, ITextRegionExtensions trExtensions) { + this.grammarAccess = grammarAccess; + this.trExtensions = trExtensions; + } + + public void formatColon(EObject semanticElement, IFormattableDocument document) { + for (ISemanticRegion colon : trExtensions.allRegionsFor(semanticElement).keywords(":")) { + ISemanticRegion sr = document.prepend(colon, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + hrf.setPriority(PRIO_3); + }); + document.append(sr, hrf -> { + hrf.oneSpace(); + hrf.setPriority(PRIO_2); + }); + } + } + + /** + * Formats whitespace around already present semicolons (;) and inserts new semicolons where the parser expects + * them. + */ + public void formatSemicolons(EObject script, IFormattableDocument document) { + for (ISemanticRegion region : trExtensions.allRegionsFor(script).ruleCallsTo(grammarAccess.getSemiRule())) { + String text = region.getText(); + IHiddenRegion previous = region.getPreviousHiddenRegion(); + if (text == ";") { + + // there is already a ";" so let's format it + document.prepend(region, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + hrf.highPriority(); + }); + } else if (region.getNextSemanticRegion() != null && "}".equals(region.getNextSemanticRegion().getText()) + && !region.isMultiline()) { + // do nothing + } else if (previous.containsComment()) { + + // we're behind a comment - insert semicolon before the comment + ITextSegment insertAt = region.getTextRegionAccess().regionForOffset(previous.getOffset(), 0); + document.addReplacer(new InsertSemi(insertAt, ";")); + } else if (text.trim().isEmpty()) { + // Don't eat up white space here. + // Look for first line break and replace with ";\n": + int lbIdx = text.indexOf("\n"); + if (lbIdx >= 0) { + ITextSegment replaceRegion = region.getTextRegionAccess().regionForOffset(region.getOffset(), + lbIdx + 1); + document.addReplacer(new InsertSemi(replaceRegion, ";\n")); + } else { + // the text region only contains whitespace, e.g. so let's insert a ; and a "\n" + document.addReplacer(new InsertSemi(region, ";")); + } + } else { + + // we probably are the comment, so let's prefix it with ; + ITextSegment insertAt = region.getTextRegionAccess().regionForOffset(region.getOffset(), 0); + document.addReplacer(new InsertSemi(insertAt, ";")); + } + } + } + + /** + * Format whitespace around (), [], and {} + * + * When multiple pairs of (), [], or {} open in the same line, indentation is only applied for the innermost pair. + */ + public void formatParenthesisBracketsAndBraces(EObject script, IFormattableDocument document) { + List> all = new ArrayList<>(); + all.addAll(trExtensions.allRegionsFor(script).keywordPairs("(", ")")); + all.addAll(trExtensions.allRegionsFor(script).keywordPairs("{", "}")); + all.addAll(trExtensions.allRegionsFor(script).keywordPairs("[", "]")); + + Map >> byLine = groupBy(all, + p -> p.getKey().getLineRegions().get(0).getOffset()); + + for (Entry >> e : byLine.entrySet()) { + List > bracePairsInSameLine = sortBy(e.getValue(), + p -> p.getKey().getOffset()); + Pair outermost = bracePairsInSameLine.get(0); + Pair innermost = last(bracePairsInSameLine); + + // keep track of lastOpen/lastClose to avoid formatting the HiddenRegion twice if that hidden region + // is located directly between two brackets. Then, first.append[] would collide with second.prepend[]. + IHiddenRegion lastOpen = null; + IHiddenRegion lastClose = null; + for (Pair pair : bracePairsInSameLine) { + ISemanticRegion open = pair.getKey(); + ISemanticRegion close = pair.getValue(); + if (open.getPreviousHiddenRegion() != lastOpen && lastOpen != null) { + // something between last opening and this one; null-check for first opening-> don't force noSpace + // here + document.prepend(open, hrf -> { + hrf.noSpace(); + hrf.setPriority(PRIO_1); + }); + } + if (pair != innermost) { + document.append(open, hrf -> { + hrf.noSpace(); + hrf.setPriority(PRIO_1); + }); + document.prepend(close, hrf -> { + hrf.noSpace(); + hrf.setPriority(PRIO_1); + }); + } + if (close.getNextHiddenRegion() != lastClose) { + if (pair == outermost) { + // close.appendNewLine(document) + } else { + document.append(close, hrf -> { + hrf.noSpace(); + hrf.setPriority(PRIO_1); + }); + } + } + lastOpen = open.getNextHiddenRegion(); + lastClose = close.getPreviousHiddenRegion(); + } + + ISemanticRegion open = innermost.getKey(); + ISemanticRegion close = innermost.getValue(); + if (open.getNextSemanticRegion() == close && !open.getNextHiddenRegion().isMultiline()) { // empty + // brace-pair + document.append(open, hrf -> { + hrf.noSpace(); + hrf.setPriority(PRIO_1); + }); + } // otherwise, if there is a newline before the innermost closing bracket, we want to format the surrounded + // tokens multiline style. + else if (close.getPreviousHiddenRegion().isMultiline()) { + appendNewLine(document.prepend(close, hrf -> { + hrf.newLine(); + hrf.setPriority(PRIO_1); + }), document); + document.append(open, hrf -> { + hrf.newLine(); + hrf.setPriority(PRIO_1); + }); + document.interior(innermost, hrf -> hrf.indent()); + for (ISemanticRegion comma : trExtensions.regionFor(open.getSemanticElement()).keywords(",")) { + // FIXME: bug in toString: println(comma.toString); + ISemanticRegion sr = document.prepend(comma, hrf -> { + hrf.noSpace(); + hrf.setPriority(PRIO_1); + }); + document.append(sr, hfr -> { + // If dangling comma, then a conflict arises with new line of close. + // Avoid here by using Prio_2 + hfr.setNewLines(1, 1, 2); + hfr.setPriority(PRIO_2); + }); + } + } // otherwise, format the tokens into a single line. + else { + if (document.getRequest().getPreferences() + .getPreference(N4JSFormatterPreferenceKeys.FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE)) { + // configured way to have single space in parenthesised expression. + document.prepend(close, hrf -> { + hrf.oneSpace(); + hrf.setPriority(PRIO_1); + }); + document.append(open, hrf -> { + hrf.oneSpace(); + hrf.setPriority(PRIO_1); + }); + } + for (ISemanticRegion comma : trExtensions.regionFor(open.getSemanticElement()).keywords(",")) { + ISemanticRegion sr = document.prepend(comma, hrf -> { + hrf.noSpace(); + hrf.setPriority(PRIO_1); + }); + document.append(sr, hrf -> { + hrf.oneSpace(); + hrf.setPriority(PRIO_1); + }); + } + } + } + } + + public ISemanticRegion appendNewLine(ISemanticRegion appendAfter, IFormattableDocument doc) { + EObject semi = appendAfter.getNextSemanticRegion() == null ? null + : appendAfter.getNextSemanticRegion().getGrammarElement(); + if (semi instanceof RuleCall && ((RuleCall) semi).getRule() == grammarAccess.getSemiRule()) { + // noop, handled by org.eclipse.n4js.formatting2.N4JSGenericFormatter.formatSemicolons(EObject, + // IFormattableDocument) + } else { + doc.append(appendAfter, hrf -> { + hrf.newLine(); + hrf.setPriority(PRIO_1); + }); + } + return appendAfter; + } + + static interface InsertSemiBase extends ITextReplacer { + // marker interface + } + + @Accessors + static class InsertSemi implements InsertSemiBase { + ITextSegment region; + String text; + + public InsertSemi(ITextSegment region, String text) { + this.region = region; + this.text = text; + } + + @Override + public ITextReplacerContext createReplacements(ITextReplacerContext context) { + context.addReplacement(region.replaceWith(text)); + return context; + } + + @Override + public ITextSegment getRegion() { + return this.region; + } + + public String getText() { + return this.text; + } + } + + static class InsertSemiFollowedByTextReplacer implements InsertSemiBase { + InsertSemiBase insertSemi; + ITextReplacer textReplacer; + ITextSegment region; + + InsertSemiFollowedByTextReplacer(InsertSemiBase insertSemi, ITextReplacer textReplacer) { + this.insertSemi = insertSemi; + this.textReplacer = textReplacer; + this.region = insertSemi.getRegion().merge(textReplacer.getRegion()); + } + + @Override + public ITextReplacerContext createReplacements(ITextReplacerContext context) { + // First insert semicolon + ITextReplacerContext replContext = insertSemi.createReplacements(context).withReplacer(textReplacer); + // Then apply the text replacer + return textReplacer.createReplacements(replContext); + } + + @Override + public ITextSegment getRegion() { + return this.region; + } + } + + static class IndentHandlingTextReplaceMerger extends TextReplacerMerger { + private final static Logger LOGGER = Logger.getLogger(IndentHandlingTextReplaceMerger.class); + + AbstractFormatter2 fmt; + + IndentHandlingTextReplaceMerger(AbstractFormatter2 formatter) { + super(formatter); + fmt = formatter; + } + + /** + * Overridden for special case of {@link InsertSemi} & {@link HiddenRegionReplacer} merging. Calls super + * implementation if no InsertSemi object is involved + */ + @Override + public ITextReplacer merge(List extends ITextReplacer> conflicting) { + if (findFirst(conflicting, tr -> tr instanceof InsertSemiBase) == null) { + // standard case, but not handled as we want by super because due to ASI there can be + // HiddenRegionReplacer that have equal offsets and length but are not identical. + List hrf = toList(filter(conflicting, HiddenRegionReplacer.class)); + if (hrf.size() == conflicting.size()) { + IHiddenRegionFormatting merged = fmt.createHiddenRegionFormattingMerger() + .merge(toList(map(hrf, hrr -> hrr.getFormatting()))); + if (merged != null) { + return fmt.createHiddenRegionReplacer(hrf.get(0).getRegion(), merged); + } + } + return super.merge(conflicting); + } + + // there is an insertSemi. + List extends ITextReplacer> semiReplacements = toList( + filter(conflicting, tr -> tr instanceof InsertSemiBase)); + List extends ITextReplacer> otherReplacements = toList( + filter(conflicting, tr -> !(tr instanceof InsertSemiBase))); + + if (semiReplacements.size() != 1 || otherReplacements.size() != 1) { + LOGGER.warn(""" + Unhandled merge-case: " + "Semis replacer («semiReplacements.size») :«semiReplacements» + "Non-Semi replacer ( «otherReplacements.size» «otherReplacements» + """); + return null; // null creates merge Exception + } + // exactly one: + InsertSemiBase semiRepl = (InsertSemiBase) semiReplacements.get(0); + ITextReplacer otherRepl = otherReplacements.get(0); + + if (otherRepl instanceof HiddenRegionReplacer) { + return new InsertSemiFollowedByTextReplacer(semiRepl, otherRepl); + } + + return null; + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend deleted file mode 100644 index 886173e018..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.formatting2 - -import org.eclipse.n4js.services.N4JSGrammarAccess -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtend.lib.annotations.Accessors -import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor -import org.eclipse.xtext.RuleCall -import org.eclipse.xtext.formatting2.IFormattableDocument -import org.eclipse.xtext.formatting2.ITextReplacer -import org.eclipse.xtext.formatting2.ITextReplacerContext -import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion -import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion -import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions -import org.eclipse.xtext.formatting2.regionaccess.ITextSegment -import org.eclipse.xtext.formatting2.internal.TextReplacerMerger -import org.eclipse.xtext.formatting2.AbstractFormatter2 -import java.util.List -import org.eclipse.n4js.utils.Log -import org.eclipse.xtext.formatting2.internal.HiddenRegionReplacer - -/** - * - */ -@FinalFieldsConstructor class N4JSGenericFormatter { - - val extension N4JSGrammarAccess - val extension ITextRegionExtensions - - public static val PRIO_1 = -10 - public static val PRIO_2 = -9 - public static val PRIO_3 = -8 - - def void formatColon(EObject semanticElement, extension IFormattableDocument document) { - for (colon : semanticElement.allRegionsFor.keywords(":")) { - colon.prepend[noSpace; newLines = 0; priority = PRIO_3] - .append[oneSpace; priority = PRIO_2]; - } - } - - /** - * Formats whitespace around already present semicolons (;) and inserts new semicolons where the parser expects them. - */ - def void formatSemicolons(EObject script, extension IFormattableDocument document) { - for (region : script.allRegionsFor.ruleCallsTo(semiRule)) { - val text = region.text; - val previous = region.previousHiddenRegion; - if (text == ";") { - - // there is already a ";" so let's format it - region.prepend[noSpace; newLines = 0; highPriority]; - } else if(region.nextSemanticRegion?.text == "}" && !region.isMultiline){ - // do nothing - } - else if (previous.containsComment) { - - // we're behind a comment - insert semicolon before the comment - val insertAt = region.textRegionAccess.regionForOffset(previous.offset, 0) - document.addReplacer(new InsertSemi(insertAt, ";")); - } else if (text.trim.isEmpty) { - // Don't eat up white space here. - // Look for first line break and replace with ";\n": - val lbIdx = text.indexOf("\n"); - if( lbIdx >= 0 ) { - val replaceRegion = region.textRegionAccess.regionForOffset(region.offset, lbIdx+1 ); - document.addReplacer(new InsertSemi(replaceRegion, ";\n")); - } else { - // the text region only contains whitespace, e.g. so let's insert a ; and a "\n" - document.addReplacer(new InsertSemi(region, ";")); - } - } else { - - // we probably are the comment, so let's prefix it with ; - val insertAt = region.textRegionAccess.regionForOffset(region.offset, 0); - document.addReplacer(new InsertSemi(insertAt, ";")); - } - } - } - - /** - * Format whitespace around (), [], and {} - * - * When multiple pairs of (), [], or {} open in the same line, indentation is only applied for the innermost pair. - */ - def void formatParenthesisBracketsAndBraces(EObject script, extension IFormattableDocument document) { - val all = newArrayList() - all += script.allRegionsFor.keywordPairs("(", ")") - all += script.allRegionsFor.keywordPairs("{", "}") - all += script.allRegionsFor.keywordPairs("[", "]") - - val byLine = all.groupBy[key.lineRegions.head.offset] - - for (e : byLine.entrySet) { - val bracePairsInSameLine = e.value.sortBy[key.offset] - val outermost = bracePairsInSameLine.head - val innermost = bracePairsInSameLine.last - - // keep track of lastOpen/lastClose to avoid formatting the HiddenRegion twice if that hidden region - // is located directly between two brackets. Then, first.append[] would collide with second.prepend[]. - var IHiddenRegion lastOpen = null - var IHiddenRegion lastClose = null - for (pair : bracePairsInSameLine) { - val open = pair.key - val close = pair.value - if (open.previousHiddenRegion != lastOpen && lastOpen !== null) { // something between last opening and this one; null-check for first opening-> don't force noSpace here - open.prepend[noSpace; priority = PRIO_1] - } - if (pair !== innermost) { - open.append[noSpace; priority = PRIO_1] - close.prepend[noSpace; priority = PRIO_1] - } - if (close.nextHiddenRegion != lastClose) { - if (pair === outermost) { -// close.appendNewLine(document) - } else { - close.append[noSpace; priority = PRIO_1] - } - } - lastOpen = open.nextHiddenRegion - lastClose = close.previousHiddenRegion - } - - val ISemanticRegion open = innermost.key; - val ISemanticRegion close = innermost.value; - if (open.nextSemanticRegion == close && !open.nextHiddenRegion.isMultiline) { // empty brace-pair - open.append[noSpace; priority = PRIO_1]; - } // otherwise, if there is a newline before the innermost closing bracket, we want to format the surrounded tokens multiline style. - else if (close.previousHiddenRegion.isMultiline) { - close.prepend[newLine; priority = PRIO_1].appendNewLine(document); - open.append[newLine; priority = PRIO_1]; - innermost.interior[indent]; - for (comma : open.semanticElement.regionFor.keywords(",")) { - // FIXME: bug in toString: println(comma.toString); - comma.prepend[noSpace; priority = PRIO_1] - .append[ - // If dangling comma, then a conflict arises with new line of close. - // Avoid here by using Prio_2 - setNewLines(1,1,2); - priority = PRIO_2 - ]; - } - } // otherwise, format the tokens into a single line. - else { - if( document.request.preferences.getPreference( N4JSFormatterPreferenceKeys.FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE ) ) { // configured way to have single space in parenthesised expression. - close.prepend[oneSpace; priority = PRIO_1] - open.append[oneSpace; priority = PRIO_1] - } - for (comma : open.semanticElement.regionFor.keywords(",")) { - comma.prepend[noSpace; priority = PRIO_1].append[oneSpace; priority = PRIO_1] - } - } - } - } - - def ISemanticRegion appendNewLine(ISemanticRegion appendAfter, extension IFormattableDocument doc) { - val semi = appendAfter.nextSemanticRegion?.grammarElement - if (semi instanceof RuleCall && (semi as RuleCall)?.rule == semiRule) { - // noop, handled by org.eclipse.n4js.formatting2.N4JSGenericFormatter.formatSemicolons(EObject, IFormattableDocument) - } else { - appendAfter.append[newLine; priority = PRIO_1] - } - return appendAfter - } -} - -interface InsertSemiBase extends ITextReplacer {} - -@Accessors class InsertSemi implements InsertSemiBase { - val ITextSegment region - val String text - - override createReplacements(ITextReplacerContext context) { - context.addReplacement(region.replaceWith(text)) - return context; - } -} - -class InsertSemiFollowedByTextReplacer implements InsertSemiBase { - val InsertSemiBase insertSemi; - val ITextReplacer textReplacer; - val ITextSegment region; - - new(InsertSemiBase insertSemi, ITextReplacer textReplacer) { - this.insertSemi = insertSemi; - this.textReplacer = textReplacer; - this.region = insertSemi.region.merge(textReplacer.region) - } - - override createReplacements(ITextReplacerContext context) { - // First insert semicolon - var replContext = insertSemi.createReplacements(context).withReplacer(textReplacer); - // Then apply the text replacer - return textReplacer.createReplacements(replContext); - } - - override getRegion() { - return this.region; - } -} - -@Log -class IndentHandlingTextReplaceMerger extends TextReplacerMerger { - val AbstractFormatter2 fmt - - new(AbstractFormatter2 formatter) { - super(formatter) - fmt = formatter - } - - /** Overridden for special case of {@link InsertSemi} & {@link HiddenRegionReplacer} merging. - * Calls super implementation if no InsertSemi object is involved */ - override merge(List extends ITextReplacer> conflicting) { - if(conflicting.findFirst[it instanceof InsertSemiBase] === null ) { - // standard case, but not handled as we want by super because due to ASI there can be - // HiddenRegionReplacer that have equal offsets and length but are not identical. - val hrf = conflicting.filter(HiddenRegionReplacer).toList - if(hrf.size === conflicting.size) { - val merged = fmt.createHiddenRegionFormattingMerger.merge(hrf.map[formatting]) - if(merged !== null) { - return fmt.createHiddenRegionReplacer(hrf.head.region, merged) - } - } - return super.merge(conflicting); - } - - // there is an insertSemi. - val semiReplacements = conflicting.filter[it instanceof InsertSemiBase].toList; - val otherReplacements = conflicting.filter[!(it instanceof InsertSemiBase)].toList; - - if( semiReplacements.size !== 1 || otherReplacements.size !== 1 ) { - logger.warn( ''' - Unhandled merge-case: " - "Semis replacer («semiReplacements.size») :«semiReplacements» - "Non-Semi replacer ( «otherReplacements.size» «otherReplacements» - '''); - return null; // null creates merge Exception - } - // exactly one: - val semiRepl = semiReplacements.get(0) as InsertSemiBase; - val otherRepl = otherReplacements.get(0); - - if( otherRepl instanceof HiddenRegionReplacer ) { - return new InsertSemiFollowedByTextReplacer(semiRepl, otherRepl); - } - - return null; - } -}