From 313ef17c54ec57c22ec148589e111bb82af508c5 Mon Sep 17 00:00:00 2001 From: Peter Freiberg <109841928+sabbaticas@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:01:54 +1100 Subject: [PATCH] Detect dprint config files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dprint was left out of the initial linter detection set because its documentation never enumerates the filenames it discovers, and a missing rule is better than a wrong one. The answer is in the source: POSSIBLE_CONFIG_FILE_NAMES in crates/dprint/src/configuration/resolve_main_config_path.rs lists exactly four names, searched in order while walking up ancestor directories, with no config/ subdirectory. The setup page corroborates the two hidden variants. dprint.json dprint.jsonc .dprint.json .dprint.jsonc Tagged formatter, not linter — dprint is a formatting platform with no lint rules. Languages are capped at JavaScript and TypeScript, its flagship plugin, rather than listing every plugin it offers (JSON, Markdown, TOML, Dockerfile), on the same reasoning as Prettier. No ignore file: exclusions are `excludes` globs inside the config, as with golangci-lint. Checked against the user-level trap that disqualified revive. dprint does read a global config at ~/.config/dprint/dprint.jsonc, reusing the dprint.jsonc name, but that does not disqualify it: revive's only auto-discovered config is user-level, so a repository rule would be fiction, whereas dprint's four names are genuinely project-level and the global config merely shares a name in a different directory. Costs nothing in the filetype tables — all four names already resolve to JSON or JSONC, the latter added for Biome. One new tag, no new filetypes. 31 tools and 100 exact filenames, up from 30 and 96. Version stays at 0.0.20, which is unreleased, so this folds into that release rather than needing a 0.0.21. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 2 +- CLAUDE.md | 2 +- changes/2026-10-linter-detection.md | 14 +++++++++++--- docs/linter-detection-rules.md | 22 ++++++++++++++++++++-- src/panopticas/constants.py | 16 ++++++++++++++++ tests/test_linter_detection.py | 18 ++++++++++++++++++ 6 files changed, 67 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf5536..6152cbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/CLAUDE.md b/CLAUDE.md index d471c6d..32c172d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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: diff --git a/changes/2026-10-linter-detection.md b/changes/2026-10-linter-detection.md index f16cc66..e4e263a 100644 --- a/changes/2026-10-linter-detection.md +++ b/changes/2026-10-linter-detection.md @@ -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 @@ -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 @@ -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 diff --git a/docs/linter-detection-rules.md b/docs/linter-detection-rules.md index 2794efa..a4fdd1e 100644 --- a/docs/linter-detection-rules.md +++ b/docs/linter-detection-rules.md @@ -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 @@ -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) | @@ -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` | — | @@ -253,4 +271,4 @@ re-read the rejected list before adding anything in it. |---|---|---|---|---| | **SQLFluff** | linter, formatter | `SQL` | `.sqlfluff` | `.sqlfluffignore` | - + diff --git a/src/panopticas/constants.py b/src/panopticas/constants.py index 3f96eb7..0ef2da6 100644 --- a/src/panopticas/constants.py +++ b/src/panopticas/constants.py @@ -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"], diff --git a/tests/test_linter_detection.py b/tests/test_linter_detection.py index b065aee..2402690 100644 --- a/tests/test_linter_detection.py +++ b/tests/test_linter_detection.py @@ -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")