Skip to main content

drep/languages/
spec.rs

1//! Language support contract shared by every registered language.
2//!
3//! drep analyzes a file in two layers, and this module is what keeps them free of
4//! per-language conditionals:
5//!
6//! - **Deterministic**: the project's own tools (ruff, eslint, gofmt, clippy).
7//!   They are precise, so their findings can gate a commit.
8//! - **Semantic**: the LLM, told which language it is looking at. It reads any
9//!   language without a parser, so it needs no per-language machinery beyond a
10//!   prompt - which is why adding a language here is a data change, not a
11//!   refactor.
12//!
13//! Deliberately free of heavyweight drep imports: the registry is consulted by
14//! file discovery (`drep.core.file_targets`), which analyzer packages import.
15
16/// A deterministic checker for one language.
17///
18/// Attributes:
19///     name: Tool name, used in logs and finding provenance.
20///     command: argv to run, minus the files. The first element is resolved
21///         against local_paths before PATH.
22///     local_paths: Repo-relative locations to prefer over PATH, so a project
23///         gets the version its own CI runs (node_modules/.bin/eslint rather
24///         than whatever is installed globally).
25///     config_files: Repo-relative paths that mean "this project has opted
26///         into this tool". A tool with none of them present is skipped: its
27///         defaults are not the project's chosen style, so running it anyway
28///         would invent findings the project never asked for.
29///     output_format: How to parse the tool's diagnostics into findings.
30///     diagnostics_stream: Which stream carries them. `go vet` writes to
31///         stderr, so reading only stdout would report every Go file clean.
32#[derive(Debug, Clone, PartialEq)]
33pub struct ToolSpec {
34    /// Tool name, used in logs and finding provenance.
35    pub name: &'static str,
36    /// argv to run, minus the files. The first element is resolved against `local_paths` before PATH.
37    pub command: &'static [&'static str],
38    /// Repo-relative locations to prefer over PATH, so a project gets the
39    /// version its own CI runs (`node_modules/.bin/eslint` rather than
40    /// whatever is installed globally).
41    pub local_paths: &'static [&'static str],
42    /// Repo-relative paths that mean "this project has opted into this tool".
43    /// A tool with none of them present is skipped: its defaults are not the
44    /// project's chosen style, so running it anyway would invent findings the
45    /// project never asked for.
46    pub config_files: &'static [&'static str],
47    /// How to parse the tool's diagnostics into findings.
48    pub output_format: &'static str,
49    /// Which stream carries them. `go vet` writes to stderr, so reading only
50    /// stdout would report every Go file clean.
51    pub diagnostics_stream: &'static str,
52    /// Whether the tool accepts file paths as arguments.
53    ///
54    /// `cargo clippy` does not: it checks a *crate*, and a path argument is
55    /// rejected outright with "unexpected argument". Appending files to it
56    /// therefore made every run fail, so every Rust file came back
57    /// `Unavailable` and `drep check` exited 2 on any Rust repository - the
58    /// deterministic half for Rust never ran at all.
59    ///
60    /// A tool with `accepts_files: false` is invoked bare and reports on the
61    /// whole project, so its findings are filtered down to the files actually
62    /// being checked. Without that filter a commit gate would block on
63    /// pre-existing issues in code the commit never touched.
64    pub accepts_files: bool,
65}
66
67impl Default for ToolSpec {
68    fn default() -> Self {
69        Self {
70            name: "",
71            command: &[],
72            local_paths: &[],
73            config_files: &[],
74            output_format: "json",
75            diagnostics_stream: "stdout",
76            accepts_files: true,
77        }
78    }
79}
80
81/// Everything drep needs to know about one language.
82///
83/// Attributes:
84///     name: Registry key (lowercase, e.g. "typescript").
85///     display_name: How the language is named to the LLM and to users.
86///     extensions: Lowercased suffixes this language owns, including the dot.
87///     tools: Deterministic checkers, in the order they should run.
88///     conventions: Language-specific guidance appended to the analysis
89///         prompt - the part that used to be hardcoded as PEP 8.
90///     vendored_dirs: Dependency and build directories this language creates,
91///         never descended into. Declared here rather than in a global list
92///         so adding a language stays a single-file change.
93#[derive(Debug, Default, Clone, PartialEq)]
94pub struct LanguageSupport {
95    /// Registry key (lowercase, e.g. `"typescript"`).
96    pub name: &'static str,
97    /// How the language is named to the LLM and to users.
98    pub display_name: &'static str,
99    /// Lowercased suffixes this language owns, including the dot.
100    pub extensions: &'static [&'static str],
101    /// Deterministic checkers, in the order they should run.
102    pub tools: &'static [&'static ToolSpec],
103    /// Language-specific guidance appended to the analysis prompt - the part
104    /// that used to be hardcoded as PEP 8.
105    pub conventions: &'static [&'static str],
106    /// Dependency and build directories this language creates, never
107    /// descended into. Declared here rather than in a global list so adding
108    /// a language stays a single-file change.
109    pub vendored_dirs: &'static [&'static str],
110}