prettycsharp 1.1.8__tar.gz → 1.1.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/CHANGELOG.md +22 -0
  2. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/DESIGN.md +19 -0
  3. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/PKG-INFO +1 -1
  4. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/__init__.py +1 -1
  5. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/printer/expressions.py +86 -6
  6. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/printer/statements.py +7 -2
  7. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_layout.py +162 -5
  8. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.gitattributes +0 -0
  9. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/codeql/codeql-config.yml +0 -0
  10. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/dependabot.yml +0 -0
  11. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/build.in +0 -0
  12. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/build.txt +0 -0
  13. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/fuzz.in +0 -0
  14. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/fuzz.txt +0 -0
  15. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/pip-audit.in +0 -0
  16. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/pip-audit.txt +0 -0
  17. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/runtime.in +0 -0
  18. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/runtime.txt +0 -0
  19. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/test.in +0 -0
  20. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/test.txt +0 -0
  21. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/zizmor.in +0 -0
  22. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/requirements/zizmor.txt +0 -0
  23. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/security/accepted.toml +0 -0
  24. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/security/clamav/Dockerfile +0 -0
  25. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/security/malware-accepted.toml +0 -0
  26. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/security/semgrep/Dockerfile +0 -0
  27. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/security/yara.json +0 -0
  28. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/security/yara_update.py +0 -0
  29. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/auto-merge.yml +0 -0
  30. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/ci.yml +0 -0
  31. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/fuzz.yml +0 -0
  32. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/malware-scan.yml +0 -0
  33. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/publish.yml +0 -0
  34. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/scorecard.yml +0 -0
  35. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/security.yml +0 -0
  36. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/workflows/update-yara-rules.yml +0 -0
  37. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.github/zizmor.yml +0 -0
  38. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/.gitignore +0 -0
  39. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/AGENTS.md +0 -0
  40. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/CLAUDE.md +0 -0
  41. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/LICENSE +0 -0
  42. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/README.md +0 -0
  43. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/SECURITY.md +0 -0
  44. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/fuzz/fuzz_formatter.py +0 -0
  45. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/pyproject.toml +0 -0
  46. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/scripts/security/malware_report.py +0 -0
  47. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/scripts/security/security_report.py +0 -0
  48. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/__main__.py +0 -0
  49. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/cli.py +0 -0
  50. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/conditional.py +0 -0
  51. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/config.py +0 -0
  52. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/doc.py +0 -0
  53. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/formatter.py +0 -0
  54. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/ignore.py +0 -0
  55. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/isolate.py +0 -0
  56. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/parsing.py +0 -0
  57. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/printer/__init__.py +0 -0
  58. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/printer/core.py +0 -0
  59. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/printer/declarations.py +0 -0
  60. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/project.py +0 -0
  61. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/py.typed +0 -0
  62. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/rewrites.py +0 -0
  63. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/settings.py +0 -0
  64. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/syntax.py +0 -0
  65. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/src/prettycsharp/verify.py +0 -0
  66. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/comment_only_accessor_list.cs +0 -0
  67. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/conditional_assignments.cs +0 -0
  68. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/declarations.cs +0 -0
  69. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/directives.cs +0 -0
  70. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/embedded_statements.cs +0 -0
  71. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/expressions.cs +0 -0
  72. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/members.cs +0 -0
  73. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/order.cs +0 -0
  74. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/queries_and_patterns.cs +0 -0
  75. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/split_by_conditionals.cs +0 -0
  76. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/statements.cs +0 -0
  77. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/fuzz_corpus/csharp/strings.cs +0 -0
  78. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/support.py +0 -0
  79. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_cli.py +0 -0
  80. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_conditional.py +0 -0
  81. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_config.py +0 -0
  82. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_doc.py +0 -0
  83. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_docs.py +0 -0
  84. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_format_around.py +0 -0
  85. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_fuzz_corpus.py +0 -0
  86. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_grammar_compatibility.py +0 -0
  87. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_ignore.py +0 -0
  88. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_parsing.py +0 -0
  89. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_project.py +0 -0
  90. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_rewrites.py +0 -0
  91. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_settings.py +0 -0
  92. {prettycsharp-1.1.8 → prettycsharp-1.1.10}/tests/test_verify.py +0 -0
