prettycsharp 1.1.4__tar.gz → 1.1.5__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.5}/CHANGELOG.md +12 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/DESIGN.md +1 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/PKG-INFO +1 -1
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/__init__.py +1 -1
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/conditional.py +40 -2
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/formatter.py +1 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/printer/core.py +19 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/printer/declarations.py +6 -1
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/printer/expressions.py +15 -3
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/printer/statements.py +4 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/settings.py +2 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_conditional.py +100 -2
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_layout.py +36 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_settings.py +6 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.gitattributes +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/codeql/codeql-config.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/dependabot.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/build.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/build.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/fuzz.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/fuzz.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/pip-audit.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/pip-audit.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/runtime.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/runtime.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/test.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/test.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/zizmor.in +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/requirements/zizmor.txt +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/security/accepted.toml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/security/clamav/Dockerfile +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/security/malware-accepted.toml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/security/semgrep/Dockerfile +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/security/yara.json +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/security/yara_update.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/auto-merge.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/ci.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/fuzz.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/malware-scan.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/publish.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/scorecard.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/security.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/workflows/update-yara-rules.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.github/zizmor.yml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/.gitignore +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/AGENTS.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/CLAUDE.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/LICENSE +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/README.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/SECURITY.md +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/fuzz/fuzz_formatter.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/pyproject.toml +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/scripts/security/malware_report.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/scripts/security/security_report.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/__main__.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/cli.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/config.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/doc.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/ignore.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/isolate.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/parsing.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/printer/__init__.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/project.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/py.typed +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/rewrites.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/syntax.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/src/prettycsharp/verify.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/comment_only_accessor_list.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/declarations.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/directives.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/embedded_statements.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/expressions.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/members.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/order.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/queries_and_patterns.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/split_by_conditionals.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/statements.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/fuzz_corpus/csharp/strings.cs +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/support.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_cli.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_config.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_doc.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_docs.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_format_around.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_fuzz_corpus.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_ignore.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_parsing.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_project.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_rewrites.py +0 -0
- {prettycsharp-1.1.4 → prettycsharp-1.1.5}/tests/test_verify.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Each release's section becomes the body of its GitHub release.
|
|
4
4
|
|
|
5
|
+
## [1.1.5] - 2026-10-18
|
|
6
|
+
|
|
7
|
+
- Nullable array types print tight, `object?[]?`, instead of
|
|
8
|
+
`object? []?`, and spread elements are spaced, `[.. items]`, as Visual
|
|
9
|
+
Studio formats them (IDE0055). Ranges such as `items[..2]` stay tight.
|
|
10
|
+
- An empty collection `[]` is never split across lines. When it does not
|
|
11
|
+
fit, the line breaks after `=>` or `=` and `[]` goes to the next line.
|
|
12
|
+
- No blank line is left directly above or below `#if`, `#elif`, `#else`
|
|
13
|
+
and `#endif`. A comment above an `#if` keeps the blank line above the
|
|
14
|
+
comment. The new setting `directive_blank_lines = "preserve"` keeps the
|
|
15
|
+
blank lines as written.
|
|
16
|
+
|
|
5
17
|
## [1.1.4] - 2026-10-17
|
|
6
18
|
|
|
7
19
|
- 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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: prettycsharp
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.5
|
|
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.5"
|
|
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)):
|
|
@@ -321,6 +323,8 @@ class Statements(Expressions):
|
|
|
321
323
|
return [self.tok(open_tok), body, self.close_comments(close_tok), HARDLINE, self.tok(close_tok, leading=False)]
|
|
322
324
|
|
|
323
325
|
def section_blank(self, previous: Node, item: Node) -> bool:
|
|
326
|
+
if self.tight_directive(previous, item):
|
|
327
|
+
return False
|
|
324
328
|
if previous.type in DIRECTIVE_TYPES or item.type in DIRECTIVE_TYPES:
|
|
325
329
|
return self.has_blank_before(item)
|
|
326
330
|
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
|
|
@@ -707,6 +707,42 @@ def test_obvious_parentheses_are_removed_and_clarifying_ones_kept() -> None:
|
|
|
707
707
|
)
|
|
708
708
|
|
|
709
709
|
|
|
710
|
+
def test_nullable_array_types_stay_tight() -> None:
|
|
711
|
+
source = (
|
|
712
|
+
"void M(params object?[]? values) { }\n"
|
|
713
|
+
"\n"
|
|
714
|
+
"int?[] a = new int?[3];\n"
|
|
715
|
+
"List<string?[]> b = (string?[][])o;\n"
|
|
716
|
+
"var c = x is int?[] d;\n"
|
|
717
|
+
"var e = f ? [1] : [2];\n"
|
|
718
|
+
"var g = h?[i];\n"
|
|
719
|
+
)
|
|
720
|
+
assert fmt(source) == source
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def test_empty_collection_moves_to_the_next_line_whole() -> None:
|
|
724
|
+
check(
|
|
725
|
+
"class C { IEnumerable<Entry> Entries(string path, IMimeTypeProvider provider) => []; int[] xs = []; }\n",
|
|
726
|
+
"""
|
|
727
|
+
class C
|
|
728
|
+
{
|
|
729
|
+
IEnumerable<Entry> Entries(string path, IMimeTypeProvider provider) =>
|
|
730
|
+
[];
|
|
731
|
+
|
|
732
|
+
int[] xs = [];
|
|
733
|
+
}
|
|
734
|
+
""",
|
|
735
|
+
max_line_length=74,
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def test_spread_elements_are_spaced_and_ranges_are_not() -> None:
|
|
740
|
+
assert fmt("x = [..entries, 1, ..more];\n") == "x = [.. entries, 1, .. more];\n"
|
|
741
|
+
assert fmt("var r = entries[1..] + entries[..2] + entries[..];\n") == (
|
|
742
|
+
"var r = entries[1..] + entries[..2] + entries[..];\n"
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
|
|
710
746
|
def test_empty_body_after_a_header_that_spans_lines_gets_its_own_line() -> None:
|
|
711
747
|
# Visual Studio (IDE0055) keeps `{ }` on the header's line only when the
|
|
712
748
|
# 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.5}/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
|