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")