@@ -2,6 +2,28 @@
2
2
 
3
3
  Each release's section becomes the body of its GitHub release.
4
4
 
5
+ ## [1.1.10] - 2026-10-18
6
+
7
+ - A tuple argument ending in a multiline block lambda closes the tuple and
8
+ call parentheses on their own line at the call's indentation, matching
9
+ Visual Studio's formatting. Ordinary lambda arguments keep their layout.
10
+ - Empty argument parentheses without interior comments stay together as
11
+ `()`. Long empty-constructor assignments break after `=` instead of
12
+ splitting the parentheses. Interior comments are preserved.
13
+
14
+ ## [1.1.9] - 2026-10-18
15
+
16
+ - With the default `binary_wrap`, multiline conditions in catch filters,
17
+ `if`, and loops start below `(` and put `)` on its own line. This includes
18
+ pattern chains, mixed Boolean conditions, and predicate calls.
19
+ - Inline collection expressions keep tight brackets around complex elements
20
+ and spreads, such as `[new HpackField(wire, "")]` and `[.. source.Select(...)]`,
21
+ avoiding Visual Studio's IDE0055 formatting diagnostic.
22
+ - A multiline collection argument no longer adds a blank line after the
23
+ call's opening parenthesis.
24
+ - Chains of negated patterns, including `is not A and not B` followed by
25
+ Boolean operators, wrap at one consistent continuation indentation level.
26
+
5
27
  ## [1.1.8] - 2026-10-18
6
28
 
7
29
  - Long `or` and `and` patterns wrap at the configured line width using
@@ -177,7 +177,21 @@ parenthesis unless all three clauses are empty: `for (; ; )`.
177
177
  | `ternary_wrap` | `before`: break before `?` and `:` when too long | `after`, `always` |
178
178
  | `lambda_body` | `hug`: the last argument hangs off the call line when it is a lambda with a block body or a broken member chain, or a creation with a broken initializer | `argument` |
179
179
 
180
+ With `lambda_body = "hug"` and `argument_wrap = "one_per_line"`, a single
181
+ tuple argument ending in a multiline block lambda closes both enclosing
182
+ parentheses on their own line at the call's indentation.
183
+
184
+ Empty argument parentheses without interior comments stay together as `()`.
185
+ An overlong assignment to an empty constructor breaks after `=` instead of
186
+ splitting the empty parentheses.
187
+
180
188
  `binary_wrap` also controls line breaks in `or` and `and` patterns.
189
+ With `before_operator_paren_own_line`, broken conditions in catch
190
+ filters, `if`, `while`, and `do` loops put the expression below `(` and `)`
191
+ on its own line, including predicate calls and mixed Boolean/pattern
192
+ conditions. Pattern operators take one further continuation indent.
193
+ Negated pattern chains and trailing Boolean operators share one continuation
194
+ indent, including `value is not A and not B && condition`.
181
195
 
182
196
  ```csharp
183
197
  var invoice = invoiceService.CreateInvoice(
@@ -225,6 +239,11 @@ orders.ForEach(order =>
225
239
  | `simple_lists_inline` | `true`: lists of literals, constants, and plain names stay on one line when they fit | `false` |
226
240
  | `trailing_commas` | `remove` | `add`, `preserve` |
227
241
 
242
+ Inline collection expressions keep their brackets tight, including complex
243
+ elements: `[new Item()]`. Broken collections put the elements on new lines.
244
+ When a collection starts on an argument's own line, its opening bracket
245
+ does not add another line break.
246
+
228
247
  ```csharp
229
248
  var empty = new Order { Id = 42 };
230
249
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: prettycsharp
3
- Version: 1.1.8
3
+ Version: 1.1.10
4
4
  Summary: An opinionated formatter for C#, in the spirit of Prettier
5
5
  Project-URL: Homepage, https://github.com/WilliamSmithEdward/prettyCSharp
6
6
  Project-URL: Repository, https://github.com/WilliamSmithEdward/prettyCSharp
