DSL markers on no-op type positions
DSL_MARKER_NOOP_TYPE_POSITION reports a @DslMarker annotation written directly on a type
position where it has no effect on scope control.
| Diagnostic | DSL_MARKER_NOOP_TYPE_POSITION |
| Default severity | Error |
| Gradle property | dslMarkerNoopTypePosition |
| Exemption | none |
What it reports
A @DslMarker written on a function, a property, a plain parameter type, a return
type, or a property or variable type marks a value that is only ever accessed by name, thus it
restricts nothing:
@file:JvmName("Branches")// Supporting DSL declarations/** Marks branch DSL receivers. */@DslMarker@Target(AnnotationTarget.CLASS,AnnotationTarget.TYPE,AnnotationTarget.TYPEALIAS,)public annotation class BranchDsl/** Branch accepted by the branch-building DSL. */public class Branch/** Appends [branch] to the current tree. */public fun append(branch: @BranchDsl Branch) { }
Unlike the API-surface checks, this one also fires on non-public and even internal declarations: an inert marker misleads the library's own authors just as much as its users.
Rationale
A marker in a no-op position gives none of the protection @DslMarker exists for: inside a nested
builder lambda, an outer builder's members stay implicitly callable, so code can silently call the
wrong scope's functions. See the Kotlin guide on scope control for DSL markers.
Don't
/** Applies [block] while constructing a tree tag. */@JvmSyntheticpublic fun configure(block: Tag.() -> Unit): @TreeDsl Unit { }
Do
/** Node whose receiver participates in tree DSL scope control. */@TreeDslpublic class Tag/** Applies [block] while constructing a tree tag. */@JvmSyntheticpublic fun configure(block: Tag.() -> Unit) { }
Don't
@file:JvmName("Trees")/** Adds [tag] to the current tree. */public fun process(tag: @TreeDsl Tag) { }
Do
@file:JvmName("Trees")// no scope control needed for a named value/** Adds [tag] to the current tree. */public fun process(tag: Tag) { }
Notes
- A context parameter's type is an implicit value just like a receiver, so a marker there is effective and not reported.
- Markers on supertypes, type parameter bounds, and type alias expansions are effective carriers
and are not reported:
class Div : @TreeDsl Tag(),typealias MarkedTag = @TreeDsl Tag. - A marker nested inside a type argument is not analyzed at all (
List<@TreeDsl Tag>triggers nothing), which is a known limitation rather than an endorsement.
Exemption
There is no @Intentionally* annotation for this diagnostic: a marker on a no-op type position
should normally be moved to an effective position (a receiver, a context parameter, or a supertype)
or removed. A deliberate flow-through use is the exception: a value whose type carries the marker
can later become an implicit receiver through type inference (with(value) { ... }). Suppress the
diagnostic on that declaration with @Suppress("DSL_MARKER_NOOP_TYPE_POSITION") when that use is
intended. To silence the check project-wide, lower its severity with the Gradle property below.
Configuration
apiWatchdog {dslMarkerNoopTypePosition = WatchdogSeverity.WARNING}
With direct compiler invocation:
-P plugin:org.jetbrains.kotlin.library.api.watchdog:diagnosticSeverity=DSL_MARKER_NOOP_TYPE_POSITION:warning