Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Feature]: Formatting a Ballerina module through project-level configurations #41280

Closed
poorna2152 opened this issue Aug 23, 2023 · 13 comments · Fixed by #42129
Closed

[New Feature]: Formatting a Ballerina module through project-level configurations #41280

poorna2152 opened this issue Aug 23, 2023 · 13 comments · Fixed by #42129

Comments

@poorna2152
Copy link
Contributor

Description

Add a section to the Ballerina.toml file which contain project-level formatting configurations. As a starting point provide capability to set maximum character limit on a code line using the Ballerina.toml file. Further modify this to include more configuration parameters.

Describe your problem(s)

No response

Describe your solution(s)

No response

Related area

-> Compilation

Related issue(s) (optional)

No response

Suggested label(s) (optional)

No response

Suggested assignee(s) (optional)

No response

@ballerina-bot ballerina-bot added needTriage The issue has to be inspected and labeled manually userCategory/Compilation labels Aug 23, 2023
@poorna2152 poorna2152 changed the title [New Feature]: Formatting Ballerina module through a project level configurations [New Feature]: Formatting Ballerina module through project level configurations Aug 23, 2023
@nipunayf nipunayf removed the needTriage The issue has to be inspected and labeled manually label Aug 24, 2023
@praneesha praneesha changed the title [New Feature]: Formatting Ballerina module through project level configurations [New Feature]: Formatting a Ballerina module through project-level configurations Aug 24, 2023
@poorna2152
Copy link
Contributor Author

poorna2152 commented Aug 30, 2023

Configuration Parameters

The configuration parameters that can be set using the Ballerina.toml file,

General

columnLimit

Maximum number of characters per line

tabWidth

Size of the tab (eg: 2 spaces, 4 spaces)

alignConsecutiveDefinitions:

Example

const int XML_PRIMITIVE_NEVER      = 1;
const int XML_PRIMITIVE_TEXT       = 1 << 1;
const int XML_PRIMITIVE_ELEMENT_RO = 1 << 2;

Import

sortImports

Sort imports by the alphabetical order

groupImports

Group imports such that module imports in a single block, Ballerina Std library imports, and Extended library imports in another single block, and all the other 3rd-party library imports in another block all separated by a newline in the above order.

Block

allowShortBlocksOnASingleLine:

Enables short blocks (e.g., if blocks, match cases, and loop bodies) to appear on a single line when set to true. To specify that only specific block types (e.g., if bodies, match cases, or loop bodies) should be on a single line, use the respective parameters (allowShortIfStatementsOnASingleLine, allowShortMatchLabelsOnASingleLine, allowShortLoopsOnASingleLine).

allowShortMatchLabelsOnASingleLine: This lets short Match cases to be in a single line.

 match td.builtinTypeName {
    "any" => { return t:ANY; }
    "anydata" => { return t:createAnydata(mod.tc); }
    "boolean" => { return t:BOOLEAN; }
}

allowShortIfStatementsOnASingleLine: This lets short If statements to be in a single line.

if x == 1 { return true; }

allowShortLoopsOnASingleLine: This lets short while loops to be in a single line.

while i < 5 { i += 1; }

elseBlocksOnANewLine

Can be set to either true or false. The default is the false.

true

if 0 < score && score < 55 {
    return "F";
}
else if 55 <= score && score < 65 {
    return "C";
}
else {
    return "invalid";
}

false

if 0 < score && score < 55 {
    return "F";
} else if 55 <= score && score < 65 {
    return "C";
} else {
    return "invalid";
}

Function Definition

  • oneArgPerLine: Sets one argument/parameter per line
  • paranAlign: Aligns function/call argument/parameters to the opening braces

eg:

  1. oneArgPerLine: true & paranAlign: true
function foo(int veryDescriptiveArgumentNumberOne,
             int veryDescriptiveArgumentTwo,
             int veryDescriptiveArgumentThree,
             int veryDescriptiveArgumentFour) {
    // ...
}
  1. oneArgPerLine: true & paranAlign: false
function foo(
    int veryDescriptiveArgumentNumberOne,
    int veryDescriptiveArgumentTwo,
    int veryDescriptiveArgumentThree,
    int veryDescriptiveArgumentFour) {
  // ...
};
  1. oneArgPerLine: false & paranAlign: true
function foo(int veryDescriptiveArgumentNumberOne, int veryDescriptiveArgumentTwo,
             int veryDescriptiveArgumentThree, int veryDescriptiveArgumentFour) {
  // ...
}
  1. oneArgPerLine: false & paranAlign: false & columnLimit: 120. Default way
function foo(int veryDescriptiveArgumentNumberOne, int veryDescriptiveArgumentTwo,
    int veryDescriptiveArgumentThree, int veryDescriptiveArgumentFour) {
  // ...
};

Records

recordBraceSpacing

If set to true this adds a space between the record opening brace and the first attribute and closing brace and the last attribute. When true,
{ offset, header, content }
default or false,
{offset, header, content}

Example Configuration

Ballerina.toml

