JEP 413:Java API 文档中的代码片段
概括
引入@snippet
JavaDoc 的标准 Doclet 标签,以简化 API 文档中示例源代码的包含。
目标
-
通过提供对源代码片段的 API 访问,促进源代码片段的验证。尽管正确性最终是作者的责任,但增强的支持
javadoc
和相关工具可以使其更容易实现。 -
启用现代样式,例如语法突出显示以及名称与声明的自动链接。
-
为创建和编辑片段提供更好的 IDE 支持。
非目标
-
工具本身的目标不是
javadoc
能够验证、编译或运行任何源代码片段。该任务留给外部工具。 -
提供测试来验证现有 JDK API 文档中的代码片段并不是我们的目标,尽管我们期望并行的工作能够做到这一点。
-
目前支持交互式代码示例并不是我们的目标。尽管我们不排除将来提供此类支持,但任何此类支持都将需要外部基础设施,这超出了本提案的范围。
成功指标
- 展示用新标签的基本实例替换
<pre>{@code ...}</pre>
关键 JDK 模块中的大部分(如果不是全部)块的能力,也许可以使用自动转换实用程序。 (查看和提交这些更改以及手动编辑选定的示例以使用标签的更高级功能超出了范围。)
动机
API 文档的作者经常在文档注释中包含源代码片段。尽管{@code ...}
可以单独用于小代码片段,但重要的片段通常包含在具有以下复合模式的文档注释中:
<pre>{@code
lines of source code
}</pre>
当该javadoc
工具在此文档注释上运行时,标准 doclet 将呈现精确反映标签正文的 HTML {@code ...}
,包括缩进,并且无需验证代码。例如,java.util.Stream 的源代码包含显示流的使用的文档注释。
这种方法存在多种缺点。
-
工具无法可靠地检测代码片段,以检查其有效性。此外,这些片段通常不完整,带有占位符注释和省略号供读者填写空白。由于无法检查每个片段,很容易出现错误,并且在实践中经常出现。
-
使用这种模式的片段不能合理地用语法突出显示来呈现,这是当今文档中代码片段的常见期望。没有正式指示片段中的内容类型,如果要验证片段或使用语法突出显示显示片段,则需要正式指示。
-
使用此模式的片段无法在 IDE 中编辑,除非注释中是纯文本。此外,并非所有代码结构都可以包含在注释中。例如,
/* ... */
不能包括传统的注释,因为片段作为一个整体呈现在Java注释中,并且*/
在这样的注释中不能表示序列。这也意味着字符序列*/
不能在片段内部使用,这对于全局模式和正则表达式可能有用。 -
使用此模式的片段不能包含 HTML 标记,这可能需要突出显示部分文本。
-
使用此模式的片段不能包含文档注释标签,这可能需要将名称链接到 API 中其他位置的定义。
-
使用此模式的片段受到有关缩进的不灵活规则的约束。这些是在删除任何前导空格和星号之后相对于注释行的开头定义的。
解决所有这些问题的更好方法是提供一个带有元数据的新标签,允许作者隐式或显式指定内容的类型,以便可以以适当的方式验证和呈现内容。允许将片段放置在单独的文件中也很有用,这些文件可以由作者的首选编辑器直接操作。
描述
标签@snippet
我们引入了一个新的内联标签 ,{@snippet ...}
来声明要出现在生成的文档中的代码片段。它可用于声明_内联片段_(其中代码片段包含在标签本身内)和_外部片段_(其中代码片段从单独的源文件读取)。
有关代码片段的其他详细信息可以以_名称__值_对的形式作为_属性_给出,放置在初始标签名称之后。属性名称始终是一个简单的标识符。属性值可以用单引号或双引号字符括起来;不支持转义字符。属性与标签名称以及彼此之间通过空格字符(例如空格和换行符)分隔。=
片段可以指定一个id
属性,该属性可用于识别 API 和生成的 HTML 中的片段,并且可用于创建指向片段的链接。在生成的 HTML 中,id 将被放置在生成的代表片段的最外层元素上。
代码片段通常是 Java 源代码,但它们也可能是属性文件的片段、其他语言的源代码或纯文本。片段可以指定lang
属性,该属性标识片段中的内容类型。对于内联代码片段,默认值为java
。对于外部代码片段,默认值源自包含代码片段内容的文件名的扩展名。
在代码片段中,可以将标记标签放置在行注释中,以识别文本中的区域并指示如何呈现文本。 (我们将在下面看到标记标签的示例,例如@highlight
和@replace
。)
内嵌片段
内联代码片段包含标签本身内代码片段的内容。
以下是内联代码片段的示例:
/**
* The following code shows how to use {@code Optional.isPresent}:
* {@snippet :
* if (v.isPresent()) {
* System.out.println("v: " + v.get());
* }
* }
*/
生成的文档中包含的代码片段的内容是冒号 ( ) 后面的换行符:
和右大括号 ( }
) 之间的文本。 (我们不希望 API 文档中经常出现两个右大括号的视觉歧义;例如,它只出现在 JDK 文档注释中的一小部分源代码片段中。)
不需要对 HTML 实体转义<
、>
、等字符&
,也不需要转义文档注释标记。
使用String::stripIndent从内容中去除前导空格。这解决了<pre>{@code ...}</pre>
块的一个恼人的缺点,即要显示的文本总是在任何前导空格和星号字符之后立即开始。在代码片段中,生成的输出中的缩进是相对于源文件中右大括号位置的缩进。这类似于文本块中的缩进相对于结束位置的方式"""
。
内联片段的内容有两个限制:
-
内联代码片段不能使用
/* ... */
注释,因为这*/
会终止封闭的文档注释。此限制适用于文档注释中的所有内容;它不是特定于@snippet
标签的。 -
内联代码片段的内容只能包含平衡的大括号字符对。 整个内联标记由与左大括号匹配的第一个右大括号终止。此限制适用于所有内联标签;它不是特定于
@snippet
标签的。
尽管有这些限制,当示例代码很短、不需要 IDE 中的语言级编辑支持并且不需要与文档中其他地方的其他代码片段共享时,内联代码片段还是很方便的。
外部片段
外部代码片段是指包含代码片段内容的单独文件。
在外部代码片段中,冒号、换行符和后续内容可以省略。
这是与之前相同的示例,作为外部片段:
/**
* The following code shows how to use {@code Optional.isPresent}:
* {@snippet file="ShowOptional.java" region="example"}
*/
其中包含以下内容ShowOptional.java
的文件:
public class ShowOptional {
void show(Optional<String> v) {
// @start region="example"
if (v.isPresent()) {
System.out.println("v: " + v.get());
}
// @end
}
}
标签中的属性{@snippet ...}
标识文件以及要显示的文件区域的名称。和@start
标签@end
定义ShowOptional.java
区域的边界。在这种情况下,该区域的内容与前面示例中的内容相同。 (下面提供了有关@start
和标签的更多信息。)@end
与内联片段不同,外部片段对其内容没有限制。特别是,它们可能包含/* ... */
评论。
外部代码的位置可以使用该class
属性通过类名指定,也可以使用该属性通过短相对文件路径指定file
。在任何一种情况下,文件都可以放置在包层次结构中,该snippet-files
层次结构以包含带有标记的源代码的目录的子目录为根{@snippet ...}
。或者,可以将文件放置在由--snippet-path
工具选项指定的辅助搜索路径上javadoc
。子目录的使用与目前辅助文档文件子目录snippet-files
的使用类似。doc-files
外部代码片段的文件可能包含多个区域,在不同的代码片段标签中引用,出现在文档的不同部分。
外部代码片段很有用,因为它们允许将示例代码编写在单独的文件中,这些文件可以在 IDE 中直接编辑,并且可以在多个相关代码片段之间共享。目录中的文件可以在同一包中的片段之间共享,并且与其他包中的目录snippet-files
中的片段隔离。snippet-files
辅助搜索路径上的文件存在于单个共享名称空间中,并且可以从文档中的任何位置引用。
混合片段
混合代码片段既是内部代码片段又是外部代码片段。它在标签本身中包含片段的内容,以方便任何人阅读所记录的类的源代码,并且它还引用包含片段内容的单独文件。
如果将混合代码片段作为内联代码片段处理的结果与将其作为外部代码片段处理的结果不匹配,则会发生错误。
标记标签
标记定义片段内容内的区域。它们还控制内容的呈现,例如突出显示部分文本、修改文本或链接到文档中的其他位置。它们可用于内部、外部和混合片段。
标记以@
name_开头,后跟任何必需的参数。它们被放置在//
注释中(或其他语言或格式中的等效内容),以免过度干扰源代码的主体,而且还因为/* ... */
注释不能在内联片段中使用。此类注释称为_标记注释。
可以将多个标记标签放置在同一个标记注释中。标记标签适用于包含注释的源行,除非注释以冒号 ( :
) 终止,在这种情况下,标记标签仅适用于下一行。如果标记注释特别长,或者如果片段内容的语法格式不允许注释与非注释源出现在同一行,则后一种语法可能很有用。标记注释不会出现在生成的输出中。
因为其他一些系统使用类似于标记注释的元注释,所以@
以无法识别的名称开头的注释将被忽略。如果名称被识别,但标记注释中有后续错误,则报告错误。相对于从代码片段生成的输出,在这种情况下生成的输出是未定义的。
地区
区域是可选命名的行范围,用于标识要由片段显示的文本。它们还定义操作的范围,例如突出显示或修改文本。
区域的开始由以下任一标记
@start region=
姓名, 或- 指定或name_的_
@highlight
, ,@replace
或标签。如果匹配标签不需要该名称,您可以省略该名称。@link``region``region=``@end
区域的末尾由@end
或@end region=
_name_标记。如果给出了名称,则标签结束以该名称开始的区域。如果未给出名称,则标记将结束尚未具有匹配@end
标记的最近启动的区域。
@start
对于不同的匹配和标签对创建的区域没有限制@end
。区域甚至可以重叠,尽管我们预计这种用法不会很常见。
突出显示
要突出显示一行或一系列行中的内容,请使用@highlight
后跟参数来指定要考虑的文本范围、该范围内要突出显示的文本以及突出显示的类型。
如果指定了region
或region=
名称,则范围是该区域,直到相应的@end
标记。否则,范围只是当前行。
要突出显示范围内文字字符串的每个实例,请使用 string 指定字符串,substring=
_其中_string_可以_是用单引号或双引号括起来的标识符或文本。要突出显示范围内与正则表达式匹配的每个文本实例,请使用regex=
string。如果未指定这两个属性,则突出显示整个范围。