Skip to content

Commit 9ca131c

Browse files
committed
Enable type resolution for Detekt behind the enable_type_resolution attr
1 parent f4c5e8d commit 9ca131c

9 files changed

Lines changed: 174 additions & 125 deletions

File tree

MODULE.bazel

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module(
22
name = "rules_detekt",
3-
version = "0.8.1.1",
3+
version = "0.8.1.1.RYANULEP",
44
compatibility_level = 1,
55
repo_name = "bazel_rules_detekt",
66
)
@@ -11,6 +11,7 @@ detekt = use_extension("//detekt:extensions.bzl", "detekt")
1111
use_repo(detekt, "detekt_cli_all")
1212

1313
bazel_dep(name = "rules_java", version = "9.0.3")
14+
bazel_dep(name = "rules_android", version = "0.7.1")
1415
bazel_dep(name = "rules_proto", version = "7.1.0")
1516
bazel_dep(name = "rules_jvm_external", version = "6.9")
1617
bazel_dep(name = "bazel_worker_api", version = "0.0.10")
@@ -22,7 +23,7 @@ maven.install(
2223
name = "rules_detekt_dependencies",
2324
artifacts = [
2425
"junit:junit:4.13.2",
25-
"io.gitlab.arturbosch.detekt:detekt-formatting:1.23.1",
26+
"io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8",
2627
],
2728
fail_if_repin_required = True,
2829
lock_file = "//:maven_install.json",

README.md

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ for the [Bazel build system](https://bazel.build).
99
- HTML, text, XML, Markdown, and SARIF reports;
1010
- [plugins](https://detekt.dev/docs/extensions/extensions/);
1111
- customizable Detekt version and JVM flags;
12-
- [persistent workers](https://blog.bazel.build/2015/12/10/java-workers.html) support;
12+
- [type resolution](https://detekt.dev/docs/gettingstarted/type-resolution/) with JVM and Android classpath support;
1313
- baseline generation via `detekt_create_baseline`;
1414
- configuration options via [attributes](docs/attrs.md).
1515

@@ -127,15 +127,15 @@ and it supports standard Bazel test flags such as `--test_output=all`.
127127

128128
### `detekt` vs `detekt_test`
129129

130-
| | `detekt` | `detekt_test` |
131-
| ------------------- | ------------------------- | -------------------------------------- |
132-
| Bazel rule type | build rule | test rule |
133-
| Run with | `bazel build` | `bazel test` |
134-
| Included in | `bazel build //...` | `bazel test //...` |
135-
| Violation behaviour | build action fails | test fails; build action always passes |
136-
| Text report | printed when action fails | printed to test output when test fails |
137-
| Result caching | yes | yes |
138-
| Bazel test flags | n/a | yes (`--test_output`, etc.) |
130+
| | `detekt` | `detekt_test` |
131+
| ------------------- | ------------------------- |-----------------------------------------------------------------------|
132+
| Bazel rule type | build rule | test rule |
133+
| Run with | `bazel build` | `bazel test` |
134+
| Included in | `bazel build //...` | `bazel test //...` |
135+
| Violation behavior | build action fails | test fails; build action always passes even if violations are present |
136+
| Text report | printed when action fails | printed to test output when test fails |
137+
| Result caching | yes | yes |
138+
| Bazel test flags | n/a | yes (`--test_output`, etc.) |
139139

140140
Use `detekt` when you want violations to block builds the same way a compiler error does. Use
141141
`detekt_test` when you want Detekt to run alongside your test suite and report results through
@@ -177,8 +177,9 @@ detekt_test(
177177

178178
### Configuration Options
179179

180-
All three rules share the same configuration options. In addition to `srcs`, `cfgs`, `baseline`, `plugins`,
181-
and report options, most attributes correspond directly to
180+
All three rules share the same configuration options. In addition to `srcs`, `deps`, `cfgs`,
181+
`baseline`, `plugins`, `enable_type_resolution`, `is_android`, and report options, most attributes
182+
correspond directly to
182183
[Detekt CLI flags](https://detekt.dev/docs/1.23.8/gettingstarted/cli/#use-the-cli) and pass them
183184
through when explicitly set.
184185

@@ -187,7 +188,7 @@ More information can be found in the [attributes](docs/attrs.md).
187188
### Reports
188189

189190
A plain-text report (`{name}_detekt_report.txt`) is **always** generated. Other report formats are
190-
available for opt-in via configuration options..
191+
available for opt-in via attributes.
191192

192193
## Advanced Configuration
193194

@@ -207,7 +208,7 @@ detekt.detekt_version(
207208
use_repo(detekt, "detekt_cli_all")
208209
```
209210

210-
To download Detekt from a custom location (e.g. an internal mirror), use the `url_templates` parameter:
211+
To download Detekt from a custom location (e.g., an internal mirror), use the `url_templates` parameter:
211212

212213
```python
213214
detekt = use_extension("@rules_detekt//detekt:extensions.bzl", "detekt")
@@ -252,17 +253,27 @@ rules_detekt_dependencies(
252253

253254
Each template may contain `{version}` which will be replaced with the version string.
254255

255-
### JVM Flags
256+
### Toolchain
257+
258+
The detekt toolchain controls JVM flags, the JVM bytecode target version, and the Kotlin language
259+
version compatibility. The defaults are:
256260

257-
The default toolchain uses `-Xms16m -Xmx128m`. To customize JVM flags, define your own toolchain
258-
in a `BUILD` file:
261+
| Setting | Default |
262+
| ------------------ | -------- |
263+
| `jvm_flags` | `-Xms16m -Xmx128m` |
264+
| `jvm_target` | `1.8` |
265+
| `language_version` | `2.0` |
266+
267+
To override any of these, define a custom toolchain in a `BUILD` file:
259268

260269
```python
261270
load("@rules_detekt//detekt:toolchain.bzl", "detekt_toolchain")
262271

263272
detekt_toolchain(
264273
name = "my_detekt_toolchain_impl",
265274
jvm_flags = ["-Xms16m", "-Xmx512m"],
275+
jvm_target = "11",
276+
language_version = "1.9",
266277
)
267278

268279
toolchain(
@@ -354,49 +365,50 @@ detekt_test(
354365
)
355366
```
356367

357-
### JVM Target
368+
### Type Resolution
369+
370+
Type resolution enables more advanced static analysis by giving Detekt access to the full
371+
compilation classpath — including return types, nullability, and symbol information. Rules
372+
requiring it are annotated with `@RequiresFullAnalysis` in Detekt's source.
358373

359-
Use `jvm_target` to set the JVM bytecode target version that matches what was used during compilation.
360-
This defaults to `1.8` if not explicitly set:
374+
Type resolution is **enabled by default** (`enable_type_resolution = True`). When enabled, the
375+
appropriate bootclasspath (JDK or Android SDK) is always included. To also include your project's
376+
library dependencies on the classpath, pass them via `deps`:
361377

362378
```python
379+
load("@rules_detekt//detekt:defs.bzl", "detekt_test")
380+
363381
detekt_test(
364382
name = "my_detekt",
365383
srcs = glob(["src/main/kotlin/**/*.kt"]),
366-
jvm_target = "11",
384+
deps = [":my_library"], # provides the classpath for type resolution
367385
)
368386
```
369387

370-
### Language Version
371-
372-
Detekt will report errors for any language features introduced after the specified version if
373-
`language_version` is specified. When unset, no compatibility restriction is applied:
388+
For **Android targets**, set `is_android = True` to include the Android SDK jar in the classpath:
374389

375390
```python
376391
detekt_test(
377392
name = "my_detekt",
378393
srcs = glob(["src/main/kotlin/**/*.kt"]),
379-
language_version = "2.0",
394+
deps = [":my_android_library"],
395+
is_android = True,
380396
)
381397
```
382398

383-
### Type Resolution
384-
385-
Type resolution enables more advanced static analysis by giving Detekt access to the full compilation classpath,
386-
including return types, nullability, and symbol information — capabilities that match those of the Kotlin compiler
387-
itself. Rules requiring it are annotated with `@RequiresFullAnalysis` in Detekt's source.
388-
389-
Use `jvm_target` and `language_version` to match the compilation settings of your project:
399+
To opt out of type resolution entirely (syntax-only analysis), set `enable_type_resolution = False`:
390400

391401
```python
392402
detekt_test(
393403
name = "my_detekt",
394404
srcs = glob(["src/main/kotlin/**/*.kt"]),
395-
jvm_target = "11",
396-
language_version = "2.0",
405+
enable_type_resolution = False,
397406
)
398407
```
399408

409+
`jvm_target` and `language_version` are toolchain-level settings — see
410+
[Toolchain](#toolchain) for how to configure them.
411+
400412
### Reports
401413

402414
By default, Detekt generates a text report internally (used for console output). To export reports as build outputs,

WORKSPACE.bazel

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ maven_install(
3737
name = "rules_detekt_dependencies",
3838
artifacts = [
3939
"junit:junit:4.13.2",
40-
"io.gitlab.arturbosch.detekt:detekt-formatting:1.23.1",
40+
"io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8",
4141
],
4242
fail_if_repin_required = True,
4343
maven_install_json = "//:maven_install.json",
@@ -52,9 +52,9 @@ pinned_maven_install()
5252

5353
## Skylib
5454

55-
skylib_version = "1.4.1"
55+
skylib_version = "1.8.2"
5656

57-
skylib_sha = "b8a1527901774180afc798aeb28c4634bdccf19c4d98e7bdd1ce79d1fe9aaad7"
57+
skylib_sha = "6e78f0e57de26801f6f564fa7c4a48dc8b36873e416257a92bbb0937eeac8446"
5858

5959
http_archive(
6060
name = "bazel_skylib",
@@ -70,9 +70,9 @@ bazel_skylib_workspace()
7070

7171
## Stardoc
7272

73-
stardoc_version = "0.5.3"
73+
stardoc_version = "0.7.2"
7474

75-
stardoc_sha = "3fd8fec4ddec3c670bd810904e2e33170bedfe12f90adf943508184be458c8bb"
75+
stardoc_sha = "0e1ed4a98f26e718776bd64d053d02bb34d98572ccd03d6ba355112a1205706b"
7676

7777
http_archive(
7878
name = "io_bazel_stardoc",

detekt/defs.bzl

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Rule declarations.
33
"""
44

5+
load("@rules_android//rules:utils.bzl", "get_android_sdk")
56
load("@rules_java//java:defs.bzl", "JavaInfo")
67

78
_ATTRS = {
@@ -10,11 +11,19 @@ _ATTRS = {
1011
executable = True,
1112
cfg = "exec",
1213
),
14+
"_java_toolchain": attr.label(
15+
default = Label("@bazel_tools//tools/jdk:current_java_toolchain"),
16+
),
1317
"srcs": attr.label_list(
1418
mandatory = True,
15-
allow_files = [".kt", ".kts"],
19+
allow_files = [".kt", ".kts", ".java"],
1620
allow_empty = False,
17-
doc = "Kotlin source code files to analyze.",
21+
doc = "Kotlin and Java source code files to analyze. Java files are included to support type resolution in mixed-language projects.",
22+
),
23+
"deps": attr.label_list(
24+
default = [],
25+
doc = "Dependencies to provide to Detekt for classpath type resolution.",
26+
providers = [JavaInfo],
1827
),
1928
"plugins": attr.label_list(
2029
default = [],
@@ -55,6 +64,10 @@ _ATTRS = {
5564
default = False,
5665
doc = "Disables default rule sets.",
5766
),
67+
"enable_type_resolution": attr.bool(
68+
default = True,
69+
doc = "Enables type resolution for more advanced static analysis. When enabled, the classpath is constructed from `deps` and the appropriate bootclasspath (Android SDK or JDK) is included. When disabled, no classpath is passed to Detekt and only syntax-based rules are applied.",
70+
),
5871
"excludes": attr.string_list(
5972
default = [],
6073
doc = "Globbing patterns describing paths to exclude from the analysis.",
@@ -63,23 +76,13 @@ _ATTRS = {
6376
default = [],
6477
doc = "Globbing patterns describing paths to include in the analysis. Useful in combination with 'excludes' patterns.",
6578
),
66-
"jvm_target": attr.string(
67-
default = "1.8",
68-
values = ["1.8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"],
69-
doc = "EXPERIMENTAL: Target version of the generated JVM bytecode that was generated during compilation and is now being used for type resolution (1.8, 9, 10, ..., 20)",
70-
),
71-
"language_version": attr.string(
72-
default = "",
73-
values = ["", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "2.0", "2.1", "2.2"],
74-
doc = "EXPERIMENTAL: Compatibility mode for Kotlin language version X.Y, reports errors for all language features that came out later",
75-
),
7679
"max_issues": attr.int(
7780
default = -1,
7881
doc = "Passes only when found issues count does not exceed specified issues count.",
7982
),
8083
"parallel": attr.bool(
8184
default = False,
82-
doc = "Enables parallel compilation and analysis of source files. Do some benchmarks first before enabling this flag. Heuristics show performance benefits starting from 2000 lines of Kotlin code.",
85+
doc = "Enables parallel compilation and analysis of source files. Do some benchmarks first before enabling this flag. Heuristics show performance benefits starting from 2,000 lines of Kotlin code.",
8386
),
8487
"txt_report": attr.bool(
8588
default = False,
@@ -101,10 +104,22 @@ _ATTRS = {
101104
default = False,
102105
doc = "Enables / disables the SARIF report generation. The report file name is `{target_name}_detekt_report.sarif`.",
103106
),
107+
"is_android": attr.bool(
108+
default = False,
109+
doc = "Whether the target is an Android target. When enabled, the Android SDK jar is included in the classpath for type resolution.",
110+
),
104111
}
105112

106113
TOOLCHAIN_TYPE = Label("//detekt:toolchain_type")
107114

115+
_ANDROID_SDK_TOOLCHAIN_TYPE = "@rules_android//toolchains/android_sdk:toolchain_type"
116+
117+
_TOOLCHAINS = [
118+
TOOLCHAIN_TYPE,
119+
config_common.toolchain_type(_ANDROID_SDK_TOOLCHAIN_TYPE, mandatory = False),
120+
"@bazel_tools//tools/jdk:toolchain_type",
121+
]
122+
108123
def _impl(
109124
ctx,
110125
run_as_test_target = False,
@@ -182,10 +197,10 @@ def _impl(
182197
if ctx.attr.includes:
183198
detekt_arguments.add_joined("--includes", ctx.attr.includes, join_with = ",")
184199

185-
detekt_arguments.add("--jvm-target", ctx.attr.jvm_target)
200+
detekt_arguments.add("--jvm-target", ctx.toolchains[TOOLCHAIN_TYPE].jvm_target)
186201

187-
if ctx.attr.language_version:
188-
detekt_arguments.add("--language-version", ctx.attr.language_version)
202+
if ctx.toolchains[TOOLCHAIN_TYPE].language_version:
203+
detekt_arguments.add("--language-version", ctx.toolchains[TOOLCHAIN_TYPE].language_version)
189204

190205
if ctx.attr.max_issues >= 0:
191206
detekt_arguments.add("--max-issues", ctx.attr.max_issues)
@@ -196,8 +211,38 @@ def _impl(
196211
if run_as_test_target:
197212
detekt_arguments.add("--run-as-test-target")
198213

199-
action_inputs.extend(ctx.files.plugins)
200-
detekt_arguments.add_joined("--plugins", ctx.files.plugins, join_with = ",")
214+
# Supports android_binary() as plugins attr, which is not a jar.
215+
plugins = depset([], transitive = [dep[JavaInfo].transitive_runtime_jars for dep in ctx.attr.plugins])
216+
action_inputs.extend(plugins.to_list())
217+
detekt_arguments.add_joined("--plugins", plugins, join_with = ",")
218+
219+
if ctx.attr.enable_type_resolution:
220+
filtered_classpath = []
221+
222+
if ctx.attr.is_android:
223+
# Android SDK is needed for resolving Android-specific types
224+
if not ctx.toolchains[_ANDROID_SDK_TOOLCHAIN_TYPE]:
225+
fail("is_android = True requires a registered Android SDK toolchain. " +
226+
"See https://github.com/bazelbuild/rules_android for setup instructions.")
227+
android_jar = get_android_sdk(ctx).android_jar
228+
action_inputs.append(android_jar)
229+
filtered_classpath.append(android_jar.path)
230+
else:
231+
# JDK platform jar is needed for resolving core types
232+
bootclasspath_files = ctx.attr._java_toolchain[java_common.JavaToolchainInfo].bootclasspath.to_list()
233+
action_inputs.extend(bootclasspath_files)
234+
filtered_classpath.extend([f.path for f in bootclasspath_files])
235+
236+
# compile_jars should contain mostly ijar/header jars that are faster to load onto the classpath
237+
classpath = depset([], transitive = [dep[JavaInfo].transitive_compile_time_jars for dep in ctx.attr.deps]).to_list()
238+
action_inputs.extend(classpath)
239+
for file in classpath:
240+
# Dependencies on AP may incorrectly bring guava JRE to classpath, make sure it's filtered out
241+
if "com/google/guava/guava/" in file.path:
242+
continue
243+
filtered_classpath.append(file.path)
244+
245+
detekt_arguments.add("--classpath", ":".join(filtered_classpath))
201246

202247
txt_report = ctx.actions.declare_file("{}_detekt_report.txt".format(ctx.label.name))
203248
action_outputs.append(txt_report)
@@ -285,21 +330,21 @@ detekt = rule(
285330
implementation = _detekt_impl,
286331
attrs = _ATTRS,
287332
provides = [DefaultInfo],
288-
toolchains = [TOOLCHAIN_TYPE],
333+
toolchains = _TOOLCHAINS,
289334
)
290335

291336
detekt_create_baseline = rule(
292337
implementation = _detekt_create_baseline_impl,
293338
attrs = _ATTRS,
294339
provides = [DefaultInfo],
295-
toolchains = [TOOLCHAIN_TYPE],
340+
toolchains = _TOOLCHAINS,
296341
executable = True,
297342
)
298343

299344
detekt_test = rule(
300345
implementation = _detekt_test_impl,
301346
attrs = _ATTRS,
302347
provides = [DefaultInfo],
303-
toolchains = [TOOLCHAIN_TYPE],
348+
toolchains = _TOOLCHAINS,
304349
test = True,
305350
)

0 commit comments

Comments
 (0)