[format]
columnLimit = 80
tabWidth = 2
alignConsecutiveDefinitions=true
recordBraceSpacing = true

[format.function]
oneArgPerLine = false
paranAlign = true

[format.block]
allowShortIfStatementsOnASingleLine = true
elseBlocksOnANewLine = true

[format.import]
groupImports = true
sortImports = true

@sameerajayasoma
Copy link
Contributor

+1 for the idea of having formatter-specific configurations in Ballerina.toml file. But the most common requirement is to use the same set of configurations across multiple packages and these packages can be in multiple repositories.

We need a way to provide the formatted configuration via an external file. Here is an example

Ballerina.toml

format.configPath = "https://foo.abc.com/bal-format.toml"

@poorna2152
Copy link
Contributor Author

We had an internal meeting and decided that we should as per the previous comment have a separate toml file containing formatting configurations rather than having them on the Ballerina.toml file. User has to explicitly provide a path to the toml file containing formatting configurations. This file can be located either locally or can be a remote file. In the remote file case if possible we decided to cache the file in the target directory so that the user don't have to pull the file again and again when reformatting.

@poorna2152
Copy link
Contributor Author

poorna2152 commented Sep 22, 2023

Updated formatting configurations following intellij and checkstyle naming configurations

Formatting Configurations

Tab and Indent

  • indentSize: Specifies the number of spaces for each level of indentation.

    • Value: Integer (e.g., 2, 4)

    • Default: 4

      function main() {
          if (condition) {
              io:println("Indented code");
          }
      }
  • continuationIndentSize: Determines the indentation size for continuation lines.

    • Value: Integer (e.g., 4, 8)

    • Default: 8

      function longFunctionName(int param1, int param2, int param3,
              int param4, int param5) {
      }

LineWrap

  • maxLineLength: Maximum line length before code is wrapped.

    • Value: Integer (e.g., 80, 120)
    • Default: No Line Wrapping if maxLineLength not specified.
  • simpleBlocksInOneLine: Preserve single-line blocks in user code.

    • Value: Boolean

    • Default: false

      if x == 1 { return true; }
      while i < 5 { i += 1; }
  • simpleMethodsInOneLine: Preserve single-line methods on a single line.

    • Value: Boolean

    • Default: false

      function getName() returns string { return self.name; }

Brace Placement

  • classBraceStyle: Values: NextLine, EndOfLine

    • Default: EndOfLine

      • NextLine:

        class MyClass 
        {
            // class members
        }
      • EndOfLine:

        class MyClass {
            // class members
        }
  • methodBraceStyle: Values: NextLine, EndOfLine

    • Default: EndOfLine

      • NextLine:

        function foo()
        {
            // class members
        }
      • EndOfLine:

        function foo(){
            // class members
        }

Method Declaration Parameters

  • methodParametersWrap: Values: Wrap, DoNotWrap, ChopDown

    • Default: Wrap if maxLineLength specified, else DoNotWrap

      • Wrap:

        function foo(int argumentNumberOne, int argumentTwo, 
            int argumentThree, int argumentFour) {
        }
      • DoNotWrap:

        function foo(int argumentOne, int argumentTwo, int argumentThree) {
        }
      • ChopDown:

        function foo(int argumentNumberOne, 
            int argumentTwo, 
            int argumentThree,
            int argumentFour) {
        }
  • alignMultilineParameters: Align multiline parameters with the open brace.

    • Values: Boolean

    • Default: false

      function foo(int argumentNumberOne, int argumentTwo,
                   int argumentThree, int argumentFour) {
      }
  • newLineAfterLeftParen: Newline after the left parenthesis of the method declaration.

    • Values: Boolean

    • Default: false

      function foo(
                  int argumentNumberOne, int argumentTwo, int argumentThree, 
                  int argumentFour) {
      }
  • rightParenOnNewLine: Right parenthesis on a newline.

    • Values: Boolean

    • Default: false

      function foo(int argumentNumberOne, int argumentTwo, 
              int argumentThree, int argumentFour
              ) {
      }

Method Call Parameters

  • parametersWrap: Values: Wrap, DoNotWrap, ChopDown

    • Default: Wrap if maxLineLength specified, else DoNotWrap

      • Wrap:

        myMethod(param1, param2, param3, param4,
                param5, param6);
      • DoNotWrap:

        myMethod(param1, param2, param3, param4, param5, param6);
      • ChopDown:

        myMethod(param1, 
               param2,
               param3,
               param4,
               param5,
               param6);
  • alignMultilineParameters: Values: Boolean

    • Default: false

      myMethod(param1, param2, param3, param4
               param5, param6);
  • newLineAfterLeftParen: Values: Boolean

    • Default: false

      myMethod(
              param1, param2, param3, param4, param5, param6);
  • rightParenOnNewLine: Values: Boolean

    • Default: false

      myMethod(param1, param2, param3, 
              param4, param5, param6
      );

