-
Notifications
You must be signed in to change notification settings - Fork 2.2k
CheckStyleRule-Javadoc-Codesnippet Batch#3, No.7 and No.8 #4920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
4cac1b9
9dbeae5
b52c5ab
cdf50a0
a41ffdc
e7ea7f2
9f7c156
c3c3571
06380ad
6d6064d
a5e48df
7187b42
340c302
5af60e2
840a047
0c7109b
d5f2576
2f9cb94
a9223ae
f473be0
0746ff7
d5f8359
e97c88c
ea9dc37
464d111
af7f4e8
5f2c597
8cf387c
8cc72ea
f8d2228
451f4bb
513fbe8
3eea6dc
068e7a0
95bfe16
ac69f96
be10e5c
0dcaa7b
99a5903
59e53ba
eb3fa99
36be65c
f38084e
8faecd8
88c6405
89e9a38
5bd8728
7997ae8
ed59769
25e36f8
d66b863
a5bc7fd
6b5f663
f97767a
3e0fbd9
631e111
9a45b7a
b99e398
9b33a3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package com.azure.tools.checkstyle.checks; | ||
|
|
||
| import com.puppycrawl.tools.checkstyle.api.DetailNode; | ||
| import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; | ||
| import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck; | ||
| import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| /** | ||
| * Requirement of Javadoc annotation {@literal @codesnippet} check: | ||
| * <ol> | ||
| * <li>Use {@literal {@codesnippet ...}} instead of '<code>', '<pre>', or {@literal {@code ...}) if these tags span | ||
| * multiple lines. Inline code sample are fine as-is</li> | ||
| * <ol>Naming pattern of {@literal {@codesnippet}}: | ||
| * <li>For package name, class name, they should all be lower case; | ||
| * For method name, the first letter of method name should be lower case; | ||
| * For parameters, they should only be letters. Concatenate all parameters with dash character.</li> | ||
| * <li>Jonathan will add more requirement specs later.</li> | ||
| * </ol> | ||
| * </ol> | ||
| */ | ||
| public class JavadocCodeSnippetCheck extends AbstractJavadocCheck { | ||
| // Code snippet annotation naming pattern: | ||
| // Check package name, class name, method name, parameters names' naming patterns. | ||
| private static final String CLASS_PATH_REGEX = "[a-z.]+"; | ||
| private static final String METHOD_NAME_REGEX = "^[a-z][a-zA-Z]+"; | ||
| private static final String PARAMETERS_REGEX = "[a-zA-Z-]+"; | ||
|
|
||
| private static final String CODE_SNIPPET_ANNOTATION = "@codesnippet"; | ||
| private static final String MULTIPLE_LINE_SPAN_ERROR = "Tag '%s' span in multiple lines. Use @codesnippet annotation" + | ||
| " instead of '%s' to ensure that the code block always compiles."; | ||
|
|
||
| // HTML tag set that need to be checked to see if there tags span on multiple lines. | ||
| private static final Set<String> CHECK_TAGS = Collections.unmodifiableSet(new HashSet<>( | ||
| Arrays.asList("pre", "code"))); | ||
|
|
||
| @Override | ||
| public int[] getDefaultJavadocTokens() { | ||
| return getRequiredJavadocTokens(); | ||
| } | ||
|
|
||
| @Override | ||
| public int[] getRequiredJavadocTokens() { | ||
| return new int[] { | ||
| JavadocTokenTypes.HTML_ELEMENT_START, | ||
| JavadocTokenTypes.JAVADOC_INLINE_TAG | ||
| }; | ||
| } | ||
|
|
||
| @Override | ||
| public void visitJavadocToken(DetailNode token) { | ||
| switch (token.getType()) { | ||
| case JavadocTokenTypes.HTML_ELEMENT_START: | ||
| checkHtmlElementStart(token); | ||
| break; | ||
| case JavadocTokenTypes.JAVADOC_INLINE_TAG: | ||
| checkJavadocInlineTag(token); | ||
| checkCodeSnippetNaming(token); | ||
| default: | ||
| // Checkstyle complains if there's no default block in switch | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Use {@literal {@codesnippet ...}} instead of '<code>', '<pre>', or {@literal {@code ...}) if these tags span | ||
| * multiple lines. Inline code sample are fine as-is. | ||
| * | ||
| * @param htmlElementStartNode HTML_ELEMENT_START node | ||
| */ | ||
| private void checkHtmlElementStart(DetailNode htmlElementStartNode) { | ||
| final DetailNode tagNameNode = JavadocUtil.findFirstToken(htmlElementStartNode, JavadocTokenTypes.HTML_TAG_NAME); | ||
| // HTML tags are case-insensitive | ||
| final String tagName = tagNameNode.getText().toLowerCase(); | ||
| if (!CHECK_TAGS.contains(tagName)) { | ||
| return; | ||
| } | ||
|
|
||
| final String tagNameBracket = "<" + tagName + ">"; | ||
| final DetailNode htmlTagNode = htmlElementStartNode.getParent(); | ||
|
|
||
| for (final DetailNode child : htmlTagNode.getChildren()) { | ||
| final int childType = child.getType(); | ||
| if (childType == JavadocTokenTypes.NEWLINE || childType == JavadocTokenTypes.LEADING_ASTERISK) { | ||
| log(child.getLineNumber(), child.getColumnNumber(), | ||
| String.format(MULTIPLE_LINE_SPAN_ERROR, tagNameBracket, tagNameBracket)); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check to see if the JAVADOC_INLINE_TAG node is {@literal @code} tag. If it is, check if the tag contains a new line | ||
| * or a leading asterisk, which implies the tag has spanned in multiple lines. | ||
| * | ||
| * @param inlineTagNode JAVADOC_INLINE_TAG javadoc node | ||
| */ | ||
| private void checkJavadocInlineTag(DetailNode inlineTagNode) { | ||
| final DetailNode codeLiteralNode = JavadocUtil.findFirstToken(inlineTagNode, JavadocTokenTypes.CODE_LITERAL); | ||
| if (codeLiteralNode == null) { | ||
| return; | ||
| } | ||
|
|
||
| final String codeLiteral = codeLiteralNode.getText(); | ||
|
|
||
| for (final DetailNode child : inlineTagNode.getChildren()) { | ||
| final int childType = child.getType(); | ||
| if (childType == JavadocTokenTypes.NEWLINE || childType == JavadocTokenTypes.LEADING_ASTERISK) { | ||
| log(child.getLineNumber(), child.getColumnNumber(), | ||
| String.format(MULTIPLE_LINE_SPAN_ERROR, codeLiteral, codeLiteral)); | ||
| return; | ||
| } | ||
| } | ||
|
mssfang marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * Check to see if the code snippet name matched the naming pattern. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you show us an example of a valid naming pattern? And what conditions need to be met for this to fit the naming pattern.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. com.azure.security.keyvault.keys.cryptography.cryptographyclient.encrypt#symmetric-encrypt. More description added.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a valid naming pattern. The My expectation that this name would actually be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should enforce that the part after the |
||
| * A valid example of naming pattern could be: | ||
| * com.azure.security.keyvault.keys.cryptography.cryptographyclient.encrypt#symmetric-encrypt | ||
| * | ||
| * For more detail, please refers to this class description javadoc. | ||
| * | ||
| * @param inlineTagNode JAVADOC_INLINE_TAG javadoc node | ||
| */ | ||
| private void checkCodeSnippetNaming(DetailNode inlineTagNode) { | ||
| // Only checks on @codesnippet annotation | ||
| final DetailNode customNameNode = JavadocUtil.findFirstToken(inlineTagNode, JavadocTokenTypes.CUSTOM_NAME); | ||
| if (customNameNode == null || !CODE_SNIPPET_ANNOTATION.equals(customNameNode.getText())) { | ||
| return; | ||
| } | ||
|
|
||
| final DetailNode descriptionNode = JavadocUtil.findFirstToken(inlineTagNode, JavadocTokenTypes.DESCRIPTION); | ||
| if (descriptionNode == null) { | ||
| log(descriptionNode.getLineNumber(), descriptionNode.getColumnNumber(), | ||
| String.format("%s has no description.", CODE_SNIPPET_ANNOTATION)); | ||
| return; | ||
|
mssfang marked this conversation as resolved.
Outdated
|
||
| } | ||
| // DESCRIPTION always has child TEXT | ||
| final DetailNode textNode = JavadocUtil.findFirstToken(descriptionNode, JavadocTokenTypes.TEXT); | ||
| final String namingPattern = textNode.getText(); | ||
| // Verify naming pattern spec: | ||
| // 1. For package name, class name, they should all be lower case; | ||
| // For method name, the first letter of method name should be lower case. | ||
| // For parameters, they should only be letters. Concatenate all parameters with dash character | ||
| // 2. Jonathon will add more requirement specs later | ||
| if (!isNamingMatch(namingPattern)) { | ||
| log(textNode.getLineNumber(), textNode.getColumnNumber(), | ||
| String.format("Naming pattern mismatch. It should only contain lower case for the package path and class name for matching regular expression ''%s''" + | ||
| " and the first letter of method name must be small case for matching regular expression ''%s'', and the parameters should be all small case for matching " + | ||
| "regular expression ''%s''.", CLASS_PATH_REGEX, METHOD_NAME_REGEX, PARAMETERS_REGEX)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Find if the given name of code snippet matched the naming pattern specs. | ||
| * | ||
| * @param name code snippet name | ||
| * @return true if match, otherwise, false | ||
| */ | ||
| private boolean isNamingMatch(String name) { | ||
| final String[] str = name.split("#"); | ||
|
|
||
| // invalid naming pattern | ||
| if (str.length == 0 || str.length > 2) { | ||
| return false; | ||
| } | ||
|
|
||
| final String classPath = str[0]; | ||
| // corner case: empty string will also be false | ||
| if (classPath.isEmpty()) { | ||
| return false; | ||
| } | ||
|
|
||
| final int lastIndexOf = classPath.lastIndexOf("."); | ||
| if (lastIndexOf == -1) { | ||
| return false; | ||
| } | ||
|
|
||
| // full path of the class, check to see if there path only has lower case letters and dot | ||
| final String className = classPath.substring(0, lastIndexOf); | ||
| if (!Pattern.compile(CLASS_PATH_REGEX).matcher(className).matches()) { | ||
| return false; | ||
| } | ||
|
|
||
| // method name | ||
| final String methodName = classPath.substring(lastIndexOf + 1); | ||
| if (!Pattern.compile(METHOD_NAME_REGEX).matcher(methodName).matches()) { | ||
| return false; | ||
| } | ||
|
|
||
| // parameters of method function | ||
| if (str.length == 2 && !Pattern.compile(PARAMETERS_REGEX).matcher(str[1]).matches()) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.