From 4854fb65213b8807db4412ff5bac01164c470f9f Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 31 Dec 2024 09:25:05 -0800 Subject: [PATCH 1/7] A single leading space (such as in the copyright header) should not override an otherwise 100% tab-indented file. (fixes #506) --- jvm/CHANGELOG.md | 2 ++ .../selfie/guts/EscapeLeadingWhitespace.kt | 2 +- .../selfie/guts/EscapeLeadingWhitespaceTest.kt | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/jvm/CHANGELOG.md b/jvm/CHANGELOG.md index 70567d01..20b6d610 100644 --- a/jvm/CHANGELOG.md +++ b/jvm/CHANGELOG.md @@ -11,6 +11,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). ## [Unreleased] +### Fixed +- 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)) ## [2.4.1] - 2024-10-07 ### Fixed diff --git a/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt b/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt index 71d1b281..9eb5effe 100644 --- a/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt +++ b/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt @@ -35,7 +35,7 @@ internal enum class EscapeLeadingWhitespace { .lineSequence() .mapNotNull { line -> val whitespace = line.takeWhile { it.isWhitespace() } - if (whitespace.isEmpty()) null + if (whitespace.isEmpty() || whitespace == " ") null else if (whitespace.all { it == ' ' }) ' ' else if (whitespace.all { it == '\t' }) '\t' else MIXED } diff --git a/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt b/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt index 6f130fd1..52928804 100644 --- a/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt +++ b/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt @@ -29,9 +29,9 @@ class EscapeLeadingWhitespaceTest { appropriateFor("abc\nabc") shouldBe ALWAYS // all spaces -> only tabs need escape - appropriateFor(" ") shouldBe ONLY_ON_TAB + appropriateFor(" ") shouldBe ALWAYS appropriateFor(" ") shouldBe ONLY_ON_TAB - appropriateFor(" \n ") shouldBe ONLY_ON_TAB + appropriateFor(" \n ") shouldBe ONLY_ON_TAB // all tabs -> only space needs escape appropriateFor("\t") shouldBe ONLY_ON_SPACE @@ -39,6 +39,18 @@ class EscapeLeadingWhitespaceTest { appropriateFor("\t\n\t") shouldBe ONLY_ON_SPACE // it's a mess -> everything needs escape - appropriateFor("\t\n ") shouldBe ALWAYS + appropriateFor("\t\n ") shouldBe ALWAYS + + // single spaces and tabs -> only tabs need escape + appropriateFor( + """ +/* +${' '}* Copyright +${' '}*/ +interface Foo { +${'\t'}fun bar() +} +""") shouldBe + ONLY_ON_SPACE } } From f7bc17b048140f8d27738789983ee5c036590401 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 31 Dec 2024 12:36:11 -0800 Subject: [PATCH 2/7] Add a python test for EscapeLeadingWhitespace. --- python/selfie-lib/selfie_lib/__init__.py | 1 + .../tests/EscapeLeadingWhitespace_test.py | 29 +++++++++++++++++++ python/selfie-lib/uv.lock | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 python/selfie-lib/tests/EscapeLeadingWhitespace_test.py diff --git a/python/selfie-lib/selfie_lib/__init__.py b/python/selfie-lib/selfie_lib/__init__.py index b7dea382..1c531129 100644 --- a/python/selfie-lib/selfie_lib/__init__.py +++ b/python/selfie-lib/selfie_lib/__init__.py @@ -6,6 +6,7 @@ from .CacheSelfie import cache_selfie_binary as cache_selfie_binary from .CacheSelfie import cache_selfie_json as cache_selfie_json from .CommentTracker import CommentTracker as CommentTracker +from .EscapeLeadingWhitespace import EscapeLeadingWhitespace as EscapeLeadingWhitespace from .FS import FS as FS from .Lens import Camera as Camera from .Lens import CompoundLens as CompoundLens diff --git a/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py b/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py new file mode 100644 index 00000000..cb324af7 --- /dev/null +++ b/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py @@ -0,0 +1,29 @@ +from selfie_lib import EscapeLeadingWhitespace + +def test_detection(): + # not enough to detect + assert EscapeLeadingWhitespace.appropriate_for("") == EscapeLeadingWhitespace.ALWAYS + assert EscapeLeadingWhitespace.appropriate_for("abc") == EscapeLeadingWhitespace.ALWAYS + assert EscapeLeadingWhitespace.appropriate_for("abc\nabc") == EscapeLeadingWhitespace.ALWAYS + + # all spaces -> only tabs need escape + assert EscapeLeadingWhitespace.appropriate_for(" ") == EscapeLeadingWhitespace.ONLY_ON_TAB + assert EscapeLeadingWhitespace.appropriate_for(" ") == EscapeLeadingWhitespace.ONLY_ON_TAB + assert EscapeLeadingWhitespace.appropriate_for(" \n ") == EscapeLeadingWhitespace.ONLY_ON_TAB + + # all tabs -> only space needs escape + assert EscapeLeadingWhitespace.appropriate_for("\t") == EscapeLeadingWhitespace.ONLY_ON_SPACE + assert EscapeLeadingWhitespace.appropriate_for("\t\t") == EscapeLeadingWhitespace.ONLY_ON_SPACE + assert EscapeLeadingWhitespace.appropriate_for("\t\n\t") == EscapeLeadingWhitespace.ONLY_ON_SPACE + + # it's a mess -> everything needs escape + assert EscapeLeadingWhitespace.appropriate_for("\t\n ") == EscapeLeadingWhitespace.ALWAYS + + # single spaces and tabs -> only tabs need escape + test_string = """/* + * Copyright + */ +interface Foo { +\tfun bar() +}""" + assert EscapeLeadingWhitespace.appropriate_for(test_string) == EscapeLeadingWhitespace.ALWAYS \ No newline at end of file diff --git a/python/selfie-lib/uv.lock b/python/selfie-lib/uv.lock index afec8460..84c8762a 100644 --- a/python/selfie-lib/uv.lock +++ b/python/selfie-lib/uv.lock @@ -112,7 +112,7 @@ wheels = [ [[package]] name = "selfie-lib" -version = "1.0.0" +version = "1.0.1.dev0" source = { virtual = "." } [package.dev-dependencies] From 7b399bb01cca6800a5df801a7363eaa0564df42d Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 31 Dec 2024 16:09:30 -0800 Subject: [PATCH 3/7] Fix the same issue in the python. --- python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py b/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py index b2e9695d..21865815 100644 --- a/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py +++ b/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py @@ -34,7 +34,7 @@ def appropriate_for(cls, file_content: str) -> "EscapeLeadingWhitespace": for line in file_content.splitlines(): whitespace = "".join(c for c in line if c.isspace()) - if not whitespace: + if whitespace == "" or whitespace == " ": continue elif all(c == " " for c in whitespace): whitespace = " " From 023eba90ef0d8a0735d9688467d0f9709047c350 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 31 Dec 2024 16:10:55 -0800 Subject: [PATCH 4/7] And verify in the python test. --- .../tests/EscapeLeadingWhitespace_test.py | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py b/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py index cb324af7..87c8fc97 100644 --- a/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py +++ b/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py @@ -1,29 +1,58 @@ from selfie_lib import EscapeLeadingWhitespace + def test_detection(): # not enough to detect assert EscapeLeadingWhitespace.appropriate_for("") == EscapeLeadingWhitespace.ALWAYS - assert EscapeLeadingWhitespace.appropriate_for("abc") == EscapeLeadingWhitespace.ALWAYS - assert EscapeLeadingWhitespace.appropriate_for("abc\nabc") == EscapeLeadingWhitespace.ALWAYS + assert ( + EscapeLeadingWhitespace.appropriate_for("abc") == EscapeLeadingWhitespace.ALWAYS + ) + assert ( + EscapeLeadingWhitespace.appropriate_for("abc\nabc") + == EscapeLeadingWhitespace.ALWAYS + ) # all spaces -> only tabs need escape - assert EscapeLeadingWhitespace.appropriate_for(" ") == EscapeLeadingWhitespace.ONLY_ON_TAB - assert EscapeLeadingWhitespace.appropriate_for(" ") == EscapeLeadingWhitespace.ONLY_ON_TAB - assert EscapeLeadingWhitespace.appropriate_for(" \n ") == EscapeLeadingWhitespace.ONLY_ON_TAB + assert ( + EscapeLeadingWhitespace.appropriate_for(" ") == EscapeLeadingWhitespace.ALWAYS + ) + assert ( + EscapeLeadingWhitespace.appropriate_for(" ") + == EscapeLeadingWhitespace.ONLY_ON_TAB + ) + assert ( + EscapeLeadingWhitespace.appropriate_for(" \n ") + == EscapeLeadingWhitespace.ONLY_ON_TAB + ) # all tabs -> only space needs escape - assert EscapeLeadingWhitespace.appropriate_for("\t") == EscapeLeadingWhitespace.ONLY_ON_SPACE - assert EscapeLeadingWhitespace.appropriate_for("\t\t") == EscapeLeadingWhitespace.ONLY_ON_SPACE - assert EscapeLeadingWhitespace.appropriate_for("\t\n\t") == EscapeLeadingWhitespace.ONLY_ON_SPACE + assert ( + EscapeLeadingWhitespace.appropriate_for("\t") + == EscapeLeadingWhitespace.ONLY_ON_SPACE + ) + assert ( + EscapeLeadingWhitespace.appropriate_for("\t\t") + == EscapeLeadingWhitespace.ONLY_ON_SPACE + ) + assert ( + EscapeLeadingWhitespace.appropriate_for("\t\n\t") + == EscapeLeadingWhitespace.ONLY_ON_SPACE + ) # it's a mess -> everything needs escape - assert EscapeLeadingWhitespace.appropriate_for("\t\n ") == EscapeLeadingWhitespace.ALWAYS + assert ( + EscapeLeadingWhitespace.appropriate_for("\t\n ") + == EscapeLeadingWhitespace.ALWAYS + ) # single spaces and tabs -> only tabs need escape - test_string = """/* + test_string = f"""/* * Copyright */ -interface Foo { -\tfun bar() -}""" - assert EscapeLeadingWhitespace.appropriate_for(test_string) == EscapeLeadingWhitespace.ALWAYS \ No newline at end of file +interface Foo [ +{'\t'} bar() +]""" + assert ( + EscapeLeadingWhitespace.appropriate_for(test_string) + == EscapeLeadingWhitespace.ALWAYS + ) From 33a7a48ee01fd9546c4c1e81c3820c6631561296 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 31 Dec 2024 16:11:03 -0800 Subject: [PATCH 5/7] Update python changelog. --- python/CHANGELOG.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index 46e4c391..4e489852 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -2,18 +2,16 @@ Changelog for the selfie Python libraries. -- [`com.diffplug.selfie:selfie-lib:VERSION`](https://pypi.org/project/selfie-lib/) -- [`com.diffplug.selfie:selfie-runner-pytest:VERSION`](https://pypi.org/project/pytest-selfie/) - 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). +- [`selfie-lib:VERSION`](https://pypi.org/project/selfie-lib/) +- [`pytest-selfie:VERSION`](https://pypi.org/project/pytest-selfie/) + +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). + +Allowable headings are `Added`, `Fixed`, and `Changed`. ## [Unreleased] -### Added -- TODO ### Fixed -- TODO -### Changed -- TODO +- 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)) ## [1.0.0] - 2024-12-16 From 7089502403ca309e1784e0574601ad1759666ed2 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 31 Dec 2024 16:39:06 -0800 Subject: [PATCH 6/7] Fix a bug in the python leading whitespace calculation. --- python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py | 2 +- python/selfie-lib/tests/EscapeLeadingWhitespace_test.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py b/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py index 21865815..840d38f6 100644 --- a/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py +++ b/python/selfie-lib/selfie_lib/EscapeLeadingWhitespace.py @@ -33,7 +33,7 @@ def appropriate_for(cls, file_content: str) -> "EscapeLeadingWhitespace": common_whitespace = None for line in file_content.splitlines(): - whitespace = "".join(c for c in line if c.isspace()) + whitespace = line[0 : len(line) - len(line.lstrip())] if whitespace == "" or whitespace == " ": continue elif all(c == " " for c in whitespace): diff --git a/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py b/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py index 87c8fc97..d52b611c 100644 --- a/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py +++ b/python/selfie-lib/tests/EscapeLeadingWhitespace_test.py @@ -46,13 +46,14 @@ def test_detection(): ) # single spaces and tabs -> only tabs need escape + tab = "\t" test_string = f"""/* * Copyright */ interface Foo [ -{'\t'} bar() +{tab}bar() ]""" assert ( EscapeLeadingWhitespace.appropriate_for(test_string) - == EscapeLeadingWhitespace.ALWAYS + == EscapeLeadingWhitespace.ONLY_ON_SPACE ) From a638736f219512df525a23160e946bd0a1eef631 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 31 Dec 2024 16:42:28 -0800 Subject: [PATCH 7/7] It's 2025 on the CI server. --- .../kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt | 2 +- .../com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt b/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt index 9eb5effe..04f154d3 100644 --- a/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt +++ b/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespace.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 DiffPlug + * Copyright (C) 2024-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt b/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt index 52928804..36943703 100644 --- a/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt +++ b/jvm/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/guts/EscapeLeadingWhitespaceTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 DiffPlug + * Copyright (C) 2024-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.