If Statement

  • elseOnNewLine: else and else if keyword on the newline which follows after the } of the previous if statement.
    • Values: Boolean

    • Default: false

      if (condition) {
          // if block
      } 
      else {
          // else block
      }

Spacing

  • afterTypeCast: Space after type casting.

    • Values: Boolean

    • Default: true

      int x = <int> someValue;
  • aroundRecordBraces: Space after the opening brace and closing brace of a record type.

    • Values: Boolean

    • Default: true

      Student x = { name:Alex”, age: 15 };

Example Configuration

[indent]
indentSize = 4
continuationIndentSize = 8

[wrapping]
maxLineLength = 120
simpleBlocksInOneLine = true
simpleMethodsInOneLine = true

[braces]
classBraceStyle = "NewLine"
methodBraceStyle = "NewLine"

[methodDeclaration]
parametersWrap = "ChopDown"
alignMultilineParameters = true
newLineAfterLeftParen = false
rightParenOnNewLine = false

[methodCall]
parametersWrap = "Wrap"
alignMultilineParameters = false
newLineAfterLeftParen = false
rightParenOnNewLine = false

[ifStatement]
elseOnNewLine = false

[spacing]
afterTypeCast = true
aroundRecordBraces = true

[import]
groupImports=true
sortImports=true

[query]
alignMultiLineQueries=false


@gimantha
Copy link
Contributor

Example Configuration

[format.indent]
indentSize = 4
continuationIndentSize = 8

[format.wrapping]
maxLineLength = 120
multipleExpressionsInOneLine = true
keepLineBreaks = true
simpleBlocksInOneLine = true
simpleMethodsInOneLine = true

[format.braces]
classBraceStyle = "NewLine"
methodBraceStyle = "NewLine"

[format.methodDeclaration]
parametersWrap = "ChopDown"
alignMultilineParameters = true
newLineAfterLeftParen = false
rightParenOnNewLine = false

[format.methodCall]
parametersWrap = "Wrap"
alignMultilineParameters = false
newLineAfterLeftParen = false
rightParenOnNewLine = false

[format.ifStatement]
elseOnNewLine = false

[format.spacing]
afterTypeCast = true
aroundRecordBraces = true

Are we going to use same config names for this formatter? Shouldnt we reword them with our own wording? @sameerajayasoma

@sameerajayasoma
Copy link
Contributor

I prefer to use familiar terms when possible to maintain familiarity. WDYT?

@sameerajayasoma
Copy link
Contributor

We had an internal meeting and decided that we should as per the previous comment have a separate toml file containing formatting configurations rather than having them on the Ballerina.toml file. User has to explicitly provide a path to the toml file containing formatting configurations. This file can be located either locally or can be a remote file. In the remote file case if possible we decided to cache the file in the target directory so that the user don't have to pull the file again and again when reformatting.

How can I specify the path to this file? Do we have an opinionated filename for this file?

@poorna2152
Copy link
Contributor Author

How can I specify the path to this file? Do we have an opinionated filename for this file?

The toml file containing formatting configuration can be given an arbitrary name by the user. User have to provide the file path including the toml file name.

@sameerajayasoma
Copy link
Contributor

How can I specify this file path? I believe having an opinionated filename would be better.

@poorna2152
Copy link
Contributor Author

poorna2152 commented Oct 13, 2023

How can I specify this file path? I believe having an opinionated filename would be better.

In the existing implementation you can specify the path to the toml configuration file within the Ballerina.toml file as the following two cases.

  • Local file
[format]
configPath = "src/test/resources/formatting/project/Format.toml"
  • Remote file
[format]
configPath = "https://gist.githubusercontent.com/ballerina-bot/ae54cc7303e9d474d730d732c1594c61/raw/db0909d0b66f97cd0035f19bcb7cde9a239f5d54/format.toml"

Example ballerina project with a Format.toml file.

We can give an opinionated file name like Format.toml and ask the user to specify the path to the file containing the file excluding the filename. If the path is not specified we can search in the project source directory to check if a Format.toml file exists. But when providing a url I think we should ask the user to specify the whole url including the filename. WDYT?

@sameerajayasoma
Copy link
Contributor

+1 for an opinionated file like Format.toml

Here are the semantics derived from what you've already specified in the previous comment:

  • If the format file path is specified in Ballerina.toml, then format package sources based on the config in that file.
    • Path has to be either of:
      • Absolute file path,
      • Relative file path from the package root,
      • HTTP or HTTPS URL
  • If the file path is not in Ballerina.toml, look for a Format.bal in the current package root directory.
  • If no Format.bal exists in the package root, then use the default format configs.

If the format config path is an HTTP URL, then we should keep a cache inside the target directory.

@MaryamZi
Copy link
Member

We seem to be using method when it comes to both function and method formatting - e.g., methodBraceStyle, methodDeclaration, methodCall. This is probably somewhere we should deviate and use function instead (to represent both functions and methods)?

@MaryamZi
Copy link
Member

Also, under methodCall (or functionCall depending on what we decide to call it), we have parametersWrap and alignMultilineParameters. I think we should use argument instead of parameter?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment