Skip to main content

Add my “fix Scala imports” script

ID
4d0a2f3
date
2022-03-21 08:12:07+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
9dc0b0a
message
Add my "fix Scala imports" script
changed files
1 file, 104 additions

Changed files

fix_scala_imports (0) → fix_scala_imports (3011)

diff --git a/fix_scala_imports b/fix_scala_imports
new file mode 100755
index 0000000..370ada5
--- /dev/null
+++ b/fix_scala_imports
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+"""
+This is to reduce diff churn in the Scala code I write at Wellcome.
+
+The autoformatter that runs against PRs prefers long imports to be split
+across multiple lines, e.g.
+
+    import weco.messaging.typesafe.{
+      AlpakkaSqsWorkerConfigBuilder,
+      SNSBuilder,
+      SQSBuilder
+    }
+
+but whenever IntelliJ touches imports, it "fixes" them onto a single line:
+
+    import weco.messaging.typesafe.{AlpakkaSqsWorkerConfigBuilder, SNSBuilder, SQSBuilder}
+
+This churn makes diffs harder to follow; this script splits imports back
+onto multiple lines.
+
+*   Why not run autoformatting locally?
+    Because Scala autoformatting is sloooooow and this is the main thing
+    I usually want to fix.
+
+*   Why not fix your IntelliJ settings?
+    Because I have no idea how to; writing a simple text editing script
+    was a time-bounded exercise in a way that editing IntelliJ settings isn't.
+
+"""
+
+
+import os
+import re
+import subprocess
+
+
+def git(*cmd):
+    return subprocess.check_output(["git"] + list(cmd)).decode("utf8").strip()
+
+
+def changed_files():
+    root = git("rev-parse", "--show-toplevel")
+    for cmd in [
+        ["diff", "--name-only"],
+        ["diff", "--name-only", "--cached"],
+    ]:
+        for relpath in git(*cmd).split():
+            yield os.path.join(root, relpath)
+
+
+if __name__ == "__main__":
+    for path in changed_files():
+        print(f"Editing {path}")
+        try:
+            with open(path) as srcfile:
+                src_lines = list(srcfile)
+        except FileNotFoundError:
+            continue
+
+        while True:
+            has_edits = False
+
+            for lineno, line in enumerate(src_lines):
+                if not line.startswith("import"):
+                    continue
+
+                if len(line.strip()) <= 80:
+                    continue
+
+                if "{" not in line:
+                    continue
+
+                if line.strip().endswith("{"):
+                    continue
+
+                m = re.match(
+                    r"^import (?P<package>[a-zA-Z0-9\._]+)\{(?P<imports>[a-zA-Z0-9, ]+)\}",
+                    line,
+                )
+                if m is None:
+                    print("???", line)
+                    continue
+                assert m is not None, (path, lineno, line)
+
+                package = m.groupdict()["package"]
+                imports = [im.strip() for im in m.groupdict()["imports"].split(",")]
+
+                src_lines[lineno] = f"import {package}{{\n"
+                for idx, im in enumerate(imports, start=1):
+                    if idx == len(imports):
+                        src_lines.insert(lineno + idx, f"  {im}\n")
+                    else:
+                        src_lines.insert(lineno + idx, f"  {im},\n")
+
+                src_lines.insert(lineno + len(imports) + 1, "}\n")
+
+                has_edits = True
+                break
+
+            if not has_edits:
+                break
+
+        with open(path, "w") as srcfile:
+            srcfile.write("".join(src_lines))