Linting

Ur lints every language it reads with the same rules. It lowers each file to one intermediate form and normalizes it, under the rules of the language version the project targets, so code that does the same thing comes out the same however it is written. Its clone engine fingerprints that normal form to find every copy, and the other rules read the same form.

Checking a project

ur check

ur check lints the directory it is given, the current one by default, and reports the findings in the files changed since its last clean check, so a check after a small edit is quick. --all reports the findings in every file, and --fix writes the fixes of findings that have one instead of reporting them.

Ur caches what it learns about each file by its content, in a store every checkout shares, so later runs redo the work only for files that changed, and a copy of a checkout reuses the work done in the original.

ur lint takes files and directories and reports every finding in them, without comparing with an earlier check:

ur lint src tests
ur lint --rule semantic-clone .

--rule runs only the rules it names, and --rules lists every rule with the instruction it gives. --format sarif --output ur.sarif writes the report as SARIF, for code scanning. Both commands exit with status 1 when they report a finding, so either can fail a CI job.

The rules

The rules fall into groups:

Each rule’s page says what it reports and why, whether it can fix its findings, and shows an example with what Ur prints for it.

Optional rules

Some rules are off by default, for the projects that want them: the text rules, no-comments, and hand-written-dual. A project turns one on in its ur.toml, with no-comments = true under [lint] or a table for a text rule, such as [lint.long-file], as the configuration reference gives. --rule runs one for a single run:

ur lint --rule stray-todo --rule emoji .

Fixing

ur check --fix and ur lint --fix write the fixes of the findings that have one, such as an expression replaced with its answer, then report the findings left. ur lint --undo puts back the files the last --fix changed, except those changed since.

Accepting a finding

An ur-allow comment above a finding’s location accepts it, with an optional reason after the rule’s name:

# ur-allow: semantic-clone kept apart until the old API is removed
def legacy_total(items):
    ...

ur lint --suppress writes such a comment above each location of every finding left, which accepts a project’s findings as they stand so that only new ones are reported. ur lint --suppressions lists the findings the comments accept, and Ur reports a comment that no longer suppresses anything.

Opportunities

ur report

ur report lists code that could be shared, ranked by how many lines sharing it would save: blocks that differ only in a few names or literals, recursive walks written out case by case, the same nested constructor calls at many sites, the same list of names in several places, and many ur-allow comments on code of one shape. The report is advisory and never fails.

Coding agents

ur init makes ur check each coding agent’s stop hook, so an agent keeps working until the check is clean. Coding agents says how.