Skip to content

[checks] Implement Python checks and fix some violations - #9146

Open
eisenwave wants to merge 24 commits into
cplusplus:mainfrom
eisenwave:python-checks
Open

[checks] Implement Python checks and fix some violations#9146
eisenwave wants to merge 24 commits into
cplusplus:mainfrom
eisenwave:python-checks

Conversation

@eisenwave

@eisenwave eisenwave commented Jun 17, 2026

Copy link
Copy Markdown
Member

Fixes #8086

I've mentioned the option to improve the checks in Croydon to @tkoeppe, and he seemed generally open to the idea.

grafik

In summary, this PR converts all the checks we have in check-source.sh (plus some additional once adopted from check-output.sh) and turns them into a Python script. The benefits are:

  • Much nicer formatting for errors. You get VSCode-friendly source locations that you can use to navigate directly to the line and column in an editor. You also get a citation of the source with the affected column span highlighted, so you're not left guessing where the error is located.
  • No more hieroglyphic sed and grep piping. The code is relatively simple Python. It still uses a bunch of regular expressions for a lot of checks; the greatest benefits are in the more complex checks.
  • Ability to write test cases for checks using EXPECTCHECKNEXTLINE.
  • Ability to enable and disable individual checks for source spans using NOCHECKBEGIN and other directives. This makes it possible for us to add much more aggressive checks that would have previously been infeasible because they generate too much noise in old TeX sources.
    • A good example of that is in this PR, where I disable the check for \tcode{\exposid{...}} in a part of numerics.tex because there are too many violations.
    • We could also use this to e.g. disallow use of \textit in "newer" TeX ranges for example without having to fix every single violation. We could also use it to catch many "bad words" or "bad phrases" that appear too often to be checked right now, but that we don't want perpetuated.
    • We could also use this to enforce a little bit of auto-enforcement of semantic line breaks (such as requiring a new line after a sentence period) without needing to fix the countless existing violations.
  • With check suppression, it is possible to suppress checks that would have inevitably failed during motions. For example, if there is a dangling \ref to a section that is introduced by another paper, you can still get a green build by suppressing the \ref check (that is, at least for the check-source checks).

There are a few notable new checks that make things a lot easier in the future:

  1. base-env-balancing checks for balancing of \begin and \end commands. It's quite easy to get it wrong, and when you do, it tends to blow up after minutes during the TeX build in bizarre and hard-to-debug ways.
  2. base-unknown-command checks for use of TeX commands that have never appeared before in the code base. It's very unlikely that this is intentional in most PRs, and very likely that someone just wrote \tocde instead of \tcode or something else stupid. If we need a new TeX command, we can just add it to the list.

@eisenwave
eisenwave requested a review from jensmaurer June 17, 2026 18:28
@eisenwave

Copy link
Copy Markdown
Member Author

Some more neat examples:

grafik grafik grafik grafik

@eisenwave

Copy link
Copy Markdown
Member Author

Latest innovation:
grafik

@eisenwave eisenwave changed the title Implement Python checks and fix some violations [checks] Implement Python checks and fix some violations Jun 17, 2026
Comment thread tools/test-checks.tex Outdated
Comment thread .gitignore
Comment thread pyrightconfig.json Outdated
Comment on lines +24 to +30
- name: setup Node
uses: actions/setup-node@v6
with:
node-version: "20"

- name: install pyright
run: npm install -g pyright

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure pyright is a good call. There are other alternatives that do not require pulling in node as dependency.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just use it because it integrates nicely with VSCode out of the box. What would you recommend instead?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I (still) tend to go for mypy for my own projects, but if this gives you a better user experience I'm okay with it.

Comment thread tools/check-source.py Outdated
Comment thread tools/check-source.py
message: str,
) -> None:
"""Report a failure at `self.file_path`."""
emit_check_failure(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emit_check_failure is only called from within classes deriving from Check. Can you fold it into fail and consistently use this one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the changes in 7af3d56, I wouldn't want to. The difference is that fail is just a convenience function that provides the file path automatically, and I would want there to be a global "emit failure" function at some level, in case we expand the sources of failures in the future.

emit_check_failure is basically the global console.error counterpart, and that has kind of a right to exist even if you always use it via a wrapper. That might change at some point.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. Have you considered a configured global logger for this instead? Python's logging package is very useful.

Comment thread tools/check-source.py Outdated
Comment thread tools/check-source.py Outdated
Comment thread tools/check-source.py Outdated
Comment thread tools/check-source.py Outdated
@eisenwave

Copy link
Copy Markdown
Member Author

@Tsche thanks a lot for the review. It'll take some time to clean it all up.

Comment thread source/numerics.tex Outdated
Comment thread source/numerics.tex
@eisenwave

Copy link
Copy Markdown
Member Author

All the pre-existing issues are taken care of in separate PRs. Now comes rebasing on the latest motions and addressing all the feedback on the Python script.

@Tsche Tsche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a brief look at this again. Please note that some of the comments apply to multiple locations - I did not repeat them.

Comment thread source/intro.tex

\definition{implementation-defined strict total order over pointers}
{defns.order.ptr}
\definition{implementation-defined strict total order over pointers}{defns.order.ptr}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like an unrelated change, does it have to be in this patch?

Comment thread pyrightconfig.json
"reportUnknownParameterType": false,
"reportUnusedImport": true,
"reportUnusedVariable": true,
"pythonVersion": "3.12"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any particular reason why this requires Python 3.12 rather than the current version (3.14)?

Comment thread tools/check-source.py
from data import *

# ===============================================================================
# Terminal color support

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please get rid of these section headers

Comment thread tools/check-source.py
Comment on lines +1248 to +1250
# Active-set management — controls whether check_line is called.
active: set[str] = set(all_ids)
# Per-check next-line skips: check_id → set of line numbers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please get rid of the LLMy em dashes and other unicode symbols (such as →) in comments. This might cause rendering issues with some editors and environments, I don't see a need to use anything but the ASCII-compatible subset here (even though Python assumes UTF-8 anyway).

Comment thread tools/check-source.py
Comment on lines +948 to +958
# -- Text checks -------------------------------------------------------------------------------
# Such checks run on all files and identify problems like illegal characters,
# trailing whitespace, etc.
# ----------------------------------------------------------------------------------------------
NonAsciiCheck("text-non-ascii-char"),
BannedPatternCheck(
"text-trailing-ws",
re.compile(r"\s+$"),
"Line has trailing whitespace; remove the extra spaces.",
),
TrailingEmptyLinesCheck("text-trailing-empty-lines"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you push those groups of checks into some CheckGroup object and attach the comment/name to it as strings?

That way you can reuse this for the command line interface (or well, its help text) to group checks and print a short description/name for those groups.

Comment thread tools/check-source.py
message: str,
) -> None:
"""Report a failure at `self.file_path`."""
emit_check_failure(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. Have you considered a configured global logger for this instead? Python's logging package is very useful.

Comment thread tools/check-source.py
help="Specific .tex files or directories to check (default: all .tex files"
" under the project root).",
)
args = parser.parse_args()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a command/argument that just prints all checks and exits. Alternatively you could put this into the help text, but I'd prefer to (at least additionally) have a command.

If you do the check grouping thing I suggested this would allow filtering checks by group etc.

Comment thread tools/check-source.py


def main() -> None:
parser = argparse.ArgumentParser(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make it configurable which checks are performed? This could be useful to debug a specific failure.

Comment thread tools/check-source.py
Comment on lines +1403 to +1404
exit_code = num_failures > 1
sys.exit(exit_code)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exit_code seems extraneous. Please fold this into the sys.exit invocation.

Comment thread tools/check-source.py

for file_path in tex_files:
lines = read_file(file_path)
for idx in range(len(lines) - 1):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about
for idx, line in enumerate(lines[:-1]):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[checks] Create a better check-source.sh in Python

3 participants