Repository: codespell-project/codespell

Issue:
Support inline ignores
I'd like to use codespell as part of an automated check workflow.  For that I need to be able to ignore certain cases on a per instance bases.

Here's an example.  In the code base there is a command called 'Trim All'.  The comment contains a statement like:
// Track how many Trim Alls have been issued.

codespell finds Alls as a possible error.  

I don't want to add 'Alls' to the ignore words list as it could be a real error in other cases.  But I don't want this message generated on every run either.
The ignore word list only supports single words So I can't do 'Trim Alls' as an ignore.

Some sort of  in line keystring that allows the next occurrence of a specific word error would be useful.  Most code formatters, pretty-printers, and static analysis tools have some sort of inline // disable-whatever  option that allows you to skip the trigger on a per instance basis.





Discussion:
Or something like pylint does where you say
/* codespell: ignore <expression|list of words> */ with a matching "find" or "unignore" comment
This is a must when using codespell with a large multi-dev project, because you don't want to then alter the whitelist for every individual thing and what is correct in some contexts is not correct in others, so similar to pylint, flake8, etc adding a inline comment that tells codespell to ignore that particular line would be very welcome!
What about extending the ignore file format to have a second column restricting the entry to being used for the given file or, better, giving line or range of lines in the given file? Sometimes you can't modify the sources themselves, but you can always add a file with codespell exceptions.
In my case I don't want to change some project level thing for my one file that needs an exclusion and where in some contexts I need the exception, while in other contexts it doesn's and should be flagged, so just the single line to be ignored would be far the easiest then, my case is with [Home Assistant](/home-assistant/core), where all the integrations are created independently of the main but there is a pre-commit hook with codespell, that shouldn't be updated by individual developers working on single things, but mine failed because I had a field coming from a external service that I had to check against which was flagged by codespell, I ended up having to move the checking list to a seperate json file (which were excluded by the project level codespell config) but it made my code more complex (reading a external file, instead of just a constant in a dict). 
The `--exclude-file/-x` option already has the ability to list lines to ignore (although in a separate file, rather than decorating the line).

For example:
https://github.com/OpenLightingProject/ola/blob/master/.codespellignorelines
Thanks for the `-x` option hint! I've totally missed it and it's perfect for my needs.
Any updates on this? 
> Any updates on this?

If `--exclude-file/-x` doesn't suit your usage @odashi , then someone still needs to propose a format (either a comment at the end of a line), or a comment on the line before, either with one syntax that works on multiple programming languages, or more likely a variable prefix (e.g. # or // or whatever and then a static match phrase).

Then someone (not necessarily the same person) will need to implement it.

Suggestions and pull requests welcome.
Maybe someone can tell me how to avoid codespell yelling about that python line `KEGEX = r"^st(?:dout)?$"` -- `dout` being the problem. Am I supposed to skip entire file because of this? Sounds like using a BFG to kill a moskito.
I have a test with following chunked encoded packet.  It thinks `nd` is not spelled correctly.  Should I ignore the entire test file?

`b'HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n5\r\n Worl\r\n1\r\nd\r\n0\r\n\r\n',`

Reference: https://github.com/abhinavsingh/proxy.py/pull/1064/files#diff-fc90f112bb0886415d939834b630f798e633c63c977a60a69b60b8fd7fcecd42R94
> I have a test with following chunked encoded packet. It thinks `nd` is not spelled correctly. Should I ignore the entire test file?
> 
> `b'HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n5\r\n Worl\r\n1\r\nd\r\n0\r\n\r\n',`
> 
> Reference: https://github.com/abhinavsingh/proxy.py/pull/1064/files#diff-fc90f112bb0886415d939834b630f798e633c63c977a60a69b60b8fd7fcecd42R94

Quickest hack is to add following at top of the file:

```python

    .. spelling::

       nd
```
So @ssbarnea and @abhinavsingh (and others on this thread), you've currently got a few options, in rough order from simplest, but biggest impact on finding other typos through the most complicated but restricted.

1. Skip the entire file with `--skip`
2. Skip the relevant line(s) with `--exclude-file`
3. Skip the specific typo with `--ignore-words-list` (e.g. dout or nd)
4. Hide the typo with `--ignore-regex` (possibly better for @abhinavsingh case)

> Quickest hack is to add following at top of the file:
> 
> ```python
>     .. spelling::
> 
>        nd
> ```

I'm not aware of that syntax, is it specific to Jupyter notebooks?
@peternewman No I should have came back to fix that.  I mixed it with `sphinx` spell check and added a directive for that instead 🙆‍♂️.  Finally, I had to add the exclude directives for all `.ipynb`

See https://github.com/abhinavsingh/proxy.py/blob/develop/.pre-commit-config.yaml#L128-L133
> 4\. Hide the typo with `--ignore-regex` (possibly better for @abhinavsingh case)

This will be much better.  Will likely fix it in coming days.
> @peternewman No I should have came back to fix that. I mixed it with `sphinx` spell check and added a directive for that instead

Ah that makes sense.

> This will be much better. Will likely fix it in coming days.

I'm glad that's an improvement for you.

