From 2a2995ca835852adb5f9e0a383a45c887f206df6 Mon Sep 17 00:00:00 2001 From: thodson-usgs Date: Tue, 1 Sep 2026 13:00:40 -0500 Subject: [PATCH 1/2] chore: catch doubled words in pre-commit A mechanical rename left "a label label" in a docstring on a branch this week. Tests, `mypy --strict`, ruff and an AST comparison all passed over it: the AST check strips docstrings before comparing, so it is silent by construction about the text a rename most easily breaks. Only reading the diff found it. A doubled word is the mechanical signature of that mistake -- a regex that rewrites one word of a phrase leaves its neighbour standing -- and it is cheap to check. Measured before adding, since a gate that mostly cries wolf is worse than none: across 168 tracked text files the pattern reports **zero** false positives. Restricting the word to `[A-Za-z]+` is what buys that; `\w+` also matches RDB column types like `10n 10n`. Its first real run found a typo in a user-facing demo notebook -- "Filters on the the associated monitoring location" -- which had been shipped and read past. The one other hit was a genuinely clumsy sentence in a test docstring ("Which variable that is is platform-specific"), reworded rather than excluded, so the hook needs no exclusion list beyond `tests/data/`, which the other prose hooks already skip because it holds byte-exact API captures. Line-scoped on purpose. Matching across newlines too was tried and rejected: it finds nothing real and reports a Markdown heading followed by its own first word, or `return df` above `df` -- 19 such reports against 0 real ones. Confirmed it can fail. Seeding a doubled word makes the hook exit 1 and name the file and line; the check was run against that seed before being kept. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01JAEQqs7XzQHGQQi2KakuXD --- .pre-commit-config.yaml | 18 ++++++++++++++++++ .../USGS_WaterData_Samples_Examples.ipynb | 2 +- tests/configuration_test.py | 6 +++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ae925227..0952be4eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,6 +20,24 @@ repos: - id: check-yaml - id: debug-statements + # A doubled word is almost always a slip, and mechanical renames are how they + # get in: a regex that rewrites one word of a phrase can leave its neighbour + # standing, so "a source label" ends up naming the same noun twice. Nothing + # else here reads prose, and AST-level checks strip docstrings before + # comparing, which is exactly where such a slip hides. + # + # Line-scoped on purpose. Matching across a newline as well finds nothing new + # in practice and reports a heading followed by its own first word, or + # ``return df`` above ``df``: 19 such reports against 0 real ones. + - repo: local + hooks: + - id: doubled-word + name: doubled word + language: pygrep + entry: \b([A-Za-z]+)[ \t]+\1\b + types: [text] + exclude: ^tests/data/ + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.16.1 hooks: diff --git a/demos/hydroshare/USGS_WaterData_Samples_Examples.ipynb b/demos/hydroshare/USGS_WaterData_Samples_Examples.ipynb index c7b90846a..804b88a7c 100644 --- a/demos/hydroshare/USGS_WaterData_Samples_Examples.ipynb +++ b/demos/hydroshare/USGS_WaterData_Samples_Examples.ipynb @@ -100,7 +100,7 @@ " A user supplied characteristic name describing one or more results.\n", " Use `get_codes(code_service=\"observedproperty\")` for all possible inputs.\n", "* **boundingBox**: list of four floats, optional\n", - " Filters on the the associated monitoring location's point location\n", + " Filters on the associated monitoring location's point location\n", " by checking if it is located within the specified geographic area. \n", " The logic is inclusive, i.e. it will include locations that overlap\n", " with the edge of the bounding box. Values are separated by commas,\n", diff --git a/tests/configuration_test.py b/tests/configuration_test.py index c0b6775e3..891114fe8 100644 --- a/tests/configuration_test.py +++ b/tests/configuration_test.py @@ -1025,9 +1025,9 @@ def test_broken_config_does_not_break_unrelated_services(config_file): def test_default_config_path_follows_a_changed_home(tmp_path, monkeypatch): """The default path derives from the home variable, so the memo watches it. - Which variable that is is platform-specific: ``ntpath.expanduser`` reads - ``USERPROFILE`` and ignores ``HOME``, so setting ``HOME`` on Windows moves - nothing and this asserted against the runner's real home directory. + Which variable that is depends on the platform: ``ntpath.expanduser`` + reads ``USERPROFILE`` and ignores ``HOME``, so setting ``HOME`` on Windows + moves nothing and this asserted against the runner's real home directory. """ home_var = "USERPROFILE" if os.name == "nt" else "HOME" monkeypatch.delenv(configuration.CONFIG_PATH_ENV, raising=False) From 0058f80b3903cdf3d9f40bd4a5f66cfb68921e98 Mon Sep 17 00:00:00 2001 From: Timothy Hodson <34148978+thodson-usgs@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:20:28 -0500 Subject: [PATCH 2/2] Update .pre-commit-config.yaml --- .pre-commit-config.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0952be4eb..d6e5562b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,15 +20,6 @@ repos: - id: check-yaml - id: debug-statements - # A doubled word is almost always a slip, and mechanical renames are how they - # get in: a regex that rewrites one word of a phrase can leave its neighbour - # standing, so "a source label" ends up naming the same noun twice. Nothing - # else here reads prose, and AST-level checks strip docstrings before - # comparing, which is exactly where such a slip hides. - # - # Line-scoped on purpose. Matching across a newline as well finds nothing new - # in practice and reports a heading followed by its own first word, or - # ``return df`` above ``df``: 19 such reports against 0 real ones. - repo: local hooks: - id: doubled-word