Skip to main content

drep/languages/definitions/
jvm.rs

1//! The JVM family: Java, Kotlin and Scala, plus Groovy build scripts.
2
3use crate::languages::spec::{
4    DEFAULT_TOOL_TIMEOUT_SECS, DiagnosticsStream, LanguageSupport, OutputFormat, ToolSpec,
5};
6
7/// Build outputs shared by the JVM languages. Gradle writes `build` and
8/// `.gradle`, Maven writes `target`. Declared once rather than repeated across
9/// four entries that can never legitimately disagree.
10///
11/// The set is global in effect: `files::is_ignored_dir` consults the union of
12/// every language's vendored directories, so an entry here skips the directory
13/// in a repository with no JVM code at all. `out` is therefore deliberately
14/// absent: IntelliJ's build output is nearly always gitignored anyway (which
15/// the walker honors on its own), while the name is generic enough that a
16/// checked-in `out/` of real sources in some other ecosystem would be silently
17/// dropped from review.
18static JVM_VENDORED_DIRS: &[&str] = &["build", ".gradle", "target"];
19
20/// Java linter. Emits SARIF 2.1.0 on stdout; its startup banner goes to stderr.
21///
22/// `-c` is not in `command`: checkstyle refuses to run without a ruleset and
23/// which one a project uses is exactly what `config_files` discovers, so the
24/// path is appended by `config_flag`. Bare, it exits 1 with "Must specify a
25/// config XML".
26pub static CHECKSTYLE: ToolSpec = ToolSpec {
27    name: "checkstyle",
28    command: &["checkstyle", "-f", "sarif"],
29    local_paths: &[],
30    config_files: &[
31        "checkstyle.xml",
32        ".checkstyle.xml",
33        "config/checkstyle/checkstyle.xml",
34        "gradle/config/checkstyle/checkstyle.xml",
35    ],
36    config_flag: Some("-c"),
37    output_format: OutputFormat::Sarif,
38    diagnostics_stream: DiagnosticsStream::Stdout,
39    // A JVM start plus a full reflections scan of the check registry, on every
40    // invocation. Two minutes is enough but not generous on a cold page cache.
41    timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
42    timeout_context: None,
43    // It parses; it does not compile. A clean run says nothing about whether
44    // javac would accept the file.
45    establishes_compilation: false,
46    serial_in_repository: false,
47    accepts_files: true,
48};
49
50/// Kotlin linter and formatter, run in lint-only mode.
51///
52/// `--log-level=none` is load-bearing: ktlint writes "Lint has found errors
53/// than can be autocorrected" to **stdout**, ahead of the JSON, and the parser
54/// would reject the whole run as unparseable rather than report the findings.
55pub static KTLINT: ToolSpec = ToolSpec {
56    name: "ktlint",
57    command: &["ktlint", "--log-level=none", "--reporter=json"],
58    local_paths: &[],
59    // ktlint reads `.editorconfig` and nothing else. A Kotlin repo without one
60    // has not chosen ktlint's defaults, so it is skipped.
61    config_files: &[".editorconfig"],
62    config_flag: None,
63    output_format: OutputFormat::Ktlint,
64    diagnostics_stream: DiagnosticsStream::Stdout,
65    timeout_secs: DEFAULT_TOOL_TIMEOUT_SECS,
66    timeout_context: None,
67    establishes_compilation: false,
68    serial_in_repository: false,
69    accepts_files: true,
70};
71
72/// Java language entry.
73pub static JAVA: LanguageSupport = LanguageSupport {
74    name: "java",
75    display_name: "Java",
76    extensions: &[".java"],
77    filenames: &[],
78    filename_prefixes: &[],
79    tools: &[&CHECKSTYLE],
80    conventions: &[
81        "Resources closed on every path, and try-with-resources where it applies",
82        "Null handling: Optional versus a nullable return, and unchecked dereferences",
83        "equals/hashCode/compareTo consistency, and mutable state in a shared object",
84        "Exceptions swallowed or logged and rethrown, losing the original cause",
85        "Concurrency: unsynchronised shared state, and non-thread-safe fields on a singleton",
86    ],
87    vendored_dirs: JVM_VENDORED_DIRS,
88};
89
90/// Kotlin language entry.
91///
92/// `.kts` covers both scratch scripts and `build.gradle.kts`, which
93/// `Path::extension` reports as `kts` rather than `gradle.kts`.
94pub static KOTLIN: LanguageSupport = LanguageSupport {
95    name: "kotlin",
96    display_name: "Kotlin",
97    extensions: &[".kt", ".kts"],
98    filenames: &[],
99    filename_prefixes: &[],
100    tools: &[&KTLINT],
101    conventions: &[
102        "Platform types from Java interop dereferenced without a null check",
103        "runBlocking on a coroutine path, and scopes that outlive their work",
104        "!! where the null case is real, and lateinit read before assignment",
105        "data class equality over mutable properties",
106    ],
107    vendored_dirs: JVM_VENDORED_DIRS,
108};
109
110/// Scala language entry.
111///
112/// No deterministic tool. scalafmt and scalafix are both build-plugin-first
113/// here, and neither has a standalone CLI drep can invoke the way it invokes
114/// ruff. The semantic half needs none, so the language is registered anyway
115/// rather than leaving `.scala` unreadable.
116pub static SCALA: LanguageSupport = LanguageSupport {
117    name: "scala",
118    display_name: "Scala",
119    extensions: &[".scala", ".sc"],
120    filenames: &[],
121    filename_prefixes: &[],
122    tools: &[],
123    conventions: &[
124        "Partial functions and non-exhaustive matches",
125        "Option/Either handling versus get and head on an empty collection",
126        "Implicits whose resolution is not obvious at the call site",
127        "Futures without an explicit ExecutionContext, and blocking inside one",
128    ],
129    vendored_dirs: JVM_VENDORED_DIRS,
130};
131
132/// Groovy language entry.
133///
134/// `.gradle` is here because a Gradle build script is Groovy, and a change to
135/// one is exactly the kind of thing worth a second read. No deterministic
136/// tool: CodeNarc is a build plugin rather than a CLI.
137pub static GROOVY: LanguageSupport = LanguageSupport {
138    name: "groovy",
139    display_name: "Groovy",
140    extensions: &[".groovy", ".gradle"],
141    filenames: &[],
142    filename_prefixes: &[],
143    tools: &[],
144    conventions: &[
145        "Dynamic dispatch where a typed call would fail at compile time",
146        "Gradle configuration-time work that belongs in a task action",
147        "Dependency and plugin versions pinned versus floating",
148        "String interpolation of values that should be escaped",
149    ],
150    vendored_dirs: JVM_VENDORED_DIRS,
151};
152
153/// The family's entries in registration order. See `ALL_LANGUAGES`.
154pub(crate) static FAMILY: &[&LanguageSupport] = &[&JAVA, &KOTLIN, &SCALA, &GROOVY];