I'd still like to have inline waivers. I have one single place in one single file where I legitimately want to use the string "ERRO". I don't want to skip the file, because it might have other problems in it later. I don't want to allowlist that word, because if it appears anywhere else it's probably a mistake. I just want codespell to trust me that I know what I'm doing in this one particular case.
I think I need inline waivers because I have contributed code to a large open source project, and am blocked from committing documentation for it because codespell thinks my attribute `.aks` should be "ask".  It seems to me that defining how the pre-commit hook works is a role for the project maintainers, rather than every developer, and my spelling quirk should not necessarily be applied across the entie project - or even my entire file as I may make typos as well.

How about

    * <b>codespell:ignore [<n>]

That is, a line possibly containing other text (e.g. comment characters for whatever language), followed by at least one whitespace, followed by the string `codespell:ignore` to cause the current line to be ignored.  The optional integer <n> parameter would specify the number of succeeding lines to also ignore spelling on.

Additionally one could envision

    * codespell:ignore <string>[, <string> ...]

As a way to specify strings to ignore in the current file from this line on.

very needed feature 

Resolving patch:
diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py
--- a/codespell_lib/_codespell.py
+++ b/codespell_lib/_codespell.py
@@ -55,6 +55,7 @@
 # Pass all misspellings through this translation table to generate
 # alternative misspellings and fixes.
 alt_chars = (("'", "’"),)  # noqa: RUF001
+inline_ignore_regex = re.compile(r"[^\w\s]\s?codespell:ignore\b(\s+(?P<words>[\w,]*))?")
 USAGE = """
 \t%prog [OPTIONS] [file1 file2 ... fileN]
 """
@@ -974,6 +975,15 @@ def parse_file(
         if line.rstrip() in exclude_lines:
             continue
 
+        extra_words_to_ignore = set()
+        match = inline_ignore_regex.search(line)
+        if match:
+            extra_words_to_ignore = set(
+                filter(None, (match.group("words") or "").split(","))
+            )
+            if not extra_words_to_ignore:
+                continue
+
         fixed_words = set()
         asked_for = set()
 
@@ -998,7 +1008,7 @@ def parse_file(
             if word in ignore_words_cased:
                 continue
             lword = word.lower()
-            if lword in misspellings:
+            if lword in misspellings and lword not in extra_words_to_ignore:
                 # Sometimes we find a 'misspelling' which is actually a valid word
                 # preceded by a string escape sequence.  Ignore such cases as
                 # they're usually false alarms; see issue #17 among others.


Test patch:
diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py
--- a/codespell_lib/tests/test_basic.py
+++ b/codespell_lib/tests/test_basic.py
@@ -391,6 +391,67 @@ def test_ignore_word_list(
     assert cs.main("-Labandonned,someword", "-Labilty", tmp_path) == 1
 
 
+@pytest.mark.parametrize(
+    ("content", "expected_error_count"),
+    [
+        # recommended form
+        ("abandonned abondon abilty  # codespell:ignore abondon", 2),
+        ("abandonned abondon abilty  // codespell:ignore abondon,abilty", 1),
+        (
+            "abandonned abondon abilty  /* codespell:ignore abandonned,abondon,abilty",
+            0,
+        ),
+        # ignore unused ignore
+        ("abandonned abondon abilty  # codespell:ignore nomenklatur", 3),
+        # wildcard form
+        ("abandonned abondon abilty  # codespell:ignore ", 0),
+        ("abandonned abondon abilty  # codespell:ignore", 0),
+        ("abandonned abondon abilty  # codespell:ignore\n", 0),
+        ("abandonned abondon abilty  # codespell:ignore\r\n", 0),
+        ("abandonned abondon abilty  # codespell:ignore  # noqa: E501\n", 0),
+        ("abandonned abondon abilty  # codespell:ignore # noqa: E501\n", 0),
+        ("abandonned abondon abilty  # codespell:ignore# noqa: E501\n", 0),
+        ("abandonned abondon abilty  # codespell:ignore, noqa: E501\n", 0),
+        ("abandonned abondon abilty  #codespell:ignore\n", 0),
+        # ignore these for safety
+        ("abandonned abondon abilty  # codespell:ignorenoqa: E501\n", 3),
+        ("abandonned abondon abilty  codespell:ignore\n", 3),
+        ("abandonned abondon abilty codespell:ignore\n", 3),
+        # ignore these as they aren't valid
+        ("abandonned abondon abilty  # codespell:igore\n", 4),
+        # showcase different comment markers
+        ("abandonned abondon abilty ' codespell:ignore\n", 0),
+        ('abandonned abondon abilty " codespell:ignore\n', 0),
+        ("abandonned abondon abilty ;; codespell:ignore\n", 0),
+        ("abandonned abondon abilty /* codespell:ignore */\n", 0),
+        # prose examples
+        (
+            "You could also use line based igore ( codespell:ignore ) to igore ",
+            0,
+        ),
+        ("You could also use line based igore (codespell:ignore) to igore ", 0),
+        (
+            "You could also use line based igore (codespell:ignore igore) to igore ",
+            0,
+        ),
+        (
+            "You could also use line based igore (codespell:ignore igare) to igore ",
+            2,
+        ),
+    ],
+)
+def test_inline_ignores(
+    tmpdir: pytest.TempPathFactory,
+    capsys: pytest.CaptureFixture[str],
+    content: str,
+    expected_error_count: int,
+) -> None:
+    d = str(tmpdir)
+    with open(op.join(d, "bad.txt"), "w") as f:
+        f.write(content)
+    assert cs.main(d) == expected_error_count
+
+
 def test_custom_regex(
     tmp_path: Path,
     capsys: pytest.CaptureFixture[str],