Skip to main content

drep/languages/
definitions.rs

1//! The registered languages.
2//!
3//! Adding a language is an entry here plus, if it has one, a tool output parser.
4//! No control flow anywhere else in drep changes.
5//!
6//! `config_files` is what makes a tool run at all: drep checks a project against
7//! the style that project has *chosen*, so a repo with no eslint config gets no
8//! eslint findings rather than a wall of default-preset complaints.
9
10use super::spec::{DEFAULT_TOOL_TIMEOUT_SECS, LanguageSupport, ToolSpec};
11
12/// Python deterministic checker.
13pub static RUFF: ToolSpec = ToolSpec {
14    name: "ruff",
15    command: &["ruff", "check", "--output-format", "json"],
16    local_paths: &["venv/bin/ruff", ".venv/bin/ruff"],
17    config_files: &["pyproject.toml", "ruff.toml", ".ruff.toml"],
18    output_format: "json",
19    diagnostics_stream: "stdout",
20    timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
21    timeout_context: None,
22    establishes_compilation: false,
23    serial_in_repository: false,
24    accepts_files: true,
25};
26
27/// JavaScript deterministic checker.
28pub static ESLINT: ToolSpec = ToolSpec {
29    name: "eslint",
30    command: &["eslint", "--format", "json"],
31    local_paths: &["node_modules/.bin/eslint"],
32    config_files: &[
33        "eslint.config.js",
34        "eslint.config.mjs",
35        "eslint.config.cjs",
36        ".eslintrc",
37        ".eslintrc.js",
38        ".eslintrc.cjs",
39        ".eslintrc.json",
40        ".eslintrc.yml",
41        ".eslintrc.yaml",
42    ],
43    output_format: "json",
44    diagnostics_stream: "stdout",
45    timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
46    timeout_context: None,
47    establishes_compilation: false,
48    serial_in_repository: false,
49    accepts_files: true,
50};
51
52/// TypeScript's compiler-as-checker. Streams diagnostics to stdout.
53pub static TSC: ToolSpec = ToolSpec {
54    name: "tsc",
55    command: &["tsc", "--noEmit", "--pretty", "false"],
56    local_paths: &["node_modules/.bin/tsc"],
57    config_files: &["tsconfig.json"],
58    output_format: "tsc",
59    diagnostics_stream: "stdout",
60    timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
61    timeout_context: None,
62    establishes_compilation: true,
63    serial_in_repository: false,
64    // Passing source files makes tsc ignore tsconfig.json. Run the configured
65    // project and filter its diagnostics back to the requested files.
66    accepts_files: false,
67};
68
69/// Go formatting checker - lists files whose formatting drifts from `gofmt`'s.
70///
71/// `go.mod` is the marker that this is a Go module at all; gofmt has no
72/// config of its own because its formatting is not configurable.
73pub static GOFMT: ToolSpec = ToolSpec {
74    name: "gofmt",
75    command: &["gofmt", "-l"],
76    local_paths: &[],
77    config_files: &["go.mod"],
78    output_format: "lines",
79    diagnostics_stream: "stdout",
80    timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
81    timeout_context: None,
82    establishes_compilation: false,
83    serial_in_repository: false,
84    accepts_files: true,
85};
86
87/// `go vet`. Streams diagnostics to stderr.
88///
89/// Not -json: that only emits JSON once the package compiles, and a package
90/// that does not compile is exactly when vet has the most to say.
91pub static GO_VET: ToolSpec = ToolSpec {
92    name: "go vet",
93    command: &["go", "vet"],
94    local_paths: &[],
95    config_files: &["go.mod"],
96    output_format: "position",
97    diagnostics_stream: "stderr",
98    timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
99    timeout_context: None,
100    establishes_compilation: true,
101    serial_in_repository: false,
102    accepts_files: true,
103};
104
105/// Rust linter - emits structured JSON via cargo's message-format.
106pub static CLIPPY: ToolSpec = ToolSpec {
107    name: "clippy",
108    command: &["cargo", "clippy", "--message-format", "json", "--quiet"],
109    local_paths: &[],
110    config_files: &["Cargo.toml"],
111    output_format: "cargo",
112    diagnostics_stream: "stdout",
113    // Cargo's build lock is acquired by Cargo itself, so its wait is part of
114    // the child process. Allow the same long-running ceiling as an LLM review
115    // rather than failing a whole gate at the generic two-minute tool limit.
116    timeout_secs: 1_800,
117    timeout_context: Some(", including its Cargo build-lock wait"),
118    establishes_compilation: true,
119    serial_in_repository: true,
120    // `cargo clippy` checks a crate, not files: a path argument is rejected
121    // with "unexpected argument". See `ToolSpec::accepts_files`.
122    accepts_files: false,
123};
124
125/// Python language entry.
126pub static PYTHON: LanguageSupport = LanguageSupport {
127    name: "python",
128    display_name: "Python",
129    extensions: &[".py"],
130    tools: &[&RUFF],
131    conventions: &[
132        "Follows PEP 8 naming and structure",
133        "Type hints on public APIs, and correct use of Optional/None",
134        "Context managers for resources rather than manual cleanup",
135        "Mutable default arguments, and late-binding closures in loops",
136    ],
137    vendored_dirs: &["__pycache__", "venv", ".venv", "env", ".tox", ".eggs"],
138};
139
140/// JavaScript language entry.
141pub static JAVASCRIPT: LanguageSupport = LanguageSupport {
142    name: "javascript",
143    display_name: "JavaScript",
144    extensions: &[".js", ".jsx", ".mjs", ".cjs"],
145    tools: &[&ESLINT],
146    conventions: &[
147        "Unhandled promise rejections and missing await",
148        "Sequential awaits in a loop where the work is independent",
149        "var versus let/const, and accidental global scope",
150        "Equality coercion (== versus ===)",
151    ],
152    vendored_dirs: &["node_modules", ".next", ".nuxt"],
153};
154
155/// TypeScript language entry.
156pub static TYPESCRIPT: LanguageSupport = LanguageSupport {
157    name: "typescript",
158    display_name: "TypeScript",
159    extensions: &[".ts", ".tsx", ".mts", ".cts"],
160    tools: &[&ESLINT, &TSC],
161    conventions: &[
162        "`any` where a real type is available, and unsafe casts",
163        "Unhandled promise rejections and missing await",
164        "Non-null assertions (!) that hide a genuine null case",
165        "Sequential awaits in a loop where the work is independent",
166    ],
167    vendored_dirs: &["node_modules", ".next", ".nuxt"],
168};
169
170/// Go language entry.
171pub static GO: LanguageSupport = LanguageSupport {
172    name: "go",
173    display_name: "Go",
174    extensions: &[".go"],
175    tools: &[&GOFMT, &GO_VET],
176    conventions: &[
177        "Errors ignored rather than checked and wrapped",
178        "Goroutine leaks, and writes to a channel nobody reads",
179        "defer inside a loop, and defer that never runs",
180        "Data races on shared state without synchronisation",
181    ],
182    vendored_dirs: &["vendor"],
183};
184
185/// Rust language entry.
186pub static RUST_LANG: LanguageSupport = LanguageSupport {
187    name: "rust",
188    display_name: "Rust",
189    extensions: &[".rs"],
190    tools: &[&CLIPPY],
191    conventions: &[
192        "unwrap/expect on values that can legitimately be None or Err",
193        "unsafe blocks, and whether their invariants are documented",
194        "Unnecessary clones and allocations in hot paths",
195        "Send/Sync correctness for types crossing threads",
196    ],
197    vendored_dirs: &["target"],
198};
199
200/// Every registered language, in registration order.
201pub static ALL_LANGUAGES: &[&LanguageSupport] =
202    &[&PYTHON, &JAVASCRIPT, &TYPESCRIPT, &GO, &RUST_LANG];