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
16pub const DEFAULT_TOOL_TIMEOUT_SECS: u64 = 120;
17
18/// A deterministic checker for one language.
19///
20/// Attributes:
21///     name: Tool name, used in logs and finding provenance.
22///     command: argv to run, minus the files. The first element is resolved
23///         against local_paths before PATH.
24///     local_paths: Repo-relative locations to prefer over PATH, so a project
25///         gets the version its own CI runs (node_modules/.bin/eslint rather
26///         than whatever is installed globally).
27///     config_files: Repo-relative paths that mean "this project has opted
28///         into this tool". A tool with none of them present is skipped: its
29///         defaults are not the project's chosen style, so running it anyway
30///         would invent findings the project never asked for.
31///     config_flag: Flag that hands the discovered config file to the tool,
32///         for the checkers that will not look for it themselves.
33///     output_format: How to parse the tool's diagnostics into findings.
34///     diagnostics_stream: Which stream carries them. `go vet` writes to
35///         stderr, so reading only stdout would report every Go file clean.
36#[derive(Debug, Clone, PartialEq)]
37pub struct ToolSpec {
38    /// Tool name, used in logs and finding provenance.
39    pub name: &'static str,
40    /// argv to run, minus the files. The first element is resolved against `local_paths` before PATH.
41    pub command: &'static [&'static str],
42    /// Repo-relative locations to prefer over PATH, so a project gets the
43    /// version its own CI runs (`node_modules/.bin/eslint` rather than
44    /// whatever is installed globally).
45    pub local_paths: &'static [&'static str],
46    /// Repo-relative paths that mean "this project has opted into this tool".
47    /// A tool with none of them present is skipped: its defaults are not the
48    /// project's chosen style, so running it anyway would invent findings the
49    /// project never asked for.
50    pub config_files: &'static [&'static str],
51    /// Flag that hands the discovered config file to the tool, e.g. `"-c"`.
52    ///
53    /// Most checkers find their own config: ruff reads `pyproject.toml` out of
54    /// the working directory without being told. The JVM linters do not.
55    /// `checkstyle` run bare exits 1 with "Must specify a config XML", so
56    /// without this it could not run at all. When set, the config path
57    /// `config_files` already discovered is appended as
58    /// `[config_flag, <path>]` ahead of the file arguments.
59    pub config_flag: Option<&'static str>,
60    /// How to parse the tool's diagnostics into findings.
61    pub output_format: &'static str,
62    /// Which stream carries them. `go vet` writes to stderr, so reading only
63    /// stdout would report every Go file clean.
64    pub diagnostics_stream: &'static str,
65    /// Wall-clock ceiling for the process. This includes any tool-owned lock
66    /// wait before analysis starts.
67    pub timeout_secs: u64,
68    /// Optional diagnostic suffix explaining a legitimate long wait.
69    pub timeout_context: Option<&'static str>,
70    /// A zero-exit run proves the checked files compiled/typechecked.
71    pub establishes_compilation: bool,
72    /// Whether invocations of this tool must be serialized within a repo.
73    pub serial_in_repository: bool,
74    /// Whether the tool accepts file paths as arguments.
75    ///
76    /// `cargo clippy` does not: it checks a *crate*, and a path argument is
77    /// rejected outright with "unexpected argument". Appending files to it
78    /// therefore made every run fail, so every Rust file came back
79    /// `Unavailable` and `drep check` exited 2 on any Rust repository - the
80    /// deterministic half for Rust never ran at all.
81    ///
82    /// A tool with `accepts_files: false` is invoked bare and reports on the
83    /// whole project, so its findings are filtered down to the files actually
84    /// being checked. Without that filter a commit gate would block on
85    /// pre-existing issues in code the commit never touched.
86    pub accepts_files: bool,
87}
88
89impl Default for ToolSpec {
90    fn default() -> Self {
91        Self {
92            name: "",
93            command: &[],
94            local_paths: &[],
95            config_files: &[],
96            config_flag: None,
97            output_format: "json",
98            diagnostics_stream: "stdout",
99            timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
100            timeout_context: None,
101            establishes_compilation: false,
102            serial_in_repository: false,
103            accepts_files: true,
104        }
105    }
106}
107
108/// Everything drep needs to know about one language.
109///
110/// Attributes:
111///     name: Registry key (lowercase, e.g. "typescript").
112///     display_name: How the language is named to the LLM and to users.
113///     extensions: Lowercased suffixes this language owns, including the dot.
114///     tools: Deterministic checkers, in the order they should run.
115///     conventions: Language-specific guidance appended to the analysis
116///         prompt - the part that used to be hardcoded as PEP 8.
117///     vendored_dirs: Dependency and build directories this language creates,
118///         never descended into. Declared here rather than in a global list
119///         so adding a language stays a single-file change.
120#[derive(Debug, Default, Clone, PartialEq)]
121pub struct LanguageSupport {
122    /// Registry key (lowercase, e.g. `"typescript"`).
123    pub name: &'static str,
124    /// How the language is named to the LLM and to users.
125    pub display_name: &'static str,
126    /// Lowercased suffixes this language owns, including the dot.
127    pub extensions: &'static [&'static str],
128    /// Deterministic checkers, in the order they should run.
129    pub tools: &'static [&'static ToolSpec],
130    /// Language-specific guidance appended to the analysis prompt - the part
131    /// that used to be hardcoded as PEP 8.
132    pub conventions: &'static [&'static str],
133    /// Dependency and build directories this language creates, never
134    /// descended into. Declared here rather than in a global list so adding
135    /// a language stays a single-file change.
136    pub vendored_dirs: &'static [&'static str],
137}