PageRenderTime 1303ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/eng/code-quality-reports/src/main/java/com/azure/tools/checkstyle/checks/FluentMethodNameCheck.java

http://github.com/WindowsAzure/azure-sdk-for-java
Java | 139 lines | 86 code | 16 blank | 37 comment | 20 complexity | e74334d528c51b418ae353e191f5b2f1 MD5 | raw file
Possible License(s): MIT
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License.
  3. package com.azure.tools.checkstyle.checks;
  4. import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
  5. import com.puppycrawl.tools.checkstyle.api.DetailAST;
  6. import com.puppycrawl.tools.checkstyle.api.TokenTypes;
  7. import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
  8. import java.util.ArrayDeque;
  9. import java.util.Collections;
  10. import java.util.HashSet;
  11. import java.util.Queue;
  12. import java.util.Set;
  13. /**
  14. * Model Class Method check requirements:
  15. * <ol>
  16. * <li>Fluent Methods: All methods that return an instance of the class, and that have one parameter.</li>
  17. * <li>The method name should not start with {@code avoidStartWords}.</li>
  18. * <li>All methods should not throw checked exceptions.</li>
  19. * </ol>
  20. */
  21. public class FluentMethodNameCheck extends AbstractCheck {
  22. /**
  23. * This is a custom defined set which contains all prefixes that are not allowed.
  24. */
  25. private final Set<String> avoidStartWords = new HashSet<>();
  26. /**
  27. * A LIFO Queue tracks the status of the inner class names when traversals the AST tree.
  28. */
  29. private final Queue<String> classNameStack = Collections.asLifoQueue(new ArrayDeque<>());
  30. /**
  31. * Adds words that methods in fluent classes should not be prefixed with.
  32. * @param avoidStartWords the starting strings that should not start with in fluent method
  33. */
  34. public final void setAvoidStartWords(String... avoidStartWords) {
  35. Collections.addAll(this.avoidStartWords, avoidStartWords);
  36. }
  37. @Override
  38. public int[] getDefaultTokens() {
  39. return getRequiredTokens();
  40. }
  41. @Override
  42. public int[] getAcceptableTokens() {
  43. return getRequiredTokens();
  44. }
  45. @Override
  46. public int[] getRequiredTokens() {
  47. return new int[] {
  48. TokenTypes.CLASS_DEF,
  49. TokenTypes.METHOD_DEF
  50. };
  51. }
  52. @Override
  53. public void visitToken(DetailAST token) {
  54. switch (token.getType()) {
  55. case TokenTypes.CLASS_DEF:
  56. classNameStack.offer(token.findFirstToken(TokenTypes.IDENT).getText());
  57. break;
  58. case TokenTypes.METHOD_DEF:
  59. if (!isFluentMethod(token)) {
  60. return;
  61. }
  62. checkMethodNamePrefix(token);
  63. // logs error if the @Fluent method has 'throws' at the method declaration.
  64. if (token.findFirstToken(TokenTypes.LITERAL_THROWS) != null) {
  65. log(token, String.format(
  66. "Fluent Method ''%s'' must not be declared to throw any checked exceptions.",
  67. token.findFirstToken(TokenTypes.IDENT).getText()));
  68. }
  69. break;
  70. default:
  71. // Checkstyle complains if there's no default block in switch
  72. break;
  73. }
  74. }
  75. @Override
  76. public void leaveToken(DetailAST token) {
  77. if (token.getType() == TokenTypes.CLASS_DEF && !classNameStack.isEmpty()) {
  78. classNameStack.poll();
  79. }
  80. }
  81. /**
  82. * Log the error if the method name is not start with {@code avoidStartWord}
  83. * @param methodDefToken METHOD_DEF AST node
  84. */
  85. private void checkMethodNamePrefix(DetailAST methodDefToken) {
  86. // A fluent method should only has one parameter.
  87. if (TokenUtil.findFirstTokenByPredicate(methodDefToken, parameters ->
  88. parameters.getType() == TokenTypes.PARAMETERS && parameters.getChildCount() != 1).isPresent()) {
  89. log(methodDefToken, "A fluent method should only have one parameter.");
  90. }
  91. // A fluent method's return type should be the class itself
  92. final DetailAST typeToken = methodDefToken.findFirstToken(TokenTypes.TYPE);
  93. if (TokenUtil.findFirstTokenByPredicate(typeToken, ident -> ident.getType() == TokenTypes.IDENT
  94. && !ident.getText().equals(classNameStack.peek())).isPresent()) {
  95. log(methodDefToken, "Return type of fluent method should be the class itself");
  96. }
  97. final String methodName = methodDefToken.findFirstToken(TokenTypes.IDENT).getText();
  98. // method name should not start with words in the avoid string list
  99. avoidStartWords.forEach(avoidStartWord -> {
  100. if (methodName.length() >= avoidStartWord.length() && methodName.startsWith(avoidStartWord)) {
  101. log(methodDefToken, String.format("''%s'' fluent method name should not start with keyword ''%s''.",
  102. methodName, avoidStartWord));
  103. }
  104. });
  105. }
  106. /**
  107. * Checks if the method is annotated with annotation @Fluent
  108. *
  109. * @param methodDefToken the METHOD_DEF AST node
  110. * @return true if the class is annotated with @Fluent, false otherwise.
  111. */
  112. private boolean isFluentMethod(DetailAST methodDefToken) {
  113. // Always has MODIFIERS node
  114. final DetailAST modifiersToken = methodDefToken.findFirstToken(TokenTypes.MODIFIERS);
  115. // If no @Fluent annotated with this class, return false
  116. return TokenUtil.findFirstTokenByPredicate(modifiersToken,
  117. annotationToken -> annotationToken.getType() == TokenTypes.ANNOTATION
  118. && TokenUtil.findFirstTokenByPredicate(annotationToken,
  119. identToken -> identToken.getType() == TokenTypes.IDENT
  120. && "Fluent".equals(identToken.getText())).isPresent())
  121. .isPresent();
  122. }
  123. }