diff --git a/CHANGELOG.md b/CHANGELOG.md index e76bb677..cd53ddff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Added `wrapping.opAddSubChain` ([#370](https://github.com/HaxeCheckstyle/haxe-formatter/issues/370)) - Added `wrapping.metadataCallParameter` ([#370](https://github.com/HaxeCheckstyle/haxe-formatter/issues/370)) - Added `emptyLines.macroClassEmptyLines`, fixes [#377](https://github.com/HaxeCheckstyle/haxe-formatter/issues/377) ([#383](https://github.com/HaxeCheckstyle/haxe-formatter/issues/383)) +- Added `emptyLines.lineCommentsBetweenTypes` and `emptyLines.lineCommentsBetweenTypes` to separate line comments from types and functions ([#387](https://github.com/HaxeCheckstyle/haxe-formatter/issues/387)) - Fixed type parameter constraint with structure type, fixes [#337](https://github.com/HaxeCheckstyle/haxe-formatter/issues/337) ([#349](https://github.com/HaxeCheckstyle/haxe-formatter/issues/349)) - Fixed wrapping of OpBool chains with null ([#349](https://github.com/HaxeCheckstyle/haxe-formatter/issues/349)) - Fixed line comments after typedefs, fixes [#331](https://github.com/HaxeCheckstyle/haxe-formatter/issues/331) ([#349](https://github.com/HaxeCheckstyle/haxe-formatter/issues/349)) diff --git a/resources/default-hxformat.json b/resources/default-hxformat.json index 6bf45db9..783241aa 100644 --- a/resources/default-hxformat.json +++ b/resources/default-hxformat.json @@ -80,6 +80,8 @@ "betweenVars": 0, "endType": 0 }, + "lineCommentsBetweenFunctions": 1, + "lineCommentsBetweenTypes": 1, "macroClassEmptyLines": { "afterPrivateFunctions": 1, "afterPrivateVars": 1, diff --git a/resources/formatter-schema.json b/resources/formatter-schema.json index 597abd6a..dcc091ca 100644 --- a/resources/formatter-schema.json +++ b/resources/formatter-schema.json @@ -458,7 +458,7 @@ "keep", "remove" ], - "propertyOrder": 7 + "propertyOrder": 9 }, "afterLeftCurly": { "description": "Remove or keep empty lines below \"{\"", @@ -467,7 +467,7 @@ "keep", "remove" ], - "propertyOrder": 8 + "propertyOrder": 10 }, "afterReturn": { "description": "Remove or keep empty lines below \"return\"", @@ -476,7 +476,7 @@ "keep", "remove" ], - "propertyOrder": 9 + "propertyOrder": 11 }, "afterPackage": { "description": "empty lines after package declaration", @@ -485,12 +485,12 @@ }, "macroClassEmptyLines": { "$ref": "#/definitions/ClassFieldsEmptyLinesConfig", - "propertyOrder": 14 + "propertyOrder": 16 }, "betweenMultilineComments": { "description": "Adds empty lines between two consecutive multiline comments", "type": "integer", - "propertyOrder": 23 + "propertyOrder": 25 }, "finalNewline": { "description": "adds a final newline", @@ -508,16 +508,16 @@ "keep", "remove" ], - "propertyOrder": 10 + "propertyOrder": 12 }, "betweenSingleLineTypes": { "description": "empty lines between two single line types", "type": "integer", - "propertyOrder": 6 + "propertyOrder": 8 }, "conditionalsEmptyLines": { "$ref": "#/definitions/ConditionalEmptyLinesConfig", - "propertyOrder": 20 + "propertyOrder": 22 }, "afterBlocks": { "type": "string", @@ -525,15 +525,15 @@ "keep", "remove" ], - "propertyOrder": 11 + "propertyOrder": 13 }, "enumEmptyLines": { "$ref": "#/definitions/TypedefFieldsEmptyLinesConfig", - "propertyOrder": 18 + "propertyOrder": 20 }, "externClassEmptyLines": { "$ref": "#/definitions/InterfaceFieldsEmptyLinesConfig", - "propertyOrder": 15 + "propertyOrder": 17 }, "betweenTypes": { "description": "empty lines between types", @@ -547,15 +547,20 @@ }, "typedefEmptyLines": { "$ref": "#/definitions/TypedefFieldsEmptyLinesConfig", - "propertyOrder": 19 + "propertyOrder": 21 }, "enumAbstractEmptyLines": { "$ref": "#/definitions/EnumAbstractFieldsEmptyLinesConfig", - "propertyOrder": 12 + "propertyOrder": 14 + }, + "lineCommentsBetweenTypes": { + "description": "empty lines for line comments between types", + "type": "integer", + "propertyOrder": 6 }, "abstractEmptyLines": { "$ref": "#/definitions/ClassFieldsEmptyLinesConfig", - "propertyOrder": 16 + "propertyOrder": 18 }, "beforePackage": { "description": "empty lines before package declaration", @@ -564,7 +569,7 @@ }, "classEmptyLines": { "$ref": "#/definitions/ClassFieldsEmptyLinesConfig", - "propertyOrder": 13 + "propertyOrder": 15 }, "beforeDocCommentEmptyLines": { "description": "\"one\" adds one empty line above doc comments\n\t\t\"none\" removes all empty lines above doc comments\n\t\t\"ignore\" respects empty lines set via \"betweenVars\", \"betweenFunctions\", etc.", @@ -574,16 +579,21 @@ "none", "one" ], - "propertyOrder": 21 + "propertyOrder": 23 }, "interfaceEmptyLines": { "$ref": "#/definitions/InterfaceFieldsEmptyLinesConfig", - "propertyOrder": 17 + "propertyOrder": 19 }, "afterFileHeaderComment": { "description": "Adds empty lines when file starts with a multiline comment", "type": "integer", - "propertyOrder": 22 + "propertyOrder": 24 + }, + "lineCommentsBetweenFunctions": { + "description": "empty lines for line comments between functions", + "type": "integer", + "propertyOrder": 7 } }, "type": "object" diff --git a/schema/JsonSchemaGenerator.hx b/schema/JsonSchemaGenerator.hx index 3f449eee..92c28de6 100644 --- a/schema/JsonSchemaGenerator.hx +++ b/schema/JsonSchemaGenerator.hx @@ -10,6 +10,7 @@ typedef ExtendedFieldsCB = Array->String->Position->DynamicAcce #end // adapted from https://github.com/nadako/haxe-type-to-json-schema + class JsonSchemaGenerator { #if (haxe_ver >= 4.0) static inline var SCHEMA_KEY:String = "$schema"; diff --git a/src/formatter/config/EmptyLinesConfig.hx b/src/formatter/config/EmptyLinesConfig.hx index b4876e15..15ec2a48 100644 --- a/src/formatter/config/EmptyLinesConfig.hx +++ b/src/formatter/config/EmptyLinesConfig.hx @@ -31,6 +31,16 @@ typedef EmptyLinesConfig = { **/ @:default(1) @:optional var betweenTypes:Int; + /** + empty lines for line comments between types + **/ + @:default(1) @:optional var lineCommentsBetweenTypes:Int; + + /** + empty lines for line comments between functions + **/ + @:default(1) @:optional var lineCommentsBetweenFunctions:Int; + /** empty lines between two single line types **/ diff --git a/src/formatter/marker/MarkEmptyLines.hx b/src/formatter/marker/MarkEmptyLines.hx index 218bd525..3f786a39 100644 --- a/src/formatter/marker/MarkEmptyLines.hx +++ b/src/formatter/marker/MarkEmptyLines.hx @@ -380,6 +380,11 @@ class MarkEmptyLines extends MarkerBase { case UNKNOWN: return; } + + if (!currVar) { + markLineCommentsBefore(currToken, config.emptyLines.lineCommentsBetweenFunctions); + markLineCommentsAfter(currToken, config.emptyLines.lineCommentsBetweenFunctions); + } prevToken = skipSharpFields(prevToken); if (prevToken == null) { return; @@ -424,6 +429,47 @@ class MarkEmptyLines extends MarkerBase { } } + function markLineCommentsBefore(token:TokenTree, count:Int) { + if (count <= 0) { + return; + } + if (token.previousSibling == null) { + return; + } + var prev:Null = token.previousSibling; + while (prev != null) { + switch (prev.tok) { + case Comment(_): + case CommentLine(_): + var prevInfo:Null = getPreviousToken(prev); + if ((prevInfo == null) || (prevInfo.whitespaceAfter == Newline)) { + emptyLinesAfter(prev, count); + } + return; + default: + return; + } + prev = prev.previousSibling; + } + } + + function markLineCommentsAfter(token:TokenTree, count:Int) { + if (count <= 0) { + return; + } + if (token.nextSibling == null) { + return; + } + var next:Null = token.nextSibling; + switch (next.tok) { + case CommentLine(_): + if (isNewLineBefore(next)) { + emptyLinesBefore(next, count); + } + default: + } + } + function markExternClass(c:TokenTree, conf:InterfaceFieldsEmptyLinesConfig) { var block:Null = c.access().firstChild().firstOf(BrOpen).token; if (block == null) { @@ -665,11 +711,13 @@ class MarkEmptyLines extends MarkerBase { var prevTypeInfo:Null = null; for (type in types) { var newTypeInfo:TypeEmptyLinesInfo = getTypeInfo(type); + markLineCommentsBefore(type, config.emptyLines.lineCommentsBetweenTypes); + markLineCommentsAfter(type, config.emptyLines.lineCommentsBetweenTypes); if (prevTypeInfo == null) { prevTypeInfo = newTypeInfo; continue; } - var next:Null = getNextToken(prevTypeInfo.token); + var next:Null = getNextToken(prevTypeInfo.lastToken); if (next != null) { switch (next.token.tok) { case Sharp(MarkLineEnds.SHARP_ELSE), Sharp(MarkLineEnds.SHARP_ELSE_IF): @@ -682,33 +730,35 @@ class MarkEmptyLines extends MarkerBase { if (prevTypeInfo.oneLine && newTypeInfo.oneLine) { emptyLines = config.emptyLines.betweenSingleLineTypes; } - emptyLinesAfterSubTree(prevTypeInfo.token, emptyLines); + emptyLinesAfterSubTree(prevTypeInfo.lastToken, emptyLines); + markLineCommentsAfter(prevTypeInfo.typeToken, config.emptyLines.lineCommentsBetweenTypes); prevTypeInfo = newTypeInfo; } } function getTypeInfo(token:TokenTree):TypeEmptyLinesInfo { var info:TypeEmptyLinesInfo = { - token: TokenTreeCheckUtils.getLastToken(token), + lastToken: TokenTreeCheckUtils.getLastToken(token), + typeToken: token, oneLine: false }; - if (isSameLine(token, info.token)) { + if (isSameLine(token, info.lastToken)) { info.oneLine = true; } var atToken:Null = token.access().firstChild().isCIdent().firstOf(At).token; if (atToken != null) { - if (!isSameLine(atToken, info.token)) { + if (!isSameLine(atToken, info.lastToken)) { info.oneLine = false; } } while (true) { - var next:Null = getNextToken(info.token); + var next:Null = getNextToken(info.lastToken); if (next == null) { break; } switch (next.token.tok) { case Sharp(MarkLineEnds.SHARP_END): - info.token = next.token; + info.lastToken = next.token; default: break; } @@ -994,6 +1044,7 @@ typedef ImportPackageInfo = { } typedef TypeEmptyLinesInfo = { - var token:TokenTree; + var lastToken:TokenTree; + var typeToken:TokenTree; var oneLine:Bool; } diff --git a/test/testcases/emptylines/line_comments_between_function.hxtest b/test/testcases/emptylines/line_comments_between_function.hxtest new file mode 100644 index 00000000..d2d8fa13 --- /dev/null +++ b/test/testcases/emptylines/line_comments_between_function.hxtest @@ -0,0 +1,43 @@ +{ +} + +--- + +class Main { + var foo; + // static function main() { + // } + static function main() { + } + // static function main() { + // } + /** + + **/ + static function main() { + } + // static function main() { + // } +} + +--- + +class Main { + var foo; + + // static function main() { + // } + + static function main() {} + + // static function main() { + // } + + /** + + **/ + static function main() {} + + // static function main() { + // } +} diff --git a/test/testcases/emptylines/line_comments_between_function_no_emptyline.hxtest b/test/testcases/emptylines/line_comments_between_function_no_emptyline.hxtest new file mode 100644 index 00000000..87cfbcfb --- /dev/null +++ b/test/testcases/emptylines/line_comments_between_function_no_emptyline.hxtest @@ -0,0 +1,44 @@ +{ + "emptyLines" : { + "lineCommentsBetweenFunctions" : 0 + } +} + +--- + +class Main { + var foo; + // static function main() { + // } + static function main() { + } + // static function main() { + // } + /** + + **/ + static function main() { + } + // static function main() { + // } +} + +--- + +class Main { + var foo; + + // static function main() { + // } + static function main() {} + + // static function main() { + // } + + /** + + **/ + static function main() {} + // static function main() { + // } +} diff --git a/test/testcases/emptylines/line_comments_between_types.hxtest b/test/testcases/emptylines/line_comments_between_types.hxtest new file mode 100644 index 00000000..03f9b351 --- /dev/null +++ b/test/testcases/emptylines/line_comments_between_types.hxtest @@ -0,0 +1,41 @@ +{ +} + +--- + +// class Main { +class Main {} + +// class Main { +typedef Main = String; +// class Main { + +class Main {} + +// class Main { +// } +typedef Main = String; +// class Main { +// } + +--- + +// class Main { + +class Main {} + +// class Main { + +typedef Main = String; + +// class Main { + +class Main {} + +// class Main { +// } + +typedef Main = String; + +// class Main { +// } diff --git a/test/testcases/emptylines/line_comments_between_types_no_emptyline.hxtest b/test/testcases/emptylines/line_comments_between_types_no_emptyline.hxtest new file mode 100644 index 00000000..dd0ca55b --- /dev/null +++ b/test/testcases/emptylines/line_comments_between_types_no_emptyline.hxtest @@ -0,0 +1,28 @@ +{ + "emptyLines" : { + "lineCommentsBetweenTypes" : 0 + } +} + +--- + +// class Main { +// } +class Main {} + +// class Main { +// } +typedef Main = String; +// class Main { +// } + +--- + +// class Main { +// } +class Main {} +// class Main { +// } +typedef Main = String; +// class Main { +// } diff --git a/test/testcases/other/mixed_samples_2.hxtest b/test/testcases/other/mixed_samples_2.hxtest index 4576283f..a092b002 100644 --- a/test/testcases/other/mixed_samples_2.hxtest +++ b/test/testcases/other/mixed_samples_2.hxtest @@ -92,9 +92,11 @@ class UInt { // keep empty lines above // space in { } + public function new() { } // leading/trailing space in anon type + public function peer():{ host:Host, port:Int } { var info = socket.peer(); var host:Host = Type.createEmptyInstance(Host); @@ -106,6 +108,7 @@ class UInt { } // space in catch { } + public function request() { try sock.close() diff --git a/test/testcases/other/mixed_samples_3.hxtest b/test/testcases/other/mixed_samples_3.hxtest new file mode 100644 index 00000000..7ea05959 --- /dev/null +++ b/test/testcases/other/mixed_samples_3.hxtest @@ -0,0 +1,116 @@ +{ + "emptyLines": { + "maxAnywhereInFile": 2, + "afterLeftCurly": "keep", + "lineCommentsBetweenFunctions" : 0, + "lineCommentsBetweenTypes" : 0 + }, + "indentation": { + "character": " " + }, + "whitespace" : { + "bracesConfig": { + "blockBraces": { + "openingPolicy": "around", + "closingPolicy": "around", + "removeInnerWhenEmpty": false + }, + "anonTypeBraces": { + "openingPolicy": "around", + "closingPolicy": "around", + "removeInnerWhenEmpty": false + }, + "objectLiteralBraces": { + "openingPolicy": "around", + "closingPolicy": "around", + "removeInnerWhenEmpty": false + } + }, + "classEmptyLines": { + "beginType": 1 + } + } +} + +--- + +class UInt { + + /** empty line at type beginning & keep grouped functions/vars declarations */ + @:commutative @:op(A + B) private static function addI(lhs:UInt, rhs:Int):UInt; + @:commutative @:op(A + B) private static function addF(lhs:UInt, rhs:Float):Float; + @:op(A + B) private static function add(lhs:UInt, rhs:UInt):UInt; + + @:commutative @:op(A * B) private static function mulI(lhs:UInt, rhs:Int):UInt; + @:commutative @:op(A * B) private static function mulF(lhs:UInt, rhs:Float):Float; + @:op(A * B) private static function mul(lhs:UInt, rhs:UInt):UInt; + + var children:Array; + var attributeMap:Map; + + + // keep empty lines above + // space in { } + public function new() { } + + // leading/trailing space in anon type + public function peer():{ host:Host, port:Int } { + var info = socket.peer(); + var host:Host = Type.createEmptyInstance(Host); + host.init(info.ip); + + + // keep empty lines above + return { host: host, port: info.port }; + } + + // space in catch { } + public function request() { + try + sock.close() + catch(e:Dynamic) { }; + } +} + +--- + +class UInt { + + /** empty line at type beginning & keep grouped functions/vars declarations */ + @:commutative @:op(A + B) private static function addI(lhs:UInt, rhs:Int):UInt; + + @:commutative @:op(A + B) private static function addF(lhs:UInt, rhs:Float):Float; + + @:op(A + B) private static function add(lhs:UInt, rhs:UInt):UInt; + + @:commutative @:op(A * B) private static function mulI(lhs:UInt, rhs:Int):UInt; + + @:commutative @:op(A * B) private static function mulF(lhs:UInt, rhs:Float):Float; + + @:op(A * B) private static function mul(lhs:UInt, rhs:UInt):UInt; + + var children:Array; + var attributeMap:Map; + + // keep empty lines above + // space in { } + public function new() { } + + // leading/trailing space in anon type + public function peer():{ host:Host, port:Int } { + var info = socket.peer(); + var host:Host = Type.createEmptyInstance(Host); + host.init(info.ip); + + + // keep empty lines above + return { host: host, port: info.port }; + } + + // space in catch { } + public function request() { + try + sock.close() + catch (e:Dynamic) { }; + } +}