prettycsharp 1.1.4__tar.gz → 1.1.6__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.
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/CHANGELOG.md +18 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/DESIGN.md +5 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/PKG-INFO +1 -1
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/__init__.py +1 -1
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/conditional.py +40 -2
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/formatter.py +1 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/printer/core.py +19 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/printer/declarations.py +6 -1
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/printer/expressions.py +15 -3
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/printer/statements.py +15 -4
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/settings.py +2 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_conditional.py +100 -2
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_layout.py +58 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_settings.py +6 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.gitattributes +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/codeql/codeql-config.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/dependabot.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/build.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/build.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/fuzz.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/fuzz.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/pip-audit.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/pip-audit.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/runtime.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/runtime.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/test.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/test.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/zizmor.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/requirements/zizmor.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/security/accepted.toml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/security/clamav/Dockerfile +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/security/malware-accepted.toml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/security/semgrep/Dockerfile +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/security/yara.json +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/security/yara_update.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/auto-merge.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/ci.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/fuzz.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/malware-scan.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/publish.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/scorecard.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/security.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/workflows/update-yara-rules.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.github/zizmor.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/.gitignore +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/AGENTS.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/CLAUDE.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/LICENSE +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/README.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/SECURITY.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/fuzz/fuzz_formatter.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/pyproject.toml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/scripts/security/malware_report.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/scripts/security/security_report.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/__main__.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/cli.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/config.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/doc.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/ignore.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/isolate.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/parsing.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/printer/__init__.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/project.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/py.typed +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/rewrites.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/syntax.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/src/prettycsharp/verify.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/comment_only_accessor_list.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/declarations.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/directives.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/embedded_statements.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/expressions.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/members.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/order.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/queries_and_patterns.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/split_by_conditionals.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/statements.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/strings.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/support.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_cli.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_config.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_doc.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_docs.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_format_around.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_fuzz_corpus.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_ignore.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_parsing.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_project.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_rewrites.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/test_verify.py +0 -0
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Each release's section becomes the body of its GitHub release.
|
|
4
4
|
|
|
5
|
+
## [1.1.6] - 2026-10-18
|
|
6
|
+
|
|
7
|
+
- `for` headers keep a space between semicolons when the condition is
|
|
8
|
+
omitted, such as `for (var start = 0; ;)`, avoiding Visual Studio's
|
|
9
|
+
IDE0055 formatting diagnostic. Fully empty headers print `for (; ; )`.
|
|
10
|
+
|
|
11
|
+
## [1.1.5] - 2026-10-18
|
|
12
|
+
|
|
13
|
+
- Nullable array types print tight, `object?[]?`, instead of
|
|
14
|
+
`object? []?`, and spread elements are spaced, `[.. items]`, as Visual
|
|
15
|
+
Studio formats them (IDE0055). Ranges such as `items[..2]` stay tight.
|
|
16
|
+
- An empty collection `[]` is never split across lines. When it does not
|
|
17
|
+
fit, the line breaks after `=>` or `=` and `[]` goes to the next line.
|
|
18
|
+
- No blank line is left directly above or below `#if`, `#elif`, `#else`
|
|
19
|
+
and `#endif`. A comment above an `#if` keeps the blank line above the
|
|
20
|
+
comment. The new setting `directive_blank_lines = "preserve"` keeps the
|
|
21
|
+
blank lines as written.
|
|
22
|
+
|
|
5
23
|
## [1.1.4] - 2026-10-17
|
|
6
24
|
|
|
7
25
|
- An empty body `{ }` after a header that spans lines goes on its own line,
|
|
@@ -134,6 +134,7 @@ while (item != null);
|
|
|
134
134
|
| `member_blank_lines` | `group_fields`: consecutive fields and auto-properties stay tight, one blank line around methods, constructors, and other members with bodies | `all`, `preserve` |
|
|
135
135
|
| `blank_lines_around_blocks` | `true`: a block statement gets a blank line before and after it, except right after `{` or right before `}` | `false` |
|
|
136
136
|
| `max_blank_lines` | `1`: author's blank lines between plain statements are kept, runs collapse to one | any integer, `0` |
|
|
137
|
+
| `directive_blank_lines` | `remove`: no blank line directly above or below `#if`, `#elif`, `#else` and `#endif` | `preserve` |
|
|
137
138
|
|
|
138
139
|
No blank line ever follows `{` or precedes `}`. A comment directly above a block moves with
|
|
139
140
|
it, so the blank line goes above the comment.
|
|
@@ -160,6 +161,10 @@ public void Ship()
|
|
|
160
161
|
|
|
161
162
|
### Line breaking
|
|
162
163
|
|
|
164
|
+
`for` headers keep a space after each semicolon even when the next clause
|
|
165
|
+
is omitted: `for (var i = 0; ;)`. There is no space before the closing
|
|
166
|
+
parenthesis unless all three clauses are empty: `for (; ; )`.
|
|
167
|
+
|
|
163
168
|
| Setting | Default | Alternatives |
|
|
164
169
|
| --- | --- | --- |
|
|
165
170
|
| `argument_wrap` | `one_per_line`: each argument on its own line, `)` on its own line. Also applies to parameter lists. | `one_per_line_close_on_last`, `fill`, `align` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: prettycsharp
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.6
|
|
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.
|
|
7
|
+
__version__ = "1.1.6"
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"CSharpSyntaxError",
|
|
@@ -369,18 +369,53 @@ def covering_sets(source: str, usable: Callable[[str], bool] = parses) -> list[f
|
|
|
369
369
|
return chosen
|
|
370
370
|
|
|
371
371
|
|
|
372
|
+
def tighten(text: str, hidden: dict[str, str]) -> str:
|
|
373
|
+
"""Drop the blank lines next to conditional directive lines behind placeholders.
|
|
374
|
+
|
|
375
|
+
A placeholder whose hidden text starts with #if, #elif, #else or #endif
|
|
376
|
+
loses the blank lines above it; one whose hidden text ends with one loses
|
|
377
|
+
the blank lines below it.
|
|
378
|
+
"""
|
|
379
|
+
lines = _split_lines(text)
|
|
380
|
+
drop: set[int] = set()
|
|
381
|
+
for number, line in enumerate(lines):
|
|
382
|
+
match = _PLACEHOLDER.match(_content(line))
|
|
383
|
+
if match is None or match.group(1) not in hidden:
|
|
384
|
+
continue
|
|
385
|
+
rows = hidden[match.group(1)].splitlines()
|
|
386
|
+
if _conditional(rows[0]):
|
|
387
|
+
above = number - 1
|
|
388
|
+
while above >= 0 and not _content(lines[above]).strip():
|
|
389
|
+
drop.add(above)
|
|
390
|
+
above -= 1
|
|
391
|
+
if _conditional(rows[-1]):
|
|
392
|
+
below = number + 1
|
|
393
|
+
while below < len(lines) and not _content(lines[below]).strip():
|
|
394
|
+
drop.add(below)
|
|
395
|
+
below += 1
|
|
396
|
+
return "".join(line for number, line in enumerate(lines) if number not in drop)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _conditional(line: str) -> bool:
|
|
400
|
+
found = _directive(line)
|
|
401
|
+
return found is not None and found[0] in ("if", "elif", "else", "endif")
|
|
402
|
+
|
|
403
|
+
|
|
372
404
|
def format_conditional(
|
|
373
405
|
source: str,
|
|
374
406
|
format_masked: Callable[[str], str],
|
|
375
407
|
same_code: Callable[[str, str], bool],
|
|
376
408
|
usable: Callable[[str], bool] = parses,
|
|
409
|
+
*,
|
|
410
|
+
tight: bool = False,
|
|
377
411
|
) -> str | None:
|
|
378
412
|
"""Format each symbol set's view of the file in turn.
|
|
379
413
|
|
|
380
414
|
Returns None when no symbol set gives text that is ``usable`` (parses,
|
|
381
415
|
by default). Raises ConditionalError when the result, seen through any
|
|
382
416
|
of the symbol sets used, is not the same code as the source
|
|
383
|
-
(``same_code`` compares two masked texts).
|
|
417
|
+
(``same_code`` compares two masked texts). With ``tight``, blank lines
|
|
418
|
+
next to conditional directives are dropped.
|
|
384
419
|
"""
|
|
385
420
|
sets = covering_sets(source, usable)
|
|
386
421
|
if not sets:
|
|
@@ -388,7 +423,10 @@ def format_conditional(
|
|
|
388
423
|
text = source
|
|
389
424
|
for symbols in sets:
|
|
390
425
|
masked = mask(text, symbols)
|
|
391
|
-
|
|
426
|
+
formatted = format_masked(masked.text)
|
|
427
|
+
if tight:
|
|
428
|
+
formatted = tighten(formatted, masked.hidden)
|
|
429
|
+
text = restore(formatted, masked.hidden)
|
|
392
430
|
for symbols in sets:
|
|
393
431
|
if not same_code(mask(source, symbols).text, mask(text, symbols).text):
|
|
394
432
|
raise BranchChanged("a branch of an #if would change in meaning")
|
|
@@ -92,6 +92,7 @@ def _format(source: str, settings: Settings, lang_version: float) -> FormatResul
|
|
|
92
92
|
functools.partial(_format_view, settings=hiding, lang_version=lang_version, around=around, kept=kept),
|
|
93
93
|
functools.partial(_same_view, around=around),
|
|
94
94
|
_usable_around if around else conditional.parses,
|
|
95
|
+
tight=settings.directive_blank_lines == "remove",
|
|
95
96
|
)
|
|
96
97
|
except conditional.BranchChanged as error:
|
|
97
98
|
raise FormattingError(str(error)) from None
|
|
@@ -72,6 +72,9 @@ _MERGING_PAIRS = {
|
|
|
72
72
|
_URL = re.compile(r"\w+://")
|
|
73
73
|
_LIST_ITEM = re.compile(r"^(?:[-*+]\s|\d+[.)]\s)")
|
|
74
74
|
|
|
75
|
+
# Directives that open a conditional branch; as list items they end in #endif.
|
|
76
|
+
_CONDITIONAL_TYPES = frozenset({"preproc_if", "preproc_elif", "preproc_else"})
|
|
77
|
+
|
|
75
78
|
_INDENTING = frozenset(
|
|
76
79
|
{"declaration_list", "block", "accessor_list", "enum_member_declaration_list"}
|
|
77
80
|
)
|
|
@@ -344,6 +347,22 @@ class Core:
|
|
|
344
347
|
return first.leading[0].newlines_before >= 2
|
|
345
348
|
return first.newlines_before >= 2
|
|
346
349
|
|
|
350
|
+
def tight_directive(self, previous: Node, item: Node) -> bool:
|
|
351
|
+
"""Whether ``directive_blank_lines`` removes the blank line between two items.
|
|
352
|
+
|
|
353
|
+
An #if item ends in #endif; a member can start with an #if around its
|
|
354
|
+
attributes. A comment above an #if keeps the blank line above it,
|
|
355
|
+
since that line is not next to the directive.
|
|
356
|
+
"""
|
|
357
|
+
if self.settings.directive_blank_lines != "remove":
|
|
358
|
+
return False
|
|
359
|
+
if previous.type in _CONDITIONAL_TYPES:
|
|
360
|
+
return True
|
|
361
|
+
starts_with_if = item.type in _CONDITIONAL_TYPES or (
|
|
362
|
+
bool(item.children) and item.children[0].type == "preproc_if_in_attribute_list"
|
|
363
|
+
)
|
|
364
|
+
return starts_with_if and not self.has_comments_before(item)
|
|
365
|
+
|
|
347
366
|
def has_comments_before(self, node: Node) -> bool:
|
|
348
367
|
first = node.first_token()
|
|
349
368
|
return first is not None and bool(first.leading)
|
|
@@ -71,6 +71,8 @@ class Declarations(Statements):
|
|
|
71
71
|
return parts
|
|
72
72
|
|
|
73
73
|
def top_blank(self, previous: Node, item: Node) -> bool:
|
|
74
|
+
if self.tight_directive(previous, item):
|
|
75
|
+
return False
|
|
74
76
|
if previous.type == "file_scoped_namespace_declaration":
|
|
75
77
|
return self.settings.max_blank_lines > 0
|
|
76
78
|
if previous.type in DIRECTIVE_TYPES or item.type in DIRECTIVE_TYPES:
|
|
@@ -141,7 +143,8 @@ class Declarations(Statements):
|
|
|
141
143
|
node.text is not None and node.text.startswith("#") and node.column == 0 and not node.leading
|
|
142
144
|
)
|
|
143
145
|
line: Doc = HARDLINE
|
|
144
|
-
|
|
146
|
+
# Every line placed here is next to a directive line.
|
|
147
|
+
if self.has_blank_before(node) and self.settings.directive_blank_lines != "remove":
|
|
145
148
|
line = [HARDLINE, HARDLINE]
|
|
146
149
|
return [line, TRIM_INDENT] if at_zero else line
|
|
147
150
|
|
|
@@ -222,6 +225,8 @@ class Declarations(Statements):
|
|
|
222
225
|
return self.print_list(items, self.member_blank, aligned=aligned)
|
|
223
226
|
|
|
224
227
|
def member_blank(self, previous: Node, item: Node) -> bool:
|
|
228
|
+
if self.tight_directive(previous, item):
|
|
229
|
+
return False
|
|
225
230
|
if previous.type in DIRECTIVE_TYPES or item.type in DIRECTIVE_TYPES:
|
|
226
231
|
return self.directive_blank(previous, item)
|
|
227
232
|
if self.settings.max_blank_lines == 0:
|
|
@@ -277,11 +277,19 @@ class Expressions(Core):
|
|
|
277
277
|
if value.verbatim:
|
|
278
278
|
return False
|
|
279
279
|
if value.type in _BLOCK_LIKE:
|
|
280
|
-
|
|
280
|
+
# An empty `[]` is one word: it moves to the next line whole.
|
|
281
|
+
return not self._empty_collection(value)
|
|
281
282
|
if value.type in _CREATION_TYPES:
|
|
282
283
|
return value.child("initializer") is not None or value.first_of("initializer_expression") is not None
|
|
283
284
|
return False
|
|
284
285
|
|
|
286
|
+
def _empty_collection(self, node: Node) -> bool:
|
|
287
|
+
"""An empty collection expression with no comments inside: `[]`."""
|
|
288
|
+
if node.type != "collection_expression" or len(node.children) != 2:
|
|
289
|
+
return False
|
|
290
|
+
open_tok, close_tok = node.children
|
|
291
|
+
return not open_tok.trailing and not close_tok.leading
|
|
292
|
+
|
|
285
293
|
# Member chains -------------------------------------------------------
|
|
286
294
|
|
|
287
295
|
def p_invocation_expression(self, node: Node) -> Doc:
|
|
@@ -655,6 +663,8 @@ class Expressions(Core):
|
|
|
655
663
|
open_tok = node.children[0]
|
|
656
664
|
close_tok = node.children[-1]
|
|
657
665
|
items, commas = self.items_and_commas(node)
|
|
666
|
+
if self._empty_collection(node):
|
|
667
|
+
return [self.tok(open_tok), self.tok(close_tok)]
|
|
658
668
|
if not items:
|
|
659
669
|
return group([self.tok(open_tok), self.closing(close_tok, SOFTLINE)])
|
|
660
670
|
docs = self._comma_docs(items, commas, may_add=True)
|
|
@@ -1005,7 +1015,8 @@ class Expressions(Core):
|
|
|
1005
1015
|
return self.join_tokens([(c, self.print(c)) for c in node.children])
|
|
1006
1016
|
|
|
1007
1017
|
def p_array_type(self, node: Node) -> Doc:
|
|
1008
|
-
|
|
1018
|
+
# A rank follows its element type tight, nullable or not: `object?[]`.
|
|
1019
|
+
return [self.print(c) for c in node.children]
|
|
1009
1020
|
|
|
1010
1021
|
def p_array_rank_specifier(self, node: Node) -> Doc:
|
|
1011
1022
|
parts: list[Doc] = []
|
|
@@ -1040,7 +1051,8 @@ class Expressions(Core):
|
|
|
1040
1051
|
return self.generic(node)
|
|
1041
1052
|
|
|
1042
1053
|
def p_spread_element(self, node: Node) -> Doc:
|
|
1043
|
-
|
|
1054
|
+
# Visual Studio spaces a spread, `[.. items]`, unlike a range, `items[..2]`.
|
|
1055
|
+
return self.spaced(node.children)
|
|
1044
1056
|
|
|
1045
1057
|
|
|
1046
1058
|
class _OperatorMarker(Node):
|
|
@@ -76,6 +76,8 @@ class Statements(Expressions):
|
|
|
76
76
|
return self.print_list(items, self.statement_blank, aligned=self.aligned_assignments(items))
|
|
77
77
|
|
|
78
78
|
def statement_blank(self, previous: Node, item: Node) -> bool:
|
|
79
|
+
if self.tight_directive(previous, item):
|
|
80
|
+
return False
|
|
79
81
|
if previous.type in DIRECTIVE_TYPES or item.type in DIRECTIVE_TYPES:
|
|
80
82
|
return self.has_blank_before(item)
|
|
81
83
|
if self.settings.blank_lines_around_blocks and (self.is_block_statement(previous) or self.is_block_statement(item)):
|
|
@@ -225,18 +227,25 @@ class Statements(Expressions):
|
|
|
225
227
|
close_tok = node.children[close_index]
|
|
226
228
|
body = node.children[close_index + 1] if close_index + 1 < len(node.children) else None
|
|
227
229
|
open_index = node.children.index(open_tok)
|
|
230
|
+
header_children = node.children[open_index + 1 : close_index]
|
|
231
|
+
empty = not any(child.named for child in header_children)
|
|
228
232
|
inner: list[Doc] = []
|
|
229
233
|
pending_space = False
|
|
230
|
-
for child in
|
|
234
|
+
for child in header_children:
|
|
235
|
+
if pending_space:
|
|
236
|
+
inner.append(LINE)
|
|
231
237
|
if child.text in (";", ",") and not child.named:
|
|
232
238
|
inner.append(self.tok(child))
|
|
233
239
|
pending_space = True
|
|
234
240
|
continue
|
|
235
|
-
if pending_space:
|
|
236
|
-
inner.append(LINE)
|
|
237
241
|
pending_space = False
|
|
238
242
|
inner.append(self.print(child))
|
|
239
|
-
header = group(
|
|
243
|
+
header = group(
|
|
244
|
+
[
|
|
245
|
+
self.tok(keyword), " ", self.tok(open_tok), indent([SOFTLINE, inner]),
|
|
246
|
+
LINE if empty else SOFTLINE, self.tok(close_tok),
|
|
247
|
+
]
|
|
248
|
+
)
|
|
240
249
|
if body is None:
|
|
241
250
|
return header
|
|
242
251
|
return [header, self.embedded(body, close_tok, node)]
|
|
@@ -321,6 +330,8 @@ class Statements(Expressions):
|
|
|
321
330
|
return [self.tok(open_tok), body, self.close_comments(close_tok), HARDLINE, self.tok(close_tok, leading=False)]
|
|
322
331
|
|
|
323
332
|
def section_blank(self, previous: Node, item: Node) -> bool:
|
|
333
|
+
if self.tight_directive(previous, item):
|
|
334
|
+
return False
|
|
324
335
|
if previous.type in DIRECTIVE_TYPES or item.type in DIRECTIVE_TYPES:
|
|
325
336
|
return self.has_blank_before(item)
|
|
326
337
|
if not self.settings.switch_case_blank_lines or self.settings.max_blank_lines == 0:
|
|
@@ -58,6 +58,7 @@ class Settings:
|
|
|
58
58
|
member_blank_lines: str = "group_fields"
|
|
59
59
|
blank_lines_around_blocks: bool = True
|
|
60
60
|
max_blank_lines: int = 1
|
|
61
|
+
directive_blank_lines: str = "remove"
|
|
61
62
|
|
|
62
63
|
argument_wrap: str = "one_per_line"
|
|
63
64
|
chain_break_threshold: int = 2
|
|
@@ -128,6 +129,7 @@ _CHOICES: dict[str, tuple[str, ...]] = {
|
|
|
128
129
|
"braces": ("preserve", "always", "single_line_only"),
|
|
129
130
|
"empty_bodies": ("inline", "allman"),
|
|
130
131
|
"member_blank_lines": ("group_fields", "all", "preserve"),
|
|
132
|
+
"directive_blank_lines": ("remove", "preserve"),
|
|
131
133
|
"argument_wrap": ("one_per_line", "one_per_line_close_on_last", "fill", "align"),
|
|
132
134
|
"expression_body_chain": ("own_line", "hang"),
|
|
133
135
|
"binary_wrap": ("before_operator_paren_own_line", "before_operator", "after_operator"),
|
|
@@ -87,6 +87,106 @@ def test_covering_sets_reach_every_branch() -> None:
|
|
|
87
87
|
assert frozenset({"B"}) in sets
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def test_no_blank_lines_next_to_conditional_directives() -> None:
|
|
91
|
+
# The first parses whole; the second splits a class header, so each view
|
|
92
|
+
# is formatted on its own.
|
|
93
|
+
check(
|
|
94
|
+
"""
|
|
95
|
+
class C
|
|
96
|
+
{
|
|
97
|
+
int a;
|
|
98
|
+
|
|
99
|
+
#if X
|
|
100
|
+
|
|
101
|
+
int b;
|
|
102
|
+
|
|
103
|
+
#else
|
|
104
|
+
|
|
105
|
+
int c;
|
|
106
|
+
|
|
107
|
+
#endif
|
|
108
|
+
|
|
109
|
+
void M() { A();
|
|
110
|
+
|
|
111
|
+
#if DEBUG
|
|
112
|
+
Log();
|
|
113
|
+
#endif
|
|
114
|
+
|
|
115
|
+
B(); }
|
|
116
|
+
|
|
117
|
+
#if X
|
|
118
|
+
[A]
|
|
119
|
+
#endif
|
|
120
|
+
void N() { }
|
|
121
|
+
}
|
|
122
|
+
""",
|
|
123
|
+
"""
|
|
124
|
+
class C
|
|
125
|
+
{
|
|
126
|
+
int a;
|
|
127
|
+
#if X
|
|
128
|
+
int b;
|
|
129
|
+
#else
|
|
130
|
+
int c;
|
|
131
|
+
#endif
|
|
132
|
+
void M()
|
|
133
|
+
{
|
|
134
|
+
A();
|
|
135
|
+
#if DEBUG
|
|
136
|
+
Log();
|
|
137
|
+
#endif
|
|
138
|
+
B();
|
|
139
|
+
}
|
|
140
|
+
#if X
|
|
141
|
+
[A]
|
|
142
|
+
#endif
|
|
143
|
+
void N() { }
|
|
144
|
+
}
|
|
145
|
+
""",
|
|
146
|
+
)
|
|
147
|
+
check(
|
|
148
|
+
"""
|
|
149
|
+
#if NET
|
|
150
|
+
|
|
151
|
+
class A : B
|
|
152
|
+
{
|
|
153
|
+
|
|
154
|
+
#else
|
|
155
|
+
|
|
156
|
+
class A
|
|
157
|
+
{
|
|
158
|
+
|
|
159
|
+
#endif
|
|
160
|
+
|
|
161
|
+
int a;
|
|
162
|
+
|
|
163
|
+
#if X
|
|
164
|
+
|
|
165
|
+
int b;
|
|
166
|
+
|
|
167
|
+
#endif
|
|
168
|
+
|
|
169
|
+
void M() { }
|
|
170
|
+
}
|
|
171
|
+
""",
|
|
172
|
+
"""
|
|
173
|
+
#if NET
|
|
174
|
+
class A : B
|
|
175
|
+
{
|
|
176
|
+
#else
|
|
177
|
+
class A
|
|
178
|
+
{
|
|
179
|
+
#endif
|
|
180
|
+
int a;
|
|
181
|
+
#if X
|
|
182
|
+
int b;
|
|
183
|
+
#endif
|
|
184
|
+
void M() { }
|
|
185
|
+
}
|
|
186
|
+
""",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
90
190
|
def test_split_class_header_formats_every_branch() -> None:
|
|
91
191
|
check(
|
|
92
192
|
"""
|
|
@@ -120,7 +220,6 @@ def test_split_class_header_formats_every_branch() -> None:
|
|
|
120
220
|
#endif
|
|
121
221
|
{
|
|
122
222
|
public static int Size = 3;
|
|
123
|
-
|
|
124
223
|
#if NET10_0_OR_GREATER
|
|
125
224
|
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
|
126
225
|
#else
|
|
@@ -194,7 +293,6 @@ def test_usings_do_not_move_across_directives() -> None:
|
|
|
194
293
|
#endif
|
|
195
294
|
using Beta;
|
|
196
295
|
using Gamma;
|
|
197
|
-
|
|
198
296
|
#if NET
|
|
199
297
|
class C : I
|
|
200
298
|
#else
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
"""The default layout, one DESIGN.md decision per test."""
|
|
2
2
|
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
3
5
|
from support import check, fmt
|
|
4
6
|
|
|
5
7
|
|
|
8
|
+
@pytest.mark.parametrize(
|
|
9
|
+
("source", "expected"),
|
|
10
|
+
[
|
|
11
|
+
(";;", "; ; "),
|
|
12
|
+
("int i=0;;", "int i = 0; ;"),
|
|
13
|
+
(";i<10;", "; i < 10;"),
|
|
14
|
+
(";;i++", "; ; i++"),
|
|
15
|
+
("int i=0;i<10;", "int i = 0; i < 10;"),
|
|
16
|
+
("int i=0;;i++", "int i = 0; ; i++"),
|
|
17
|
+
(";i<10;i++", "; i < 10; i++"),
|
|
18
|
+
("int i=0;i<10;i++", "int i = 0; i < 10; i++"),
|
|
19
|
+
("int i=0,j=1;;i++,j--", "int i = 0, j = 1; ; i++, j--"),
|
|
20
|
+
("var start=query[0]=='?'?1:0;;", "var start = query[0] == '?' ? 1 : 0; ;"),
|
|
21
|
+
("int i=0;/* condition omitted */;", "int i = 0; /* condition omitted */ ;"),
|
|
22
|
+
],
|
|
23
|
+
)
|
|
24
|
+
def test_for_header_spacing_with_omitted_clauses(source: str, expected: str) -> None:
|
|
25
|
+
check(f"for({source}){{break;}}", f"for ({expected})\n{{\n break;\n}}\n")
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
def test_allman_braces_with_blank_line_before_continuations() -> None:
|
|
7
29
|
check(
|
|
8
30
|
"""
|
|
@@ -707,6 +729,42 @@ def test_obvious_parentheses_are_removed_and_clarifying_ones_kept() -> None:
|
|
|
707
729
|
)
|
|
708
730
|
|
|
709
731
|
|
|
732
|
+
def test_nullable_array_types_stay_tight() -> None:
|
|
733
|
+
source = (
|
|
734
|
+
"void M(params object?[]? values) { }\n"
|
|
735
|
+
"\n"
|
|
736
|
+
"int?[] a = new int?[3];\n"
|
|
737
|
+
"List<string?[]> b = (string?[][])o;\n"
|
|
738
|
+
"var c = x is int?[] d;\n"
|
|
739
|
+
"var e = f ? [1] : [2];\n"
|
|
740
|
+
"var g = h?[i];\n"
|
|
741
|
+
)
|
|
742
|
+
assert fmt(source) == source
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def test_empty_collection_moves_to_the_next_line_whole() -> None:
|
|
746
|
+
check(
|
|
747
|
+
"class C { IEnumerable<Entry> Entries(string path, IMimeTypeProvider provider) => []; int[] xs = []; }\n",
|
|
748
|
+
"""
|
|
749
|
+
class C
|
|
750
|
+
{
|
|
751
|
+
IEnumerable<Entry> Entries(string path, IMimeTypeProvider provider) =>
|
|
752
|
+
[];
|
|
753
|
+
|
|
754
|
+
int[] xs = [];
|
|
755
|
+
}
|
|
756
|
+
""",
|
|
757
|
+
max_line_length=74,
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def test_spread_elements_are_spaced_and_ranges_are_not() -> None:
|
|
762
|
+
assert fmt("x = [..entries, 1, ..more];\n") == "x = [.. entries, 1, .. more];\n"
|
|
763
|
+
assert fmt("var r = entries[1..] + entries[..2] + entries[..];\n") == (
|
|
764
|
+
"var r = entries[1..] + entries[..2] + entries[..];\n"
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
|
|
710
768
|
def test_empty_body_after_a_header_that_spans_lines_gets_its_own_line() -> None:
|
|
711
769
|
# Visual Studio (IDE0055) keeps `{ }` on the header's line only when the
|
|
712
770
|
# header is one line.
|
|
@@ -315,6 +315,12 @@ def test_comment_space_and_wrap_off() -> None:
|
|
|
315
315
|
)
|
|
316
316
|
|
|
317
317
|
|
|
318
|
+
def test_directive_blank_lines_preserve() -> None:
|
|
319
|
+
source = "class C\n{\n int a;\n\n#if X\n\n int b;\n\n#endif\n\n int c;\n}\n"
|
|
320
|
+
assert fmt(source, directive_blank_lines="preserve") == source
|
|
321
|
+
assert fmt(source) == "class C\n{\n int a;\n#if X\n int b;\n#endif\n int c;\n}\n"
|
|
322
|
+
|
|
323
|
+
|
|
318
324
|
def test_max_blank_lines_zero() -> None:
|
|
319
325
|
assert fmt("void M() { A();\n\n B(); }\n", max_blank_lines=0) == "void M()\n{\n A();\n B();\n}\n"
|
|
320
326
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prettycsharp-1.1.4 → prettycsharp-1.1.6}/tests/fuzz_corpus/csharp/comment_only_accessor_list.cs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|