@@ -4,7 +4,7 @@ from prettycsharp.formatter import FormatResult, FormattingError, format_details
4
4
  from prettycsharp.parsing import CSharpSyntaxError
5
5
  from prettycsharp.settings import Settings, SettingsError
6
6
 
7
- __version__ = "1.1.8"
7
+ __version__ = "1.1.10"
8
8
 
9
9
  __all__ = [
10
10
  "CSharpSyntaxError",
@@ -7,6 +7,7 @@ from collections.abc import Sequence
7
7
  from dataclasses import dataclass, field
8
8
 
9
9
  from prettycsharp.doc import (
10
+ ENSURE_LINE,
10
11
  HARDLINE,
11
12
  LINE,
12
13
  SOFTLINE,
@@ -227,6 +228,14 @@ class Expressions(Core):
227
228
  if value.verbatim:
228
229
  return False
229
230
  kind = value.type
231
+ if kind in ("object_creation_expression", "implicit_object_creation_expression") and not self.block_like(value):
232
+ arguments = value.first_of("argument_list")
233
+ if arguments is not None and len(arguments.children) == 2:
234
+ opening, closing = arguments.children
235
+ if not opening.trailing and not closing.leading:
236
+ # An empty constructor cannot break internally; move the
237
+ # whole value after the assignment operator when needed.
238
+ return False
230
239
  if kind in _SELF_BREAKING:
231
240
  return True
232
241
  if kind == "await_expression":
@@ -460,7 +469,31 @@ class Expressions(Core):
460
469
  close_tok = node.children[-1]
461
470
  items, commas = self.items_and_commas(node)
462
471
  if not items:
472
+ if not open_tok.trailing and not close_tok.leading:
473
+ return [self.tok(open_tok), self.tok(close_tok)]
463
474
  return group([self.tok(open_tok), self.closing(close_tok, SOFTLINE)])
475
+ if (
476
+ node.type == "argument_list" and self.settings.lambda_body == "hug"
477
+ and self.settings.argument_wrap == "one_per_line" and len(items) == 1 and len(items[0]) == 1
478
+ ):
479
+ argument = items[0][0]
480
+ target = argument.children[-1] if argument.type == "argument" and len(argument.children) == 1 else argument
481
+ if target.type == "tuple_expression" and not target.verbatim:
482
+ tuple_items, tuple_commas = self.items_and_commas(target)
483
+ last = tuple_items[-1][0] if tuple_items and len(tuple_items[-1]) == 1 else None
484
+ value = last.children[-1] if last is not None and last.type == "argument" and last.children else last
485
+ body = value.child("body") if value is not None and value.type == "lambda_expression" else None
486
+ if (
487
+ body is not None and body.type == "block" and self._can_hug(tuple_items)
488
+ and (len(body.children) > 2 or any(t.leading or t.trailing for t in body.tokens()))
489
+ ):
490
+ # Keep the tuple's lambda with the call header, but close
491
+ # both parentheses at the call's indentation after its body.
492
+ tuple_docs = self._comma_docs(tuple_items, tuple_commas)
493
+ return [
494
+ self.tok(open_tok), indent([self.tok(target.children[0]), join(" ", tuple_docs)]),
495
+ self.closing(target.children[-1], HARDLINE), self.closing(close_tok, ""),
496
+ ]
464
497
  docs = [self.print_item(item) for item in items]
465
498
  for i, comma in enumerate(commas):
466
499
  if i < len(docs):
@@ -678,7 +711,10 @@ class Expressions(Core):
678
711
  return group([self.tok(open_tok), indent([SOFTLINE, body]), self.closing(close_tok, SOFTLINE)])
679
712
  force = self._forced_initializer([[_element_value(i[0])] for i in items if len(i) == 1])
680
713
  return group(
681
- [SOFTLINE, self.tok(open_tok), indent([LINE, join(LINE, docs)]), self.closing(close_tok, LINE)],
714
+ [
715
+ IfBreak(ENSURE_LINE, ""), self.tok(open_tok),
716
+ indent([SOFTLINE, join(LINE, docs)]), self.closing(close_tok, SOFTLINE),
717
+ ],
682
718
  should_break=force,
683
719
  )
684
720
 
@@ -870,8 +906,16 @@ class Expressions(Core):
870
906
  def p_is_expression(self, node: Node) -> Doc:
871
907
  return self.spaced(node.children)
872
908
 
873
- def p_is_pattern_expression(self, node: Node) -> Doc:
874
- return self.spaced(node.children)
909
+ def p_is_pattern_expression(self, node: Node, *, in_condition: bool = False) -> Doc:
910
+ pattern = node.child("pattern")
911
+ if not in_condition or pattern is None or pattern.verbatim or pattern.type not in (
912
+ "and_pattern", "or_pattern", "negated_pattern"
913
+ ):
914
+ return self.spaced(node.children)
915
+ return join(" ", [
916
+ self.logical_pattern(child, group_result=False) if child is pattern else self.print(child)
917
+ for child in node.children
918
+ ])
875
919
 
876
920
  def p_range_expression(self, node: Node) -> Doc:
877
921
  return self.join_tokens([(c, self.print(c)) for c in node.children])
@@ -941,13 +985,49 @@ class Expressions(Core):
941
985
  return self.spaced(node.children)
942
986
 
943
987
  def p_and_pattern(self, node: Node) -> Doc:
944
- return self.binary(node, in_condition=False)
988
+ return self.logical_pattern(node)
945
989
 
946
990
  def p_or_pattern(self, node: Node) -> Doc:
947
- return self.binary(node, in_condition=False)
991
+ return self.logical_pattern(node)
948
992
 
949
993
  def p_negated_pattern(self, node: Node) -> Doc:
950
- return self.spaced(node.children)
994
+ return self.logical_pattern(node)
995
+
996
+ def logical_pattern(self, node: Node, *, group_result: bool = True) -> Doc:
997
+ # The grammar nests `not A and not B` under successive negations,
998
+ # sometimes including trailing &&/|| expressions in constant patterns.
999
+ # Share a continuation level without moving any tokens or parentheses.
1000
+ segments: list[list[Node]] = [[]]
1001
+ operators: list[Node] = []
1002
+ stack = [node]
1003
+ while stack:
1004
+ current = stack.pop()
1005
+ if current.type == "constant_pattern" and len(current.children) == 1 and not current.verbatim:
1006
+ value = current.children[0]
1007
+ value_op = value.child("operator")
1008
+ if value.type == "binary_expression" and value_op is not None and value_op.text in ("&&", "||"):
1009
+ stack.append(value)
1010
+ continue
1011
+ op = current.child("operator")
1012
+ logical = current.type in ("and_pattern", "or_pattern", "negated_pattern") or (
1013
+ current.type == "binary_expression" and op is not None and op.text in ("&&", "||")
1014
+ )
1015
+ if logical and not current.verbatim:
1016
+ stack.extend(reversed(current.children))
1017
+ elif not current.named and current.text in ("and", "or", "&&", "||"):
1018
+ operators.append(current)
1019
+ segments.append([])
1020
+ else:
1021
+ segments[-1].append(current)
1022
+ first = self.spaced(segments[0])
1023
+ rest: list[Doc] = []
1024
+ for operator, segment in zip(operators, segments[1:], strict=True):
1025
+ if self.settings.binary_wrap == "after_operator":
1026
+ rest.append([" ", self.tok(operator), LINE, self.spaced(segment)])
1027
+ else:
1028
+ rest.append([LINE, self.tok(operator), " ", self.spaced(segment)])
1029
+ contents: Doc = [first, indent(rest)]
1030
+ return group(contents) if group_result else contents
951
1031
 
952
1032
  def p_declaration_pattern(self, node: Node) -> Doc:
953
1033
  return self.spaced(node.children)
@@ -144,13 +144,18 @@ class Statements(Expressions):
144
144
  def condition(self, open_tok: Node, condition: Node, close_tok: Node) -> Doc:
145
145
  if (
146
146
  self.settings.binary_wrap == "before_operator_paren_own_line"
147
- and condition.type == "binary_expression"
148
147
  and not condition.verbatim
149
148
  ):
149
+ if condition.type == "binary_expression":
150
+ contents = self.binary(condition, in_condition=True)
151
+ elif condition.type == "is_pattern_expression":
152
+ contents = self.p_is_pattern_expression(condition, in_condition=True)
153
+ else:
154
+ contents = self.print(condition)
150
155
  return group(
151
156
  [
152
157
  self.tok(open_tok),
153
- indent([SOFTLINE, self.binary(condition, in_condition=True)]),
158
+ indent([SOFTLINE, contents]),
154
159
  SOFTLINE,
155
160
  self.tok(close_tok),
156
161
  ]
@@ -113,8 +113,67 @@ def test_catch_filter_wraps_logical_pattern_before_expanding_property_pattern()
113
113
  'try { Run(); } catch (Exception error) when (error is ObjectDisposedException or SocketException '
114
114
  'or ArgumentException { ParamName: "socketAddress" }) { Stop(); }',
115
115
  'try\n{\n Run();\n}\n\n'
116
- 'catch (Exception error) when (error is ObjectDisposedException\n'
117
- ' or SocketException\n or ArgumentException { ParamName: "socketAddress" })\n{\n Stop();\n}\n',
116
+ 'catch (Exception error) when (\n error is ObjectDisposedException\n'
117
+ ' or SocketException\n or ArgumentException { ParamName: "socketAddress" }\n)\n{\n Stop();\n}\n',
118
+ )
119
+
120
+
121
+ def test_broken_pattern_catch_filter_puts_parentheses_on_own_lines() -> None:
122
+ check(
123
+ 'try { Run(); } catch (Exception error) when '
124
+ '(error is SocketException or ObjectDisposedException or OperationCanceledException) { }',
125
+ 'try\n{\n Run();\n}\n\ncatch (Exception error) when (\n'
126
+ ' error is SocketException\n or ObjectDisposedException\n'
127
+ ' or OperationCanceledException\n)\n{ }\n',
128
+ max_line_length=90,
129
+ )
130
+
131
+
132
+ @pytest.mark.parametrize("keyword", ["if", "while"])
133
+ def test_broken_pattern_conditions_share_catch_filter_layout(keyword: str) -> None:
134
+ check(
135
+ f'{keyword} (error is SocketException or ObjectDisposedException or OperationCanceledException) {{ Stop(); }}',
136
+ f'{keyword} (\n error is SocketException\n or ObjectDisposedException\n'
137
+ ' or OperationCanceledException\n)\n{\n Stop();\n}\n',
138
+ max_line_length=65,
139
+ )
140
+
141
+
142
+ def test_broken_pattern_do_while_condition_has_own_parenthesis_lines() -> None:
143
+ check(
144
+ 'do { Run(); } while (error is SocketException or ObjectDisposedException or OperationCanceledException);',
145
+ 'do\n{\n Run();\n}\n\nwhile (\n error is SocketException\n'
146
+ ' or ObjectDisposedException\n or OperationCanceledException\n);\n',
147
+ max_line_length=65,
148
+ )
149
+
150
+
151
+ @pytest.mark.parametrize("keyword", ["if", "while"])
152
+ def test_short_pattern_condition_stays_inline(keyword: str) -> None:
153
+ check(f'{keyword} (x is A or B) {{ Run(); }}', f'{keyword} (x is A or B)\n{{\n Run();\n}}\n')
154
+
155
+
156
+ @pytest.mark.parametrize("keyword", ["if", "while"])
157
+ def test_multiline_predicate_condition_uses_own_parenthesis_lines(keyword: str) -> None:
158
+ check(
159
+ f'{keyword} (Predicate(veryLongArgumentName, anotherLongArgumentName)) {{ Run(); }}',
160
+ f'{keyword} (\n Predicate(\n veryLongArgumentName,\n'
161
+ ' anotherLongArgumentName\n )\n)\n{\n Run();\n}\n',
162
+ max_line_length=50,
163
+ )
164
+
165
+
166
+ @pytest.mark.parametrize("wrap", ["before_operator", "after_operator"])
167
+ def test_pattern_conditions_respect_alternative_parenthesis_layout(wrap: str) -> None:
168
+ if wrap == "after_operator":
169
+ pattern = 'error is SocketException or\n ObjectDisposedException or\n OperationCanceledException'
170
+ else:
171
+ pattern = 'error is SocketException\n or ObjectDisposedException\n or OperationCanceledException'
172
+ check(
173
+ 'if (error is SocketException or ObjectDisposedException or OperationCanceledException) { Run(); }',
174
+ f'if ({pattern})\n{{\n Run();\n}}\n',
175
+ max_line_length=65,
176
+ binary_wrap=wrap,
118
177
  )
119
178
 
120
179
 
@@ -154,6 +213,16 @@ def test_short_logical_patterns_keep_grouping_and_stay_inline(pattern: str) -> N
154
213
  check(f"var ok=value is {pattern};", f"var ok = value is {pattern};\n")
155
214
 
156
215
 
216
+ def test_negated_pattern_chain_and_boolean_tail_share_one_indent() -> None:
217
+ check(
218
+ "var persistent = _status is not 400 and not 408 and not 411 and not 413 and not 414 "
219
+ "and not 500 and not 503 && !_transport.IsDraining && KeepAlive;",
220
+ "var persistent = _status is not 400\n"
221
+ " and not 408\n and not 411\n and not 413\n and not 414\n"
222
+ " and not 500\n and not 503\n && !_transport.IsDraining\n && KeepAlive;\n",
223
+ )
224
+
225
+
157
226
  def test_call_breaks_one_argument_per_line_with_paren_on_its_own_line() -> None:
158
227
  check(
159
228
  """
@@ -827,6 +896,30 @@ def test_empty_collection_moves_to_the_next_line_whole() -> None:
827
896
  )
828
897
 
829
898
 
899
+ def test_long_empty_constructor_keeps_parentheses_together() -> None:
900
+ check(
901
+ 'private readonly ConcurrentDictionary<IPAddress, ConcurrentBag<long>> _failRegexMatches = '
902
+ 'new ConcurrentDictionary<IPAddress, ConcurrentBag<long>>();',
903
+ 'private readonly ConcurrentDictionary<IPAddress, ConcurrentBag<long>> _failRegexMatches =\n'
904
+ ' new ConcurrentDictionary<IPAddress, ConcurrentBag<long>>();\n',
905
+ )
906
+
907
+
908
+ @pytest.mark.parametrize("wrap", ["one_per_line", "one_per_line_close_on_last", "fill", "align"])
909
+ def test_empty_call_parentheses_never_split(wrap: str) -> None:
910
+ check(
911
+ 'SomeVeryLongMethodName();', 'SomeVeryLongMethodName();\n',
912
+ max_line_length=12, argument_wrap=wrap,
913
+ )
914
+
915
+
916
+ def test_comments_inside_empty_constructor_are_preserved() -> None:
917
+ source = 'var instance = new Item( // Keep this note.\n);'
918
+ output = fmt(source)
919
+ assert '// Keep this note.\n' in output
920
+ assert fmt(output) == output
921
+
922
+
830
923
  def test_spread_elements_are_spaced_and_ranges_are_not() -> None:
831
924
  assert fmt("x = [..entries, 1, ..more];\n") == "x = [.. entries, 1, .. more];\n"
832
925
  assert fmt("var r = entries[1..] + entries[..2] + entries[..];\n") == (
@@ -834,6 +927,50 @@ def test_spread_elements_are_spaced_and_ranges_are_not() -> None:
834
927
  )
835
928
 
836
929
 
930
+ @pytest.mark.parametrize("element", ['new HpackField(wire, "")', "GetField()", "wire.Length + 1", "wire"])
931
+ @pytest.mark.parametrize("simple_inline", [True, False])
932
+ def test_inline_collection_brackets_stay_tight(element: str, simple_inline: bool) -> None:
933
+ check(
934
+ f"Validate([ {element} ]);",
935
+ f"Validate([{element}]);\n",
936
+ simple_lists_inline=simple_inline,
937
+ )
938
+
939
+
940
+ def test_collection_brackets_in_awaited_send_stay_tight() -> None:
941
+ check(
942
+ 'await SendAsync([ new Http2Frame(6, 1, 0, frame.Payload) ], token).ConfigureAwait(false);',
943
+ 'await SendAsync([new Http2Frame(6, 1, 0, frame.Payload)], token).ConfigureAwait(false);\n',
944
+ )
945
+
946
+
947
+ def test_multiline_collection_argument_has_no_extra_blank_line() -> None:
948
+ check(
949
+ 'await SendHeadersAsync([new HpackField(":status", "200"), '
950
+ 'new HpackField("content-length", bytes.Length.ToString(CultureInfo.InvariantCulture))], '
951
+ 'bytes.Length == 0 || Request.Method == "HEAD", token).ConfigureAwait(false);',
952
+ 'await SendHeadersAsync(\n'
953
+ ' [\n'
954
+ ' new HpackField(":status", "200"),\n'
955
+ ' new HpackField("content-length", bytes.Length.ToString(CultureInfo.InvariantCulture))\n'
956
+ ' ],\n'
957
+ ' bytes.Length == 0 || Request.Method == "HEAD",\n'
958
+ ' token\n'
959
+ ').ConfigureAwait(false);\n',
960
+ )
961
+
962
+
963
+ def test_spread_of_call_in_multiline_arguments_keeps_brackets_tight() -> None:
964
+ check(
965
+ '_session = Session.Start([ .. _prefixes.Select(value => new ListenerPrefix(value)) ], '
966
+ '_certificates, TlsPolicy, _native, DeferContinue, Telemetry, _bodyPolicy, _dispatcher);',
967
+ '_session = Session.Start(\n'
968
+ ' [.. _prefixes.Select(value => new ListenerPrefix(value))],\n'
969
+ ' _certificates,\n TlsPolicy,\n _native,\n DeferContinue,\n'
970
+ ' Telemetry,\n _bodyPolicy,\n _dispatcher\n);\n',
971
+ )
972
+
973
+
837
974
  def test_empty_body_after_a_header_that_spans_lines_gets_its_own_line() -> None:
838
975
  # Visual Studio (IDE0055) keeps `{ }` on the header's line only when the
839
976
  # header is one line.
@@ -901,9 +1038,9 @@ def test_empty_catch_and_lambda_bodies_after_a_header_that_spans_lines() -> None
901
1038
  try { }
902
1039
 
903
1040
  // Ignored.
904
- catch (Exception error) when (IsRecoverable(
905
- error
906
- ))
1041
+ catch (Exception error) when (
1042
+ IsRecoverable(error)
1043
+ )
907
1044
  { }
908
1045
 
909
1046
  Run((
@@ -967,6 +1104,26 @@ def test_auto_properties_and_empty_bodies_stay_on_one_line() -> None:
967
1104
  )
968
1105
 
969
1106
 
1107
+ def test_tuple_block_lambda_closes_parentheses_on_the_call_line_indent() -> None:
1108
+ check(
1109
+ '_dataHandlerPairs.Add((data, (ctx, route) => { handler(ctx, route); return Task.CompletedTask; }));',
1110
+ '_dataHandlerPairs.Add((data, (ctx, route) =>\n'
1111
+ ' {\n handler(ctx, route);\n return Task.CompletedTask;\n }\n));\n',
1112
+ )
1113
+
1114
+
1115
+ def test_ordinary_block_lambda_argument_keeps_hugged_parenthesis() -> None:
1116
+ check(
1117
+ 'Run(data, (ctx, route) => { handler(ctx, route); return Task.CompletedTask; });',
1118
+ 'Run(data, (ctx, route) =>\n'
1119
+ '{\n handler(ctx, route);\n return Task.CompletedTask;\n});\n',
1120
+ )
1121
+
1122
+
1123
+ def test_empty_tuple_lambda_stays_inline() -> None:
1124
+ check('Run((data, () => { }));', 'Run((data, () => { }));\n')
1125
+
1126
+
970
1127
  def test_object_initializer_hugs_the_last_argument() -> None:
971
1128
  check(
972
1129
  """
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes