diff --git a/CHANGELOG.md b/CHANGELOG.md index a0ef3e127e7d..05ed3faa7e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ All notable changes to this project will be documented in this file. When sendin ### Tooling -- +- [Add Formatting CLI tool](https://github.com/ballerina-platform/ballerina-lang/pull/15664) - - - diff --git a/cli/ballerina-launcher/src/main/resources/cli-help/ballerina-format.help b/cli/ballerina-launcher/src/main/resources/cli-help/ballerina-format.help new file mode 100644 index 000000000000..a0abe2622ace --- /dev/null +++ b/cli/ballerina-launcher/src/main/resources/cli-help/ballerina-format.help @@ -0,0 +1,55 @@ +NAME + Ballerina Format - Formats the Ballerina source files according to + the default Ballerina style guide. + Style guide: https://github.com/ballerina-platform/ballerina-lang/tree/master/docs/style_guide + +SYNOPSIS + ballerina format [ballerinaFile | moduleName] [-d | --dry-run] + +DESCRIPTION + Formats Ballerina source files according to the Ballerina style guide. + + Formatting can be performed on a Ballerina project, on a Ballerina Module, or + on a Ballerina file. + + The formatted content will be written to the original files. By using the dry run + option, you will be able to check which files will be formatted after the execution. + + If Ballerina sources contain syntax errors, they will be notified and + formatting will not be proceed until they are fixed. + +SUB COMMANDS + ballerina format + Formats all the Ballerina source files that are available in the given Ballerina project. + This command should be executed from the root of the Ballerina project. + + ballerina format + Formats all the Ballerina source files that are available in the given module. This command + should be executed from the root of the Ballerina project. + + ballerina format + Formats the given Ballerina file to match the default Ballerina source formatting style as per the style guide. + The Formatted source will replace the content of the given file. + +OPTIONS + -d + --dry-run + By providing this option, you can dry run the formatter and see which files will + be formatted after the execution. + + -h + --help + Prints the help guide for the Ballerina format tool. + +EXAMPLES + Formats a Ballerina project. + $ ballerina format + + Formats a Ballerina module. + $ ballerina format module1 + + Formats a Ballerina file. + $ ballerina format hello.bal + + Performs a dry run to see which files will be formatted. + $ ballerina format -d \ No newline at end of file diff --git a/cli/ballerina-launcher/src/main/resources/cli-help/ballerina-help.help b/cli/ballerina-launcher/src/main/resources/cli-help/ballerina-help.help index 082636e8ead4..595de3e7cf65 100644 --- a/cli/ballerina-launcher/src/main/resources/cli-help/ballerina-help.help +++ b/cli/ballerina-launcher/src/main/resources/cli-help/ballerina-help.help @@ -20,25 +20,26 @@ DESCRIPTION ballerina [COMMAND] [options] COMMAND is one of the available commands listed below: - run Run Ballerina program - build Compile Ballerina program - install Install modules to the home repository - pull Download modules from Ballerina Central - push Upload modules to Ballerina Central - init Initialize Ballerina project - search Search for modules within Ballerina Central - list List dependencies of modules - doc Generate Ballerina API documentation + run Run Ballerina program. + build Compile Ballerina program. + install Install modules to the home repository. + pull Download modules from Ballerina Central. + push Upload modules to Ballerina Central. + init Initialize Ballerina project. + search Search for modules within Ballerina Central. + list List dependencies of modules. + doc Generate Ballerina API documentation. grpc Generate connector/service using protobuf - definition + definition. openapi Generate client/service using openapi definition - or export openapi file for a Ballerina service - test Test Ballerina program - version Print Ballerina version - encrypt Encrypt sensitive data + or export openapi file for a Ballerina service. + test Tests the Ballerina program. + version Prints the Ballerina version. + encrypt Encrypts sensitive data. + format Formats Ballerina source code. OPTIONS - Ballerina help takes no command line options + Ballerina help does not take command line options. EXAMPLES Look for help on how to use the ballerina command diff --git a/distribution/zip/ballerina-tools/build.gradle b/distribution/zip/ballerina-tools/build.gradle index 059d59dadae3..45416b3f6638 100644 --- a/distribution/zip/ballerina-tools/build.gradle +++ b/distribution/zip/ballerina-tools/build.gradle @@ -72,6 +72,7 @@ dependencies { dist project(':openapi-ballerina:openapi-to-ballerina-generator') dist project(':ballerina-backend-jvm') dist project(':language-server:language-server-compiler') + dist project(':ballerina-formatter') dist project(':ballerina-packerina') dist project(':protobuf-ballerina') dist project(':openapi-ballerina:openapi-to-ballerina-generator') diff --git a/distribution/zip/ballerina-tools/pom.xml b/distribution/zip/ballerina-tools/pom.xml index 7894969540cd..7ff7da36c93b 100644 --- a/distribution/zip/ballerina-tools/pom.xml +++ b/distribution/zip/ballerina-tools/pom.xml @@ -282,6 +282,10 @@ org.ballerinalang openapi-to-ballerina-generator + + org.ballerinalang + ballerina-formatter + org.ballerinalang ballerina-client-generator diff --git a/distribution/zip/ballerina-tools/src/assembly/bin.xml b/distribution/zip/ballerina-tools/src/assembly/bin.xml index 96de473a7f96..016c16b6d8ad 100644 --- a/distribution/zip/ballerina-tools/src/assembly/bin.xml +++ b/distribution/zip/ballerina-tools/src/assembly/bin.xml @@ -142,6 +142,7 @@ in ballerina OpenAPI CLI tools --> org.ballerinalang:language-server-compiler:jar + org.ballerinalang:ballerina-formatter:jar org.ballerinalang:ballerina-packerina:jar org.ballerinalang:protobuf-ballerina:jar org.ballerinalang:ballerina-client-generator:jar diff --git a/distribution/zip/jballerina-tools/build.gradle b/distribution/zip/jballerina-tools/build.gradle index d8839087dd57..3181f9fc443c 100644 --- a/distribution/zip/jballerina-tools/build.gradle +++ b/distribution/zip/jballerina-tools/build.gradle @@ -73,7 +73,7 @@ dependencies { dist project(':ballerina-runtime') dist project(':testerina:testerina-core') - + dist project(':ballerina-formatter') dist project(':openapi-ballerina:openapi-to-ballerina-generator') dist project(':ballerina-backend-jvm') dist project(':language-server:language-server-compiler') diff --git a/distribution/zip/jballerina-tools/pom.xml b/distribution/zip/jballerina-tools/pom.xml index 0a35b7381071..7f2fe7e1d94c 100644 --- a/distribution/zip/jballerina-tools/pom.xml +++ b/distribution/zip/jballerina-tools/pom.xml @@ -286,6 +286,10 @@ org.ballerinalang ballerina-client-generator + + org.ballerinalang + ballerina-formatter + org.ballerinalang protobuf-ballerina diff --git a/distribution/zip/jballerina-tools/src/assembly/bin.xml b/distribution/zip/jballerina-tools/src/assembly/bin.xml index 336507edc297..c312394446dc 100644 --- a/distribution/zip/jballerina-tools/src/assembly/bin.xml +++ b/distribution/zip/jballerina-tools/src/assembly/bin.xml @@ -141,6 +141,7 @@ in ballerina OpenAPI CLI tools --> org.ballerinalang:language-server-compiler:jar + org.ballerinalang:ballerina-formatter:jar org.ballerinalang:ballerina-packerina:jar org.ballerinalang:protobuf-ballerina:jar org.ballerinalang:ballerina-client-generator:jar diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingConstants.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingConstants.java similarity index 97% rename from language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingConstants.java rename to language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingConstants.java index 1947ebbff8f7..5dd823074781 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingConstants.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingConstants.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ballerinalang.langserver.formatting; +package org.ballerinalang.langserver.compiler.format; /** * Formatting constants for keywords used in the formatting util. diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingNodeTree.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingNodeTree.java similarity index 99% rename from language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingNodeTree.java rename to language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingNodeTree.java index ccceaa8b5778..a2ba1566f51e 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingNodeTree.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingNodeTree.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ballerinalang.langserver.formatting; +package org.ballerinalang.langserver.compiler.format; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import org.ballerinalang.langserver.compiler.sourcegen.FormattingSourceGen; import java.util.ArrayList; import java.util.List; @@ -3025,8 +3026,8 @@ public void formatPanicNode(JsonObject node) { } // Handle the expression formatting. - if (node.has("expressions")) { - node.getAsJsonObject("expressions").add(FormattingConstants.FORMATTING_CONFIG, + if (node.has(FormattingConstants.EXPRESSIONS)) { + node.getAsJsonObject(FormattingConstants.EXPRESSIONS).add(FormattingConstants.FORMATTING_CONFIG, this.getFormattingConfig(0, 1, this.getWhiteSpaceCount(indentation), false, this.getWhiteSpaceCount(indentationOfParent), formatConfig.get(FormattingConstants.USE_PARENT_INDENTATION).getAsBoolean())); @@ -3118,20 +3119,10 @@ public void formatRecordLiteralExprNode(JsonObject node) { if (node.has(FormattingConstants.WS) && node.has(FormattingConstants.FORMATTING_CONFIG)) { JsonArray ws = node.getAsJsonArray(FormattingConstants.WS); JsonObject formatConfig = node.getAsJsonObject(FormattingConstants.FORMATTING_CONFIG); - String indentation = this.getIndentation(formatConfig, false); String indentWithParentIndentation = this.getParentIndentation(formatConfig); String parentKind = node.getAsJsonObject(FormattingConstants.PARENT).get(FormattingConstants.KIND) .getAsString(); boolean isTable = parentKind.equals("Table"); - boolean isExpression = parentKind.equals("Endpoint") || parentKind.equals("AnnotationAttachment") - || parentKind.equals("Service") || parentKind.equals("Variable") || parentKind.equals("Invocation") - || parentKind.equals("ErrorConstructor") || parentKind.equals("RecordLiteralExpr") - || parentKind.equals("RecordLiteralKeyValue") || parentKind.equals("Return"); - - if (isExpression) { - node.getAsJsonObject(FormattingConstants.POSITION).addProperty(FormattingConstants.START_COLUMN, - indentation); - } // Preserve line separation that already available. this.preserveHeight(ws, indentWithParentIndentation); @@ -3149,12 +3140,12 @@ public void formatRecordLiteralExprNode(JsonObject node) { // Update whitespace for opening brace. if (text.equals(Tokens.OPENING_BRACE) && this.noHeightAvailable(currentWS.get(FormattingConstants.WS).getAsString())) { - if (isExpression) { - currentWS.addProperty(FormattingConstants.WS, - this.getWhiteSpaces(formatConfig.get(FormattingConstants.SPACE_COUNT).getAsInt())); - } else if (isTable) { + if (isTable) { currentWS.addProperty(FormattingConstants.WS, this.getNewLines(formatConfig .get(FormattingConstants.NEW_LINE_COUNT).getAsInt()) + indentWithParentIndentation); + } else { + currentWS.addProperty(FormattingConstants.WS, + this.getWhiteSpaces(formatConfig.get(FormattingConstants.SPACE_COUNT).getAsInt())); } } @@ -4334,8 +4325,8 @@ public void formatStringTemplateLiteralNode(JsonObject node) { } // Handle expressions format - if (node.has("expressions")) { - JsonArray expressions = node.getAsJsonArray("expressions"); + if (node.has(FormattingConstants.EXPRESSIONS)) { + JsonArray expressions = node.getAsJsonArray(FormattingConstants.EXPRESSIONS); // Every three expression is related to // Start of the template expression // expression of the template expression @@ -5221,8 +5212,51 @@ public void formatTypedescExpressionNode(JsonObject node) { * @param node {JsonObject} node as json object */ public void formatTypeInitExprNode(JsonObject node) { - // TODO: fix formatting for type init expression - this.skipFormatting(node, true); + if (node.has(FormattingConstants.WS) && node.has(FormattingConstants.FORMATTING_CONFIG)) { + JsonObject formatConfig = node.getAsJsonObject(FormattingConstants.FORMATTING_CONFIG); + JsonArray ws = node.getAsJsonArray(FormattingConstants.WS); + + String indentation = this.getIndentation(formatConfig, false); + String indentationOfParent = this.getParentIndentation(formatConfig); + boolean useParentIndentation = formatConfig.get(FormattingConstants.USE_PARENT_INDENTATION).getAsBoolean(); + + // Preserve available new lines in node. + this.preserveHeight(ws, useParentIndentation ? indentationOfParent : indentation); + + // Iterate and update whitespaces for node. + for (JsonElement wsItem : ws) { + JsonObject currentWS = wsItem.getAsJsonObject(); + if (this.noHeightAvailable(currentWS.get(FormattingConstants.WS).getAsString())) { + String text = currentWS.get(FormattingConstants.TEXT).getAsString(); + if (text.equals(Tokens.NEW)) { + currentWS.addProperty(FormattingConstants.WS, + this.getWhiteSpaces(formatConfig.get(FormattingConstants.SPACE_COUNT).getAsInt())); + } else if (text.equals(Tokens.OPENING_PARENTHESES)) { + if (node.has(FormattingConstants.TYPE)) { + currentWS.addProperty(FormattingConstants.WS, FormattingConstants.EMPTY_SPACE); + } else { + currentWS.addProperty(FormattingConstants.WS, FormattingConstants.SINGLE_SPACE); + } + } else if (text.equals(Tokens.COMMA) || text.equals(Tokens.CLOSING_PARENTHESES)) { + currentWS.addProperty(FormattingConstants.WS, FormattingConstants.EMPTY_SPACE); + } + } + } + + // Iterate and format expressions. + if (node.has(FormattingConstants.EXPRESSIONS)) { + JsonArray expressions = node.getAsJsonArray(FormattingConstants.EXPRESSIONS); + iterateAndFormatMembers(indentation.isEmpty() ? indentationOfParent : indentation, + expressions); + } + + // Handle type name formatting. + if (node.has(FormattingConstants.TYPE)) { + node.getAsJsonObject(FormattingConstants.TYPE).add(FormattingConstants.FORMATTING_CONFIG, + this.getFormattingConfig(0, 1, 0, false, + this.getWhiteSpaceCount(indentationOfParent), true)); + } + } } /** @@ -6245,8 +6279,8 @@ private void modifyReturnTypeAnnotations(JsonObject node, String indentation) { } private void modifyExpressions(JsonObject node, String indentWithParentIndentation) { - if (node.has("expressions")) { - JsonArray expressions = node.getAsJsonArray("expressions"); + if (node.has(FormattingConstants.EXPRESSIONS)) { + JsonArray expressions = node.getAsJsonArray(FormattingConstants.EXPRESSIONS); iterateAndFormatMembers(indentWithParentIndentation, expressions); } } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingVisitor.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingVisitor.java similarity index 97% rename from language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingVisitor.java rename to language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingVisitor.java index c3ffa88f40f2..258c31100c59 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingVisitor.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingVisitor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ballerinalang.langserver.formatting; +package org.ballerinalang.langserver.compiler.format; import com.google.gson.JsonObject; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingVisitorEntry.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingVisitorEntry.java similarity index 98% rename from language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingVisitorEntry.java rename to language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingVisitorEntry.java index 8274a16a43fd..f8bac5f83faa 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingVisitorEntry.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/FormattingVisitorEntry.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ballerinalang.langserver.formatting; +package org.ballerinalang.langserver.compiler.format; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/Tokens.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/Tokens.java similarity index 98% rename from language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/Tokens.java rename to language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/Tokens.java index f7eab7dbcb48..ecac9fec9769 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/Tokens.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/format/Tokens.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ballerinalang.langserver.formatting; +package org.ballerinalang.langserver.compiler.format; /** * Constants values to be used when comparing tokens in formatting. @@ -110,5 +110,6 @@ public class Tokens { public static final String TRAP = "trap"; public static final String TYPEOF = "typeof"; public static final String UNTAINT = "untaint"; + public static final String NEW = "new"; public static final String FLUSH = "flush"; } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingSourceGen.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/sourcegen/FormattingSourceGen.java similarity index 99% rename from language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingSourceGen.java rename to language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/sourcegen/FormattingSourceGen.java index 230aa5840b7d..b0d087504210 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/formatting/FormattingSourceGen.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/sourcegen/FormattingSourceGen.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ballerinalang.langserver.formatting; +package org.ballerinalang.langserver.compiler.sourcegen; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import org.ballerinalang.langserver.compiler.format.FormattingConstants; import java.util.ArrayList; import java.util.Comparator; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java index 686480b082ef..c5262810ce07 100755 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java @@ -31,7 +31,9 @@ import org.ballerinalang.langserver.compiler.LSServiceOperationContext; import org.ballerinalang.langserver.compiler.common.LSCustomErrorStrategy; import org.ballerinalang.langserver.compiler.common.LSDocument; +import org.ballerinalang.langserver.compiler.format.FormattingVisitorEntry; import org.ballerinalang.langserver.compiler.format.TextDocumentFormatUtil; +import org.ballerinalang.langserver.compiler.sourcegen.FormattingSourceGen; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentException; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager; import org.ballerinalang.langserver.completions.CompletionKeys; @@ -39,8 +41,6 @@ import org.ballerinalang.langserver.completions.util.CompletionUtil; import org.ballerinalang.langserver.definition.LSReferencesException; import org.ballerinalang.langserver.diagnostic.DiagnosticsHelper; -import org.ballerinalang.langserver.formatting.FormattingSourceGen; -import org.ballerinalang.langserver.formatting.FormattingVisitorEntry; import org.ballerinalang.langserver.implementation.GotoImplementationCustomErrorStrategy; import org.ballerinalang.langserver.implementation.GotoImplementationUtil; import org.ballerinalang.langserver.index.LSIndexImpl; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaDocumentServiceImpl.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaDocumentServiceImpl.java index a99e51c7ffc4..f67d0acc3ba1 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaDocumentServiceImpl.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaDocumentServiceImpl.java @@ -37,10 +37,10 @@ import org.ballerinalang.langserver.compiler.common.modal.SymbolMetaInfo; import org.ballerinalang.langserver.compiler.format.JSONGenerationException; import org.ballerinalang.langserver.compiler.format.TextDocumentFormatUtil; +import org.ballerinalang.langserver.compiler.sourcegen.FormattingSourceGen; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentException; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager; import org.ballerinalang.langserver.extensions.OASGenerationException; -import org.ballerinalang.langserver.formatting.FormattingSourceGen; import org.ballerinalang.model.tree.ServiceNode; import org.ballerinalang.model.tree.TopLevelNode; import org.ballerinalang.openapi.CodeGenerator; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/fragment/BallerinaFragmentServiceImpl.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/fragment/BallerinaFragmentServiceImpl.java index db6f8c29080e..e7719dd5e646 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/fragment/BallerinaFragmentServiceImpl.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/fragment/BallerinaFragmentServiceImpl.java @@ -26,7 +26,7 @@ import org.ballerinalang.langserver.compiler.common.modal.BallerinaFile; import org.ballerinalang.langserver.compiler.format.JSONGenerationException; import org.ballerinalang.langserver.compiler.format.TextDocumentFormatUtil; -import org.ballerinalang.langserver.formatting.FormattingSourceGen; +import org.ballerinalang.langserver.compiler.sourcegen.FormattingSourceGen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/formatting/FormattingTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/formatting/FormattingTest.java index 64cb990f274c..80c0da947aba 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/formatting/FormattingTest.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/formatting/FormattingTest.java @@ -152,6 +152,7 @@ public Object[][] fileProvider() { {"expectedTypeDesc.bal", "typeDesc.bal"}, {"expectedAnonObject.bal", "anonObject.bal"}, {"expectedUnaryExpr.bal", "unaryExpr.bal"}, + {"expectedTypeInitExpr.bal", "typeInitExpr.bal"}, }; } diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/sourcegen/SourceGenTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/sourcegen/SourceGenTest.java index f28f979abf0b..21783b64cff1 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/sourcegen/SourceGenTest.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/sourcegen/SourceGenTest.java @@ -20,9 +20,9 @@ import org.ballerinalang.langserver.compiler.LSCompiler; import org.ballerinalang.langserver.compiler.LSServiceOperationContext; import org.ballerinalang.langserver.compiler.format.TextDocumentFormatUtil; +import org.ballerinalang.langserver.compiler.sourcegen.FormattingSourceGen; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManagerImpl; -import org.ballerinalang.langserver.formatting.FormattingSourceGen; import org.ballerinalang.langserver.util.TestUtil; import org.eclipse.lsp4j.jsonrpc.Endpoint; import org.slf4j.Logger; diff --git a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedEndpoint.bal b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedEndpoint.bal index 83d6ea9a7e04..075c9efed5db 100644 --- a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedEndpoint.bal +++ b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedEndpoint.bal @@ -1,8 +1,8 @@ import ballerina/http; # test listener -listener http:Listener listenerEP = new(9091, config = {host: ""}); +listener http:Listener listenerEP = new (9091, config = {host: ""}); function name() { - http:Listener listenerEP2 = new(9090, config = {host: ""}); + http:Listener listenerEP2 = new (9090, config = {host: ""}); } diff --git a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedInvocation.bal b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedInvocation.bal index 7dabda36affd..0df9b8217b63 100644 --- a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedInvocation.bal +++ b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedInvocation.bal @@ -18,7 +18,7 @@ type Person1 object { }; function invocationObj() returns string { - Person1 p = new(); + Person1 p = new (); string name = p.getName(); return p.getName(); } diff --git a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedLock.bal b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedLock.bal index 3f9070983737..ce3e7ff602bd 100644 --- a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedLock.bal +++ b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedLock.bal @@ -10,7 +10,7 @@ byte[] blobValue = []; boolean boolValue = false; -listener http:MockListener echoEP = new(9090); +listener http:MockListener echoEP = new (9090); int sampleRequestCount = 0; diff --git a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedService.bal b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedService.bal index 578e64112f40..1500b3e47b15 100644 --- a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedService.bal +++ b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedService.bal @@ -12,7 +12,7 @@ service serviceName1 on new http:Listener(9090) { } } -listener http:Listener listenerEP = new(8080); +listener http:Listener listenerEP = new (8080); service serviceName2 on listenerEP { @@ -30,7 +30,7 @@ service serviceName4 on new http:Listener(9090) { } private resource function newResource2(http:Caller caller, http:Request request) { - http:Listener listener1 = new(8080); + http:Listener listener1 = new (8080); http:Response res = new; res.setPayload("sd"); @@ -47,7 +47,7 @@ service serviceName4 on new http:Listener(9090) { resource function send2(http:Caller caller, http:Request request) { - http:Client locationEP = new("http://www.mocky.io"); + http:Client locationEP = new ("http://www.mocky.io"); var jsonMsg = req.getJsonPayload(); if (jsonMsg is json) { diff --git a/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedTypeInitExpr.bal b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedTypeInitExpr.bal new file mode 100644 index 000000000000..1f137b8823c3 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/formatting/expected/expectedTypeInitExpr.bal @@ -0,0 +1,56 @@ +import ballerina/mysql; + +type Person object { + string name = ""; + + function __init(string name = "", record {int id = 0; int address = "";} details = {}) { + self.name = name; + var detailRec = details; + } +} + +function name() { + mysql:Client studentDb1 = new ({ + host: "localhost", + port: 5690, + name: "testdb", + username: "root", + password: "", + dbOptions: {useSSL: false} + }); + + mysql:Client studentDb2 = new ({host: "localhost", port: 5690, name: "testdb", username: "root", password: "", dbOptions: {useSSL: false}}); + + mysql:Client studentDb3 = new ({ + host: "localhost", + port: 5690, + name: "testdb", + username: "root", + password: "", + dbOptions: {useSSL: false} + }); + + Person p1 = new Person("", {id: 0, address: ""}); + Person p2 = new; + Person p3 = new ("", {id: 0, address: ""}); + Person p4 = new Person("", { + id: 0, + address: "" + }); + Person p5 = new ("", { + id: 0, + address: "" + }); + + mysql:Client studentDb4 = + new + ({ + host: "localhost", + port: 5690, + name: "testdb", + username: "root", + password: "", + dbOptions: {useSSL: false} + }) + ; +} diff --git a/language-server/modules/langserver-core/src/test/resources/formatting/typeInitExpr.bal b/language-server/modules/langserver-core/src/test/resources/formatting/typeInitExpr.bal new file mode 100644 index 000000000000..0c9ef60afc35 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/formatting/typeInitExpr.bal @@ -0,0 +1,46 @@ +import ballerina/mysql; + +type Person object { + string name = ""; + + function __init(string name="" ,record {int id = 0;int address="";} details={}) { + self.name = name; + var detailRec = details; + } +} + +function name() { + mysql:Client studentDb1 = new ( { + host:"localhost", + port: 5690 , +name : "testdb" , + username : "root" , + password:"", + dbOptions:{useSSL: false} + }); + + mysql:Client studentDb2 =new( { host : "localhost" ,port: 5690,name: "testdb",username:"root",password:"" ,dbOptions:{ useSSL:false }}); + + mysql:Client studentDb3 = new ({host: "localhost", port: 5690, name: "testdb", username: "root", password: "", +dbOptions: {useSSL: false}}); + + Person p1= new Person ( "" ,{id: 0, address: ""} ) ; + Person p2 =new ; +Person p3 = new("",{ id:0, address : ""}); +Person p4 = new Person("", { +id: 0,address: ""}); +Person p5 = new ("" , {id: 0, + address: ""}); + + mysql:Client studentDb4 = + new + ({ + host: "localhost", + port: 5690, + name: "testdb", + username: "root", + password: "", + dbOptions: {useSSL: false} + }) + ; +} \ No newline at end of file diff --git a/misc/ballerina-formatter/LICENSE.txt b/misc/ballerina-formatter/LICENSE.txt new file mode 100644 index 000000000000..8dada3edaf50 --- /dev/null +++ b/misc/ballerina-formatter/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/misc/ballerina-formatter/README.md b/misc/ballerina-formatter/README.md new file mode 100644 index 000000000000..e4a7e0524673 --- /dev/null +++ b/misc/ballerina-formatter/README.md @@ -0,0 +1,54 @@ +# Ballerina Format CLI Tool + +The Ballerina format CLI tool can be used to format Ballerina sources +according to the default [Ballerina style guide](https://github.com/ballerina-platform/ballerina-lang/tree/master/docs/style_guide). + +## Using the Ballerina Format CLI Tool + +The Ballerina format command can be used to format Ballerina source files. + +```sh +$ ballerina format --help +Prints the help guide for the Ballerina format tool. + +Usage: +ballerina format [ | ] [-d | --dry-run] + ballerinaFile: + Path of a single Ballerina source file, which needs to be formatted. + moduleName: + The name of the Ballerina module, which has Ballerina source files that need to + be formatted. The format command should be executed from the root of the Ballerina project. +Flags: + -d + --dry-run + By providing this option, you can dry run the formatter and see which files will + be formatted after the execution. +``` + +### Usage Examples + +**Example 1:** Formats all the Ballerina source files in a Ballerina project. +```sh +$ ballerina format +``` + +This command should be executed from the root of the Ballerina project. + +**Example 2:** Formats all the Ballerina source files in a Ballerina module. +```sh +$ ballerina format module1 +``` +This command should be executed from the root of the Ballerina project. + +**Example 3:** Formats a single Ballerina source file. +```sh +$ ballerina format hello.bal +``` + +**Example 4:** Performs a dry run of the formatter to see which files will be formatted +if executed. +```sh +$ ballerina format -d +$ ballerina format module1 -d +$ ballerina format hello.bal -d +``` diff --git a/misc/ballerina-formatter/build.gradle b/misc/ballerina-formatter/build.gradle new file mode 100644 index 000000000000..2810e9b71e26 --- /dev/null +++ b/misc/ballerina-formatter/build.gradle @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +apply from: "$rootDir/gradle/javaProjectWithExtBalo.gradle" +apply plugin: 'com.github.johnrengelman.shadow' + +dependencies { + implementation 'commons-io:commons-io' + implementation 'info.picocli:picocli' + implementation 'com.google.code.gson:gson' + implementation project(':ballerina-lang') + implementation project(':ballerina-launcher') + implementation project(':language-server:language-server-compiler') + + testCompile 'org.testng:testng' +} + +jar { + enabled = false + dependsOn(shadowJar { classifier = null }) +} + +shadowJar { + configurations = [project.configurations.runtimeClasspath] + dependencies { + include(dependency(':ballerina-lang')) + include(dependency('commons-io:commons-io')) + include(dependency('org.ballerinalang:language-server-compiler')) + include(dependency('com.google.code.gson:gson')) + exclude('META-INF/*.SF') + exclude('META-INF/*.DSA') + exclude('META-INF/*.RSA') + } +} + +test { + useTestNG() { + suites 'src/test/resources/testng.xml' + } +} + +description = 'Ballerina - Formatting CLI tool' diff --git a/misc/ballerina-formatter/pom.xml b/misc/ballerina-formatter/pom.xml new file mode 100644 index 000000000000..84a1d04ade9e --- /dev/null +++ b/misc/ballerina-formatter/pom.xml @@ -0,0 +1,147 @@ + + + + + + org.ballerinalang + ballerina-parent + 0.992.0-m2-SNAPSHOT + ../../pom.xml + + 4.0.0 + ballerina-formatter + jar + Ballerina - formatting tool + + + + info.picocli + picocli + + + org.ballerinalang + ballerina-lang + + + org.ballerinalang + ballerina-launcher + + + org.slf4j + slf4j-log4j12 + + + + + org.ballerinalang + language-server-compiler + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + package + + shade + + + true + + ${java.io.tmpdir}/dependency-reduced-pom.xml + + + + commons-io:commons-io + com.google.code.gson:gson + org.ballerinalang:language-server-compiler + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + false + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Dfile.encoding=UTF-8 ${jacoco.agent.argLine} + + ${project.build.directory} + + + src/test/resources/testng.xml + + + + + org.jacoco + jacoco-maven-plugin + + + prepare-it-test-agent + + prepare-agent + + + true + true + jacoco.agent.argLine + ${project.build.directory}/coverage-reports/jacoco.exec + + + + it-report + verify + + report-aggregate + + + + **/coverage-reports/jacoco.exec + + ${project.build.directory}/coverage-reports/site + + + + + + + + + spotbugs-exclude.xml + + diff --git a/misc/ballerina-formatter/spotbugs-exclude.xml b/misc/ballerina-formatter/spotbugs-exclude.xml new file mode 100644 index 000000000000..1c56b1fc808a --- /dev/null +++ b/misc/ballerina-formatter/spotbugs-exclude.xml @@ -0,0 +1,19 @@ + + + diff --git a/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/FormatCmd.java b/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/FormatCmd.java new file mode 100644 index 000000000000..3df1838c99a3 --- /dev/null +++ b/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/FormatCmd.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.format; + +import org.ballerinalang.launcher.BLauncherCmd; +import picocli.CommandLine; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +/** + * Class to implement "format" command for ballerina. + * Ex: ballerina format [ballerinaFile | ModuleName] [-d | --dry-run] + */ +@CommandLine.Command(name = "format", description = "format given Ballerina source file") +public class FormatCmd implements BLauncherCmd { + private static final String USER_DIR = "user.dir"; + + @CommandLine.Parameters + private List argList; + + @CommandLine.Option(names = {"-h", "--help"}, hidden = true) + private boolean helpFlag; + + @CommandLine.Option(names = {"-d", "--dry-run"}, hidden = true) + private boolean dryRun; + + @Override + public void execute() { + // Get source root path. + Path sourceRootPath = Paths.get(System.getProperty(USER_DIR)); + FormatUtil.execute(argList, helpFlag, dryRun, sourceRootPath); + } + + @Override + public String getName() { + return FormatUtil.CMD_NAME; + } + + @Override + public void printLongDesc(StringBuilder out) { + + } + + @Override + public void printUsage(StringBuilder out) { + + } + + @Override + public void setParentCmdParser(CommandLine parentCmdParser) { + + } +} diff --git a/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/FormatUtil.java b/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/FormatUtil.java new file mode 100644 index 000000000000..6cd7022e6c7a --- /dev/null +++ b/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/FormatUtil.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.format; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.ballerinalang.compiler.CompilerPhase; +import org.ballerinalang.langserver.compiler.LSCompilerUtil; +import org.ballerinalang.langserver.compiler.format.FormattingVisitorEntry; +import org.ballerinalang.langserver.compiler.format.JSONGenerationException; +import org.ballerinalang.langserver.compiler.sourcegen.FormattingSourceGen; +import org.ballerinalang.launcher.BLauncherCmd; +import org.ballerinalang.launcher.LauncherUtils; +import org.wso2.ballerinalang.compiler.Compiler; +import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; +import org.wso2.ballerinalang.compiler.tree.BLangPackage; +import org.wso2.ballerinalang.compiler.tree.BLangTestablePackage; +import org.wso2.ballerinalang.compiler.util.CompilerContext; +import org.wso2.ballerinalang.compiler.util.CompilerOptions; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.ballerinalang.compiler.CompilerOptionName.COMPILER_PHASE; +import static org.ballerinalang.compiler.CompilerOptionName.EXPERIMENTAL_FEATURES_ENABLED; +import static org.ballerinalang.compiler.CompilerOptionName.LOCK_ENABLED; +import static org.ballerinalang.compiler.CompilerOptionName.OFFLINE; +import static org.ballerinalang.compiler.CompilerOptionName.PRESERVE_WHITESPACE; +import static org.ballerinalang.compiler.CompilerOptionName.PROJECT_DIR; +import static org.ballerinalang.compiler.CompilerOptionName.SIDDHI_RUNTIME_ENABLED; +import static org.ballerinalang.compiler.CompilerOptionName.SKIP_TESTS; +import static org.ballerinalang.compiler.CompilerOptionName.TEST_ENABLED; +import static org.ballerinalang.langserver.compiler.format.TextDocumentFormatUtil.generateJSON; + +/** + * Util class for compilation and format execution for formatting CLI tool. + */ +public class FormatUtil { + static final String CMD_NAME = "format"; + private static final PrintStream outStream = System.err; + + /** + * Execute formatter. + * + * @param argList argument list from the console + * @param helpFlag flag to get the help page + * @param dryRun run the whole formatting + * @param sourceRootPath execution path + */ + static void execute(List argList, boolean helpFlag, boolean dryRun, Path sourceRootPath) { + if (helpFlag) { + String commandUsageInfo = BLauncherCmd.getCommandUsageInfo(CMD_NAME); + outStream.println(commandUsageInfo); + return; + } + + if (argList != null && argList.size() > 1) { + throw LauncherUtils.createLauncherException(Messages.getArgumentError()); + } + + String moduleName; + String ballerinaFilePath; + + try { + // If parameters are available user has given either the module name or the ballerina file path. + // Else user is in a ballerina project and expecting to format the whole ballerina project + if (argList != null && argList.size() > 0) { + // If the given param is a ballerina file path, this will format the file. + // Else the given param is a module name, this will format the module. + if (FormatUtil.isBalFile(argList.get(0))) { + ballerinaFilePath = argList.get(0); + Path filePath = Paths.get(ballerinaFilePath); + // If the file doesn't exist or is a directory. + if (!Files.exists(filePath) || Files.isDirectory(filePath)) { + throw LauncherUtils.createLauncherException(Messages.getNoBallerinaFile(ballerinaFilePath)); + } + + String sourceRoot = LSCompilerUtil.getSourceRoot(filePath.toAbsolutePath()); + String packageName = LSCompilerUtil.getPackageNameForGivenFile(sourceRoot, + filePath.toAbsolutePath().toString()); + if ("".equals(packageName)) { + Path path = filePath.getFileName(); + if (path != null) { + packageName = path.toString(); + } + } + + // Compile the given ballerina file. + BLangPackage bLangPackage = compileFile(Paths.get(sourceRoot), packageName); + + // If there are compilation errors do not continue the process. + if (bLangPackage.diagCollector.hasErrors()) { + return; + } + + BLangCompilationUnit compilationUnit = bLangPackage.getCompilationUnits().get(0); + + // Format and get the generated formatted source code content. + String formattedSourceCode = format(compilationUnit); + if (doChangesAvailable(compilationUnit, formattedSourceCode)) { + if (!dryRun) { + // Write the formatted content back to the file. + FormatUtil.writeFile(filePath.toAbsolutePath().toString(), formattedSourceCode); + outStream.println(Messages.getModifiedFiles() + System.lineSeparator() + ballerinaFilePath); + outStream.println(System.lineSeparator() + Messages.getSuccessMessage()); + } else { + outStream.println(Messages.getFilesToModify() + System.lineSeparator() + ballerinaFilePath); + } + } else { + outStream.println(Messages.getNoChanges()); + } + } else if (Files.isRegularFile(Paths.get(argList.get(0)))) { + // If file is a regular file but not a ballerina source file + // throw the following exception. + throw LauncherUtils.createLauncherException(Messages.getNotABallerinaFile()); + } else { + moduleName = argList.get(0); + + // Check whether the module dir exists. + if (!FormatUtil.isModuleExist(moduleName, sourceRootPath)) { + // If module directory doesn't exist and contains a "." + // throw a exception to say file or module doesn't exist. + // Else throw a exception to say module doesn't exist. + if (moduleName.contains(".")) { + throw LauncherUtils.createLauncherException(Messages + .getNoBallerinaModuleOrFile(moduleName)); + } else { + throw LauncherUtils.createLauncherException(Messages.getNoModuleFound(moduleName)); + } + } + + // Check whether the given directory is not in a ballerina project. + if (FormatUtil.notABallerinaProject(sourceRootPath)) { + throw LauncherUtils.createLauncherException(Messages.getNotBallerinaProject()); + } + BLangPackage bLangPackage = FormatUtil + .compileModule(sourceRootPath, moduleName); + + // If there are no compilation errors do not continue the process. + if (bLangPackage.diagCollector.hasErrors()) { + return; + } + + // Iterate and format the ballerina package. + List formattedFiles = iterateAndFormat(bLangPackage, sourceRootPath, dryRun); + generateChangeReport(formattedFiles, dryRun); + } + } else { + // Check whether the given source root is not + if (FormatUtil.notABallerinaProject(sourceRootPath)) { + throw LauncherUtils.createLauncherException(Messages.getNotBallerinaProject()); + } + + List packages = FormatUtil.compileProject(sourceRootPath); + boolean hasCompilationErrors = false; + for (BLangPackage bLangPackage : packages) { + if (bLangPackage.diagCollector.hasErrors()) { + hasCompilationErrors = true; + break; + } + } + if (hasCompilationErrors) { + return; + } + + List formattedFiles = new ArrayList<>(); + // Iterate and format all the ballerina packages. + for (BLangPackage bLangPackage : packages) { + formattedFiles.addAll(iterateAndFormat(bLangPackage, sourceRootPath, dryRun)); + } + + generateChangeReport(formattedFiles, dryRun); + } + } catch (IOException | JSONGenerationException | NullPointerException e) { + throw LauncherUtils.createLauncherException(Messages.getException()); + } + } + + /** + * Check whether the given path isn't a source root of a ballerina project. + * + * @param path - path where the command is executed from + * @return {@link boolean} true or false + */ + private static boolean notABallerinaProject(Path path) { + Path cachePath = path.resolve(".ballerina"); + return !Files.exists(cachePath); + } + + /** + * Write content to a file. + * + * @param filePath - path of the file to add the content + * @param content - content to be added to the file + * @throws IOException - throws and IO exception + */ + private static void writeFile(String filePath, String content) throws IOException { + OutputStreamWriter fileWriter = null; + try { + File newFile = new File(filePath); + FileOutputStream fileStream = new FileOutputStream(newFile); + fileWriter = new OutputStreamWriter(fileStream, StandardCharsets.UTF_8); + fileWriter.write(content); + } finally { + if (fileWriter != null) { + fileWriter.close(); + } + } + } + + /** + * Check whether the given file is a ballerina file. + * + * @param fileName file name to be check whether a ballerina file + * @return {@link Boolean} true or false + */ + private static boolean isBalFile(String fileName) { + return fileName.endsWith(".bal"); + } + + /** + * Check whether the given module name exists. + * + * @param module module name + * @param projectRoot path of the ballerina project root + * @return {@link Boolean} true or false + */ + private static boolean isModuleExist(String module, Path projectRoot) { + Path modulePath = projectRoot.resolve(module); + return Files.isDirectory(modulePath); + } + + /** + * Compile whole ballerina project. + * + * @param sourceRoot source root + * @return {@link List} list of BLangPackages + */ + private static List compileProject(Path sourceRoot) { + CompilerContext context = getCompilerContext(sourceRoot); + Compiler compiler = Compiler.getInstance(context); + return compiler.compilePackages(false); + } + + /** + * Compile only a ballerina module. + * + * @param sourceRoot source root + * @param moduleName name of the module to be compiled + * @return {@link BLangPackage} ballerina package + */ + private static BLangPackage compileModule(Path sourceRoot, String moduleName) { + CompilerContext context = getCompilerContext(sourceRoot); + Compiler compiler = Compiler.getInstance(context); + return compiler.compile(moduleName); + } + + /** + * Compile a ballerina file. + * + * @param sourceRoot source root of the file + * @param packageName package name of the file + * @return {@link BLangPackage} ballerina package + */ + private static BLangPackage compileFile(Path sourceRoot, String packageName) { + CompilerContext context = getCompilerContext(sourceRoot); + Compiler compiler = Compiler.getInstance(context); + return compiler.compile(packageName); + } + + /** + * Get prepared compiler context. + * + * @param sourceRootPath ballerina compilable source root path + * @return {@link CompilerContext} compiler context + */ + private static CompilerContext getCompilerContext(Path sourceRootPath) { + CompilerPhase compilerPhase = CompilerPhase.DEFINE; + CompilerContext context = new CompilerContext(); + CompilerOptions options = CompilerOptions.getInstance(context); + options.put(PROJECT_DIR, sourceRootPath.toString()); + options.put(OFFLINE, Boolean.toString(false)); + options.put(COMPILER_PHASE, compilerPhase.toString()); + options.put(SKIP_TESTS, Boolean.toString(false)); + options.put(TEST_ENABLED, "true"); + options.put(LOCK_ENABLED, Boolean.toString(false)); + options.put(EXPERIMENTAL_FEATURES_ENABLED, Boolean.toString(true)); + options.put(SIDDHI_RUNTIME_ENABLED, Boolean.toString(true)); + options.put(PRESERVE_WHITESPACE, Boolean.toString(true)); + + return context; + } + + private static String format(BLangCompilationUnit compilationUnit) throws JSONGenerationException { + JsonElement modelElement = generateJSON(compilationUnit, new HashMap<>(), new HashMap<>()); + JsonObject model = modelElement.getAsJsonObject(); + FormattingSourceGen.build(model, "CompilationUnit"); + + // Format the given AST. + FormattingVisitorEntry formattingUtil = new FormattingVisitorEntry(); + formattingUtil.accept(model); + + return FormattingSourceGen.getSourceOf(model); + } + + private static boolean doChangesAvailable(BLangCompilationUnit compilationUnit, String formattedSource) + throws JSONGenerationException { + JsonElement modelElement = generateJSON(compilationUnit, new HashMap<>(), new HashMap<>()); + JsonObject model = modelElement.getAsJsonObject(); + FormattingSourceGen.build(model, "CompilationUnit"); + String originalSource = FormattingSourceGen.getSourceOf(model); + return !originalSource.equals(formattedSource); + } + + private static List iterateAndFormat(BLangPackage bLangPackage, Path sourceRootPath, boolean dryRun) + throws IOException, JSONGenerationException { + List formattedFiles = new ArrayList<>(); + + // Iterate compilation units and format. + for (BLangCompilationUnit compilationUnit : bLangPackage.getCompilationUnits()) { + formatAndWrite(compilationUnit, sourceRootPath, formattedFiles, dryRun); + } + + // Iterate testable packages and format. + for (BLangTestablePackage testablePackage : bLangPackage.getTestablePkgs()) { + for (BLangCompilationUnit compilationUnit : testablePackage.getCompilationUnits()) { + formatAndWrite(compilationUnit, sourceRootPath, formattedFiles, dryRun); + } + } + + return formattedFiles; + } + + private static void formatAndWrite(BLangCompilationUnit compilationUnit, Path sourceRootPath, + List formattedFiles, boolean dryRun) + throws JSONGenerationException, IOException { + String fileName = sourceRootPath.toString() + File.separator + + compilationUnit.getPosition().getSource().getPackageName() + + File.separator + + compilationUnit.getPosition().getSource().getCompilationUnitName(); + + // Format and get the formatted source. + String formattedSource = format(compilationUnit); + + if (doChangesAvailable(compilationUnit, formattedSource)) { + if (!dryRun) { + // Write formatted content to the file. + FormatUtil.writeFile(fileName, formattedSource); + } + formattedFiles.add(fileName); + } + } + + private static void generateChangeReport(List formattedFiles, boolean dryRun) { + if (formattedFiles.size() > 0) { + StringBuilder fileList = new StringBuilder(); + if (dryRun) { + fileList.append(Messages.getFilesToModify()).append(System.lineSeparator()); + } else { + fileList.append(Messages.getModifiedFiles()).append(System.lineSeparator()); + } + for (String file : formattedFiles) { + fileList.append(file).append(System.lineSeparator()); + } + outStream.println(fileList.toString()); + if (!dryRun) { + outStream.println(Messages.getSuccessMessage()); + } + } else { + outStream.println(Messages.getNoChanges()); + } + } +} diff --git a/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/Messages.java b/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/Messages.java new file mode 100644 index 000000000000..fe73474628bb --- /dev/null +++ b/misc/ballerina-formatter/src/main/java/org/ballerinalang/format/Messages.java @@ -0,0 +1,81 @@ +package org.ballerinalang.format; + +/** + * Class to hold the messages generated by the Formatting CLI tool. + */ +public class Messages { + private static final String ARGUMENT_ERROR = "too many arguments." + System.lineSeparator() + + "usage: only one argument, either a ballerina file name or a module " + + "name, can be applied at a time with or without the option." + System.lineSeparator() + + "i.e: ballerina format [ballerinaFile | ModuleName] [-d | --dry-run]" + System.lineSeparator() + + System.lineSeparator() + "run `ballerina format -h` for more details."; + + private static final String SUCCESS_MESSAGE = "format successful."; + + private static final String NOT_BALLERINA_PROJECT = "not a valid Ballerina project." + System.lineSeparator() + + "usage: ballerina format should be run inside a ballerina project or pass in a ballerina file." + + System.lineSeparator() + "i.e. `ballerina format `" + + System.lineSeparator() + System.lineSeparator() + "run `ballerina format -h` for more details"; + + private static final String NO_MODULE_FOUND = "couldn't find an existing module by the name: "; + + private static final String EXCEPTION = "something went wrong when formatting." + System.lineSeparator(); + + private static final String NO_BALLERINA_FILE = "couldn't find an existing ballerina file by the name: "; + + private static final String NOT_BALLERINA_FILE = "not a valid ballerina source file." + System.lineSeparator() + + "usage: ballerina source files should have the file extension as `.bal`." + System.lineSeparator() + + "i.e. `ballerina format hello.bal`"; + + private static final String NO_BALLERINA_FILE_OR_MODULE = "couldn't find an existing ballerina file or " + + "module by the name: "; + private static final String NO_CHANGES = "no changes."; + + private static final String FILES_TO_MODIFY = "files to be modified:"; + + private static final String MODIFIED_FILES = "modified files:"; + + static String getArgumentError() { + return ARGUMENT_ERROR; + } + + static String getSuccessMessage() { + return SUCCESS_MESSAGE; + } + + static String getNotBallerinaProject() { + return NOT_BALLERINA_PROJECT; + } + + static String getNoModuleFound(String moduleName) { + return NO_MODULE_FOUND + moduleName; + } + + static String getException() { + return EXCEPTION; + } + + static String getNoBallerinaFile(String fileName) { + return NO_BALLERINA_FILE + fileName; + } + + static String getNoBallerinaModuleOrFile(String fileName) { + return NO_BALLERINA_FILE_OR_MODULE + fileName; + } + + static String getNotABallerinaFile() { + return NOT_BALLERINA_FILE; + } + + static String getNoChanges() { + return NO_CHANGES; + } + + static String getFilesToModify() { + return FILES_TO_MODIFY; + } + + static String getModifiedFiles() { + return MODIFIED_FILES; + } +} diff --git a/misc/ballerina-formatter/src/main/resources/META-INF/services/org.ballerinalang.launcher.BLauncherCmd b/misc/ballerina-formatter/src/main/resources/META-INF/services/org.ballerinalang.launcher.BLauncherCmd new file mode 100644 index 000000000000..df8fe8276e0b --- /dev/null +++ b/misc/ballerina-formatter/src/main/resources/META-INF/services/org.ballerinalang.launcher.BLauncherCmd @@ -0,0 +1 @@ +org.ballerinalang.format.FormatCmd \ No newline at end of file diff --git a/misc/ballerina-formatter/src/test/java/org/ballerinalang/format/FormatCmdTest.java b/misc/ballerina-formatter/src/test/java/org/ballerinalang/format/FormatCmdTest.java new file mode 100644 index 000000000000..4c254933c360 --- /dev/null +++ b/misc/ballerina-formatter/src/test/java/org/ballerinalang/format/FormatCmdTest.java @@ -0,0 +1,166 @@ +package org.ballerinalang.format; + +import org.ballerinalang.launcher.BLauncherException; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Format CLI tool test suit for testing tool's exceptions. + */ +public class FormatCmdTest { + private static final Path RES_DIR = Paths.get("src/test/resources/").toAbsolutePath(); + + @Test(description = "Test to check the exception for too many argument provided.") + public void formatCLITooManyArgumentsTest() { + Path sourceRoot = RES_DIR.resolve("notAProject"); + List argList = new ArrayList<>(); + argList.add("pkg2"); + argList.add("asd"); + try { + FormatUtil.execute(argList, false, false, sourceRoot); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + Messages.getArgumentError(), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } + + @Test(description = "Test to check the exception for not a ballerina project.") + public void formatCLINotAProjectTest() { + Path sourceRoot = RES_DIR.resolve("notAProject"); + try { + FormatUtil.execute(null, false, false, sourceRoot); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + Messages.getNotBallerinaProject(), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } + + @Test(description = "Test to check the exception for not a ballerina project when given a module name.") + public void formatCLINotAProjectInModuleTest() { + Path sourceRoot = RES_DIR.resolve("notAProject"); + List argList = new ArrayList<>(); + argList.add("pkg1"); + try { + FormatUtil.execute(argList, false, false, sourceRoot); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + Messages.getNotBallerinaProject(), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } + + @Test(description = "Test to check the exception for no ballerina module found for a given module name.") + public void formatCLINotAModuleTest() { + Path sourceRoot = RES_DIR.resolve("project"); + List argList = new ArrayList<>(); + argList.add("pkg2"); + try { + FormatUtil.execute(argList, false, false, sourceRoot); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + Messages.getNoModuleFound("pkg2"), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } + + @Test(description = "Test to check the exception for file that is not a ballerina file.") + public void formatCLINotABalFileTest() { + List argList = new ArrayList<>(); + argList.add(RES_DIR.resolve("invalidFile.txt").toString()); + try { + FormatUtil.execute(argList, false, false, RES_DIR); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + + Messages.getNotABallerinaFile(), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } + + @Test(description = "Test to check the exception for no existing ballerina file.") + public void formatCLINoBallerinaFileTest() { + List argList = new ArrayList<>(); + argList.add(RES_DIR.resolve("invalidFile.bal").toString()); + try { + FormatUtil.execute(argList, false, false, RES_DIR); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + + Messages.getNoBallerinaFile(RES_DIR.resolve("invalidFile.bal").toString()), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } + + @Test(description = "Test to check the exception for no existing ballerina file or module.") + public void formatCLINotABallerinaFileOrModuleTest() { + List argList = new ArrayList<>(); + argList.add("invalid.pkg2"); + try { + FormatUtil.execute(argList, false, false, RES_DIR); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + + Messages.getNoBallerinaModuleOrFile("invalid.pkg2"), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } + + @Test(description = "Test to check the exception for general error in File IO or a argument.") + public void formatCLIGeneralExceptionTest() { + try { + FormatUtil.execute(null, false, false, null); + } catch (BLauncherException e) { + List exception = e.getMessages(); + if (exception.size() == 1) { + Assert.assertEquals(exception.get(0), "error: " + + Messages.getException(), + "actual exception didn't match the expected."); + } else { + Assert.fail("failed the test with " + exception.size() + + " exceptions where there needs to be 1 exception"); + } + } + } +} diff --git a/misc/ballerina-formatter/src/test/resources/invalidFile.txt b/misc/ballerina-formatter/src/test/resources/invalidFile.txt new file mode 100644 index 000000000000..dd7382457c06 --- /dev/null +++ b/misc/ballerina-formatter/src/test/resources/invalidFile.txt @@ -0,0 +1 @@ +function name() {int a = 0;} \ No newline at end of file diff --git a/misc/ballerina-formatter/src/test/resources/notAProject/main.bal b/misc/ballerina-formatter/src/test/resources/notAProject/main.bal new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/misc/ballerina-formatter/src/test/resources/notAProject/pkg1/testd.bal b/misc/ballerina-formatter/src/test/resources/notAProject/pkg1/testd.bal new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/misc/ballerina-formatter/src/test/resources/project/main.bal b/misc/ballerina-formatter/src/test/resources/project/main.bal new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/misc/ballerina-formatter/src/test/resources/project/pkg1/dates.bal b/misc/ballerina-formatter/src/test/resources/project/pkg1/dates.bal new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/misc/ballerina-formatter/src/test/resources/testng.xml b/misc/ballerina-formatter/src/test/resources/testng.xml new file mode 100644 index 000000000000..67e681fd8f97 --- /dev/null +++ b/misc/ballerina-formatter/src/test/resources/testng.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index e78cc9479004..2b5323406a2d 100644 --- a/pom.xml +++ b/pom.xml @@ -1149,6 +1149,11 @@ zip ballerina-binary-repo + + org.ballerinalang + ballerina-formatter + ${ballerina.version} + org.ballerinalang openapi-to-ballerina-generator @@ -1939,6 +1944,7 @@ misc/metrics-extensions misc/tracing-extensions misc/strip-bouncycastle + misc/ballerina-formatter composer distribution/zip/ballerina @@ -2039,6 +2045,7 @@ misc/metrics-extensions misc/tracing-extensions misc/strip-bouncycastle + misc/ballerina-formatter distribution/zip/ballerina @@ -2136,6 +2143,7 @@ misc/metrics-extensions misc/tracing-extensions misc/strip-bouncycastle + misc/ballerina-formatter composer diff --git a/settings.gradle b/settings.gradle index 299d46d971a9..a17fedb34d70 100644 --- a/settings.gradle +++ b/settings.gradle @@ -71,6 +71,7 @@ include(':openapi-ballerina:openapi-to-ballerina-generator') include(':openapi-ballerina:ballerina-to-openapi-generator') include(':openapi-ballerina:ballerina-client-generator') include(':openapi-ballerina') +include(':ballerina-formatter') include(':testerina:testerina-core') include(':testerina') include(':ballerina-spec-conformance-tests') @@ -171,6 +172,7 @@ project(':language-server:language-server-core').projectDir = file('language-ser project(':language-server:language-server-stdio-launcher').projectDir = file('language-server/modules/launchers/stdio-launcher') project(':language-server:language-server-launchers').projectDir = file('language-server/modules/launchers') project(':language-server:language-server-test-coverage').projectDir = file('language-server/modules/test-coverage') +project(':ballerina-formatter').projectDir = file('misc/ballerina-formatter') project(':docerina-gradle-plugin').projectDir = file('misc/ballerina-gradle-plugins/docerina-gradle-plugin') project(':docerina').projectDir = file('misc/docerina') project(':openapi-ballerina:openapi-to-ballerina-generator').projectDir = file('misc/openapi-ballerina/modules/openapi-to-ballerina-generator')