The design section covers rules that require inter-file or semantic analysis, or that cannot be resolved automatically with a tool.
The C# coding guidelines in Pine are stricter than Microsoft's general recommendations to achieve greater transparency and readability.
Usages of the operators == and != with primitive types (e.g., int, string) on one side and a literal or null on the other side must be replaced with pattern-matching syntax using is and is not.
Background:
- C# allows overriding these operators, so that surprising behavior could be hidden in there.
The formatting section covers rules that do not require analysis beyond a single file and can be applied automatically.
The basis for formatting is dotnet format, which in turn applies rules we have specified in the .editorconfig files in the repository.
Compatibility with dotnet format: Our formatting must always be stable with respect to dotnet format. That means running dotnet format on already-formatted code must not change anything. We never want to format in a way that would be undone or altered by a subsequent dotnet format run. Some formatting concerns may be deferred entirely to dotnet format or its internal implementation (Formatter.Format).
The only reason we have additional rules beyond dotnet format is that we have not yet found a way to make dotnet format apply these changes. In particular, dotnet format does not normalize indentation in most cases (due to Roslyn's SuppressFormattingRule, which intentionally preserves the developer's existing layout choices). Our extra rules address the gaps where dotnet format is too lenient.
Items in lists such as argument lists, parameter lists, collection expressions, tuples, and array/object/collection initializers must be distributed in one of two ways: Either all items on the same line or each item starting a new line. If the last item is not on the same line as the first, the items must be placed so that each starts on a new line.
For multiline collection expressions, the closing bracket must be placed on a new line after the last item. For argument lists and parameter lists, the closing parentheses must be placed on the same line as the last item.
For argument lists in the multiline form, the first argument must be separated from the opening parens with a line break.
For example, the following code:
int[] alfa =
[1, 2,
3];
var beta =
func(1, 2, 3);
var gamma =
func(1, 2,
3);
decl =
alfa(beta(
gamma(delta(a, b))));Must be formatted to:
int[] alfa =
[
1,
2,
3
];
var beta =
func(1, 2, 3);
var gamma =
func(
1,
2,
3);
decl =
alfa(
beta(
gamma(delta(a, b))));In declaration statements, the initializer may be placed on the same line. The initializer must never start on the same line as the equals sign if the initializer expression itself spans multiple lines.
For example, the following code:
var firstItemRange = getItemRange(separatedList.First);
var commentsAfterOpen = commentQueries.GetOnRowBetweenColumns(
containerRange.Start.Row,
containerRange.Start.Column,
firstItemRange.Start.Column);Must be formatted to:
var firstItemRange = getItemRange(separatedList.First);
var commentsAfterOpen =
commentQueries.GetOnRowBetweenColumns(
containerRange.Start.Row,
containerRange.Start.Column,
firstItemRange.Start.Column);When a line exceeds a length of 120 columns, line breaks must be inserted at the following locations:
- Before a declaration initializer.
- Before an expression body.
- Before the dot in a member access expression.
- Before the expression part of a switch expression arm.
- In a conditional expression (splitting it to multiple lines as described in the conditional expression rule below).
Also, list forms such as argument lists, parameter lists, or object initializer lists must be switched to a multiline layout if the containing line exceeds the length threshold.
In automated formatting, these line breaks must be inserted starting from the outermost syntax node, only as far as necessary to meet the line length limit, so that the layout of inner expressions is preserved to the degree possible.
When a chain of binary operators (such as || or &&) exceeds the line length limit, the chain must be wrapped so that continuation lines are at the same indent level as the first operand. Since binary operators are left-associative, chains form nested left-recursive trees; when breaking, all continuation operands must remain at a consistent indent rather than cascading deeper with each break.
For example, the following code:
var putOnNewLine =
node.Expression is ThrowExpressionSyntax || SpansMultipleLines(node) || node.Pattern is DiscardPatternSyntax || node.Pattern is DeclarationPatternSyntax;Must be formatted to:
var putOnNewLine =
node.Expression is ThrowExpressionSyntax || SpansMultipleLines(node) || node.Pattern is DiscardPatternSyntax ||
node.Pattern is DeclarationPatternSyntax;If a statement spans multiple lines, it must be separated from previous and following statements by at least one empty line. If a statement spans multiple lines, it must be separated from following comments by at least one empty line.
Type declarations, method declarations and member declarations must be separated by at least one empty line.
If a conditional expression spans multiple lines, each subexpression and token must be placed on their own line.
For example, the following code:
count < 13
? 17
: 21Must be formatted to:
count < 13
?
17
:
21- If a switch expression arm spans multiple lines, it must be separated from other arms by at least one empty line.
- If a switch expression arm spans multiple lines, the expression must start on a new line after the pattern and arrow.
- If the pattern syntax of a switch expression arm is
DiscardPattern, it must always be separated from other arms by at least one empty line. - If a switch expression arm contains a
throwexpression, the expression must be placed on a new line after the pattern and arrow.
Every section of a switch statement must be separated from other sections by at least one empty line.
If a return statement spans multiple lines, the expression must start on a new line after the return keyword;
For any statement in an if or else body that spans multiple lines, the statement must be wrapped in a block (braces { }).
Single-line statements may remain without braces.
For example, the following code:
if (condition)
CallMethod(
arg1,
arg2);
else
OtherMethod(
arg1,
arg2);Must be formatted to:
if (condition)
{
CallMethod(
arg1,
arg2);
}
else
{
OtherMethod(
arg1,
arg2);
}If a comment appears before other content on the same line, the comment must be moved to a separate line above that content.
For example, the following code:
/* comment */ int x = 1;
// comment
string y = "hello";Must be formatted to:
/* comment */
int x = 1;
// comment
string y = "hello";Trailing // comments on a code line are preserved with exactly one space separating the code from the //. No blank line is added or removed between the trailing comment and the next line (e.g. a closing brace).
For example, the following code must remain unchanged:
if (x < 0)
{
x = -x; // negate
}Indentation must only depend on the content. That means, existing indent before formatting must never influence the resulting indentation after formatting.
A file must end with one empty line.