Skip to content

Commit 357996a

Browse files
authored
A single leading space should not override an otherwise 100% tab-indented file (#507)
2 parents 6183019 + a638736 commit 357996a

File tree

8 files changed

+90
-18
lines changed

8 files changed

+90
-18
lines changed

jvm/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1111
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1212

1313
## [Unreleased]
14+
### Fixed
15+
- A single leading space (such as in the copyright header) should not override an otherwise 100% tab-indented file. ([#506](https://github.com/diffplug/selfie/issues/506))
1416

1517
## [2.4.1] - 2024-10-07
1618
### Fixed

jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2024 DiffPlug
2+
* Copyright (C) 2024-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ internal enum class EscapeLeadingWhitespace {
3535
.lineSequence()
3636
.mapNotNull { line ->
3737
val whitespace = line.takeWhile { it.isWhitespace() }
38-
if (whitespace.isEmpty()) null
38+
if (whitespace.isEmpty() || whitespace == " ") null
3939
else if (whitespace.all { it == ' ' }) ' '
4040
else if (whitespace.all { it == '\t' }) '\t' else MIXED
4141
}

jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2024 DiffPlug
2+
* Copyright (C) 2024-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,16 +29,28 @@ class EscapeLeadingWhitespaceTest {
2929
appropriateFor("abc\nabc") shouldBe ALWAYS
3030

3131
// all spaces -> only tabs need escape
32-
appropriateFor(" ") shouldBe ONLY_ON_TAB
32+
appropriateFor(" ") shouldBe ALWAYS
3333
appropriateFor(" ") shouldBe ONLY_ON_TAB
34-
appropriateFor(" \n ") shouldBe ONLY_ON_TAB
34+
appropriateFor(" \n ") shouldBe ONLY_ON_TAB
3535

3636
// all tabs -> only space needs escape
3737
appropriateFor("\t") shouldBe ONLY_ON_SPACE
3838
appropriateFor("\t\t") shouldBe ONLY_ON_SPACE
3939
appropriateFor("\t\n\t") shouldBe ONLY_ON_SPACE
4040

4141
// it's a mess -> everything needs escape
42-
appropriateFor("\t\n ") shouldBe ALWAYS
42+
appropriateFor("\t\n ") shouldBe ALWAYS
43+
44+
// single spaces and tabs -> only tabs need escape
45+
appropriateFor(
46+
"""
47+
/*
48+
${' '}* Copyright
49+
${' '}*/
50+
interface Foo {
51+
${'\t'}fun bar()
52+
}
53+
""") shouldBe
54+
ONLY_ON_SPACE
4355
}
4456
}

python/CHANGELOG.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22

33
Changelog for the selfie Python libraries.
44

5-
- [`com.diffplug.selfie:selfie-lib:VERSION`](https://pypi.org/project/selfie-lib/)
6-
- [`com.diffplug.selfie:selfie-runner-pytest:VERSION`](https://pypi.org/project/pytest-selfie/)
7-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5+
- [`selfie-lib:VERSION`](https://pypi.org/project/selfie-lib/)
6+
- [`pytest-selfie:VERSION`](https://pypi.org/project/pytest-selfie/)
7+
8+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9+
10+
Allowable headings are `Added`, `Fixed`, and `Changed`.
911

1012
## [Unreleased]
11-
### Added
12-
- TODO
1313
### Fixed
14-
- TODO
15-
### Changed
16-
- TODO
14+
- A single leading space (such as in the copyright header) should not override an otherwise 100% tab-indented file. ([#506](https://github.com/diffplug/selfie/issues/506))
1715

1816
## [1.0.0] - 2024-12-16
1917

python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def appropriate_for(cls, file_content: str) -> "EscapeLeadingWhitespace":
3333
common_whitespace = None
3434

3535
for line in file_content.splitlines():
36-
whitespace = "".join(c for c in line if c.isspace())
37-
if not whitespace:
36+
whitespace = line[0 : len(line) - len(line.lstrip())]
37+
if whitespace == "" or whitespace == " ":
3838
continue
3939
elif all(c == " " for c in whitespace):
4040
whitespace = " "

python/selfie-lib/selfie_lib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .CacheSelfie import cache_selfie_binary as cache_selfie_binary
77
from .CacheSelfie import cache_selfie_json as cache_selfie_json
88
from .CommentTracker import CommentTracker as CommentTracker
9+
from .EscapeLeadingWhitespace import EscapeLeadingWhitespace as EscapeLeadingWhitespace
910
from .FS import FS as FS
1011
from .Lens import Camera as Camera
1112
from .Lens import CompoundLens as CompoundLens
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from selfie_lib import EscapeLeadingWhitespace
2+
3+
4+
def test_detection():
5+
# not enough to detect
6+
assert EscapeLeadingWhitespace.appropriate_for("") == EscapeLeadingWhitespace.ALWAYS
7+
assert (
8+
EscapeLeadingWhitespace.appropriate_for("abc") == EscapeLeadingWhitespace.ALWAYS
9+
)
10+
assert (
11+
EscapeLeadingWhitespace.appropriate_for("abc\nabc")
12+
== EscapeLeadingWhitespace.ALWAYS
13+
)
14+
15+
# all spaces -> only tabs need escape
16+
assert (
17+
EscapeLeadingWhitespace.appropriate_for(" ") == EscapeLeadingWhitespace.ALWAYS
18+
)
19+
assert (
20+
EscapeLeadingWhitespace.appropriate_for(" ")
21+
== EscapeLeadingWhitespace.ONLY_ON_TAB
22+
)
23+
assert (
24+
EscapeLeadingWhitespace.appropriate_for(" \n ")
25+
== EscapeLeadingWhitespace.ONLY_ON_TAB
26+
)
27+
28+
# all tabs -> only space needs escape
29+
assert (
30+
EscapeLeadingWhitespace.appropriate_for("\t")
31+
== EscapeLeadingWhitespace.ONLY_ON_SPACE
32+
)
33+
assert (
34+
EscapeLeadingWhitespace.appropriate_for("\t\t")
35+
== EscapeLeadingWhitespace.ONLY_ON_SPACE
36+
)
37+
assert (
38+
EscapeLeadingWhitespace.appropriate_for("\t\n\t")
39+
== EscapeLeadingWhitespace.ONLY_ON_SPACE
40+
)
41+
42+
# it's a mess -> everything needs escape
43+
assert (
44+
EscapeLeadingWhitespace.appropriate_for("\t\n ")
45+
== EscapeLeadingWhitespace.ALWAYS
46+
)
47+
48+
# single spaces and tabs -> only tabs need escape
49+
tab = "\t"
50+
test_string = f"""/*
51+
* Copyright
52+
*/
53+
interface Foo [
54+
{tab}bar()
55+
]"""
56+
assert (
57+
EscapeLeadingWhitespace.appropriate_for(test_string)
58+
== EscapeLeadingWhitespace.ONLY_ON_SPACE
59+
)

python/selfie-lib/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)