Kotlin 类型的安全构建器 DSL,最外层功能的安全性

Kotlin type safe builder DSLs, safety for the outermost function

我将使用实现 DSL 的文档中的 official example 进行一些 HTML 创建。

从 Kotlin 1.1 开始,@DslMarker 注释允许我们限制 类 中函数的范围,就像示例中使用 @HtmlTagMarker 注释一样。当我们尝试编写结构不正确的代码时,这会给我们一个错误:

html {
    body { 
        body { // this in an error, as it's a function call on the outside Html element
        }
    }
}

但是,这不会阻止嵌套最外层的函数,它是 DSL 的入口点。比如现在这个例子,这样写是没有问题的:

html {
    html {
    }
}

在这方面有没有办法让 DSL 更安全?

可能这可以以更优雅的方式完成,但我建议在具有为接收器类型定义的匹配签名的函数上使用 @Deprecated 注释和 DeprecationLevel.ERROR,例如:

@Deprecated("Cannot be used in a html block.", level = DeprecationLevel.ERROR)
fun HtmlReceiver.html(action: HtmlReceiver.() -> Unit): Nothing = error("...")

或者这可以是一个成员函数。顺便说一下,IDE 完成的行为根据它是扩展还是成员而有所不同。

这将使内部调用无效:

html {
    html { // Error: Cannot be used in a html block.
    }
}

(demo of this code)

顶级函数仍然可以通过其 FQN 在 DSL 块内调用,例如com.example.html { },所以这个技巧只是防止用户误调用顶层函数。