Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The format of this changelog is based on [Keep a Changelog](https://keepachangel
## [Unreleased]

### Added
- Detection of linter, formatter and type checker configuration files and their ignore counterparts: 30 tools across JavaScript, TypeScript, CSS, Python, Go, Ruby, Java, Groovy, C#/.NET and SQL, covering 96 exact filenames. Every convention was verified against the tool's current official documentation; see [`docs/linter-detection-rules.md`](https://panopticas.io/linter-detection-rules) for the inventory, the sources, and the candidates that were investigated and rejected.
- Detection of linter, formatter and type checker configuration files and their ignore counterparts: 31 tools across JavaScript, TypeScript, CSS, Python, Go, Ruby, Java, Groovy, C#/.NET and SQL, covering 100 exact filenames. Every convention was verified against the tool's current official documentation; see [`docs/linter-detection-rules.md`](https://panopticas.io/linter-detection-rules) for the inventory, the sources, and the candidates that were investigated and rejected.
- `LINTER_RULES` in `constants.py` is the single table describing those tools, expanded into the existing `METADATA_RULES` tables at import. `LINTER_ROLES` constrains the legal roles.
- Two new role tags, `formatter` and `typechecker`, joining the existing `linter`. A tool may hold more than one — Ruff, Biome, RuboCop, Standard and SQLFluff all lint and format.
- `.mjs` and `.cjs` now report as JavaScript, `.mts` and `.cts` as TypeScript. These were missing from the extension table entirely, so every file using them reported `Unknown` regardless of linters.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Rules to follow when adding a product:

### Linter, Formatter and Type Checker Detection

> **Before adding or changing a rule, read [`docs/linter-detection-rules.md`](docs/linter-detection-rules.md)** — the full inventory, the source confirming each convention, and the candidates already investigated and **rejected**. Eleven were rejected as wrong; re-adding one would mislabel repositories.
> **Before adding or changing a rule, read [`docs/linter-detection-rules.md`](docs/linter-detection-rules.md)** — the full inventory, the source confirming each convention, and the candidates already investigated and **rejected**. Ten were rejected as wrong; re-adding one would mislabel repositories.

`LINTER_RULES` in `constants.py` describes each tool once — its languages, its
roles, and the files that identify it:
Expand Down
14 changes: 11 additions & 3 deletions changes/2026-10-linter-detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## What changed

Panopticas now detects the configuration and exclusion files of code-quality
tooling — 30 tools, 96 exact filenames — across JavaScript, TypeScript, CSS,
tooling — 31 tools, 100 exact filenames — across JavaScript, TypeScript, CSS,
Python, Go, Ruby, Java, Groovy, C#/.NET and SQL.

Before this change the only linters detected were ESLint's flat config and
Expand Down Expand Up @@ -69,7 +69,7 @@ Every convention was verified against the tool's current official
documentation before being added. The full evidence base, the sources, and the
rejected candidates are in `docs/linter-detection-rules.md`.

Eleven candidates were investigated and **rejected**, each of which would have
Ten candidates were investigated and **rejected**, each of which would have
been a wrong rule:

- **revive** auto-discovers only `$HOME/revive.toml` — a user-level path, not
Expand All @@ -79,9 +79,17 @@ been a wrong rule:
- **Black**, **StandardJS** and **Spotless** have no dedicated config file.
- **gofmt**, **gofumpt** and **go vet** have no configuration at all.
- **pycodestyle**'s dedicated file is user-level.
- **dprint** is plausible but its filename list could not be confirmed.
- Security scanners (Sonar, Bandit, Brakeman) are out of scope.

**dprint** was initially in that list and has since been added. Its docs never
enumerate the filenames it discovers, so the answer came from
`POSSIBLE_CONFIG_FILE_NAMES` in dprint's
`crates/dprint/src/configuration/resolve_main_config_path.rs`: exactly four
names, walking up ancestor directories, no `config/` subdirectory. It also
reads a user-level global config reusing the `dprint.jsonc` name, which does
not disqualify it the way revive's user-level-only config did — dprint's four
names are genuinely project-level.

`pyproject.toml`, `setup.cfg`, `tox.ini` and `package.json` genuinely hold
linter configuration, but which tool is inside is not knowable from the path,
and detection here is path-based only. Tagging them would mislabel nearly
Expand Down
22 changes: 20 additions & 2 deletions docs/linter-detection-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,25 @@ Each of these was investigated against the tool's current documentation and
| **Spotless** | Configured only in `build.gradle` / `pom.xml`. |
| **gofmt**, **gofumpt**, **go vet** | No configuration file at all. |
| **pycodestyle** | Project-level config lives in `setup.cfg` or `tox.ini`; the dedicated file is the user-level `~/.config/pycodestyle`. |
| **dprint** | Plausible (`dprint.json`), but the filename list could not be confirmed from the official docs. Left out pending verification — a missing rule is better than a wrong one. |
| **Sonar**, **Bandit**, **Brakeman**, **GitLeaks** | Security scanners, out of scope. GitLeaks is already detected separately via `.gitleaksignore`. |

### How dprint was settled

dprint was initially left out because its documentation never enumerates the
filenames it discovers. The answer is in the source —
`POSSIBLE_CONFIG_FILE_NAMES` in
[`resolve_main_config_path.rs`](https://github.com/dprint/dprint/blob/main/crates/dprint/src/configuration/resolve_main_config_path.rs)
— which lists exactly four names, searched in order while walking up ancestor
directories, with no `config/` subdirectory. The setup page corroborates the
two hidden variants.

Worth noting against the user-level trap that disqualified revive: dprint also
reads a global config at `~/.config/dprint/dprint.jsonc`, reusing the
`dprint.jsonc` name. That does not disqualify it. revive's *only*
auto-discovered config is user-level, so a repository rule would be fiction;
dprint's four names are genuinely project-level, and its global config merely
shares a name in a different directory.

### Two judgement calls worth knowing

**`checkstyle.xml`** is not a documented Checkstyle convention — Checkstyle
Expand Down Expand Up @@ -146,6 +162,7 @@ documentation:
| Biome | [Configure Biome](https://biomejs.dev/guides/configure-biome/) |
| oxlint | [Linter config](https://oxc.rs/docs/guide/usage/linter/config.html) |
| TypeScript | [tsconfig.json](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) |
| dprint | [Setup](https://dprint.dev/setup/) and `POSSIBLE_CONFIG_FILE_NAMES` in [resolve_main_config_path.rs](https://github.com/dprint/dprint/blob/main/crates/dprint/src/configuration/resolve_main_config_path.rs) |
| EditorConfig | [editorconfig.org](https://editorconfig.org/) |
| Ruff | [Configuration](https://docs.astral.sh/ruff/configuration/) |
| Flake8 | [Configuring flake8](https://flake8.pycqa.org/en/latest/user/configuration.html) |
Expand Down Expand Up @@ -192,6 +209,7 @@ re-read the rejected list before adding anything in it.
| **Stylelint** | linter | `CSS` | `stylelint.config.js`, `stylelint.config.mjs`, `stylelint.config.cjs`, `stylelint.config.ts`, `.stylelintrc`, `.stylelintrc.js`, `.stylelintrc.mjs`, `.stylelintrc.cjs`, `.stylelintrc.yml`, `.stylelintrc.yaml`, `.stylelintrc.json` | `.stylelintignore` |
| **Biome** | linter, formatter | `JavaScript`, `TypeScript`, `CSS` | `biome.json`, `biome.jsonc`, `.biome.json`, `.biome.jsonc` | — |
| **oxlint** | linter | `JavaScript`, `TypeScript` | `.oxlintrc.json`, `.oxlintrc.jsonc`, `oxlint.config.ts`, `oxlint.config.mts` | — |
| **dprint** | formatter | `JavaScript`, `TypeScript` | `dprint.json`, `dprint.jsonc`, `.dprint.json`, `.dprint.jsonc` | — |
| **JSHint** | linter | `JavaScript` | `.jshintrc` | `.jshintignore` |
| **TypeScript** | typechecker | `TypeScript`, `JavaScript` | `tsconfig.json`, `jsconfig.json` | — |
| **EditorConfig** | formatter | *(none)* | `.editorconfig` | — |
Expand Down Expand Up @@ -253,4 +271,4 @@ re-read the rejected list before adding anything in it.
|---|---|---|---|---|
| **SQLFluff** | linter, formatter | `SQL` | `.sqlfluff` | `.sqlfluffignore` |

<!-- 30 tools, 96 exact filenames -->
<!-- 31 tools, 100 exact filenames -->
16 changes: 16 additions & 0 deletions src/panopticas/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,22 @@
"config": [".oxlintrc.json", ".oxlintrc.jsonc", "oxlint.config.ts",
"oxlint.config.mts"],
},
"dprint": {
# A plugin platform rather than one formatter; the TypeScript and
# JavaScript plugin is the flagship. It also formats JSON, Markdown,
# TOML and Dockerfiles, which is why only the two main languages are
# tagged.
"languages": ["JavaScript", "TypeScript"],
"roles": ["formatter"],
# POSSIBLE_CONFIG_FILE_NAMES in dprint's
# crates/dprint/src/configuration/resolve_main_config_path.rs. dprint
# also reads a user-level global config that reuses the dprint.jsonc
# name under ~/.config/dprint/ — not a repository artifact, and not
# matched by these rules, which are relative to the scanned tree.
"config": ["dprint.json", "dprint.jsonc", ".dprint.json",
".dprint.jsonc"],
# Exclusions are `excludes` globs inside the config; no ignore file.
},
"JSHint": {
"languages": ["JavaScript"],
"roles": ["linter"],
Expand Down
18 changes: 18 additions & 0 deletions tests/test_linter_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,24 @@ def test_biome_is_linter_and_formatter(self, filename):
def test_oxlint_config(self, filename):
assert {"linter", "oxlint"} <= tags_for(filename)

@pytest.mark.parametrize("filename", [
"dprint.json", "dprint.jsonc", ".dprint.json", ".dprint.jsonc",
])
def test_dprint_config(self, filename):
# POSSIBLE_CONFIG_FILE_NAMES in dprint's
# crates/dprint/src/configuration/resolve_main_config_path.rs — the
# four names it auto-discovers, walking up ancestor directories.
assert {"formatter", "dprint", "config"} <= tags_for(filename)

def test_dprint_is_a_formatter_not_a_linter(self):
assert "linter" not in tags_for("dprint.json")

def test_dprint_has_no_ignore_file(self):
# Exclusions are `excludes` globs inside the config, as with
# golangci-lint. There is no .dprintignore to detect.
from panopticas.constants import LINTER_RULES
assert not LINTER_RULES["dprint"].get("ignore")

def test_jshint_config_and_ignore(self):
assert {"JavaScript", "linter", "JSHint", "config"} \
<= tags_for(".jshintrc")
Expand Down
Loading