001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.java.ast.check; 021 022 import java.util.Arrays; 023 import java.util.List; 024 import java.util.Set; 025 026 import org.apache.commons.lang.StringUtils; 027 import org.sonar.check.Priority; 028 import org.sonar.check.Rule; 029 import org.sonar.java.ast.visitor.AstUtils; 030 import org.sonar.java.ast.visitor.JavaAstVisitor; 031 import org.sonar.java.recognizer.JavaFootprint; 032 import org.sonar.squid.api.CheckMessage; 033 import org.sonar.squid.api.SourceFile; 034 import org.sonar.squid.recognizer.CodeRecognizer; 035 036 import com.google.common.collect.Sets; 037 import com.puppycrawl.tools.checkstyle.api.DetailAST; 038 import com.puppycrawl.tools.checkstyle.api.TextBlock; 039 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 040 041 /** 042 * @since 2.13 043 */ 044 @Rule(key = "CommentedOutCodeLine", priority = Priority.MAJOR) 045 public class CommentedOutCodeLineCheck extends JavaAstVisitor { 046 047 private static final double THRESHOLD = 0.9; 048 049 /** 050 * This list was taken from com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck 051 */ 052 private static final List<Integer> WANTED_TOKENS = Arrays.asList( 053 TokenTypes.INTERFACE_DEF, 054 TokenTypes.CLASS_DEF, 055 TokenTypes.ANNOTATION_DEF, 056 TokenTypes.ENUM_DEF, 057 TokenTypes.METHOD_DEF, 058 TokenTypes.CTOR_DEF, 059 TokenTypes.VARIABLE_DEF, 060 TokenTypes.ENUM_CONSTANT_DEF, 061 TokenTypes.ANNOTATION_FIELD_DEF, 062 TokenTypes.PACKAGE_DEF); 063 064 private final CodeRecognizer codeRecognizer; 065 private Set<TextBlock> comments; 066 067 public CommentedOutCodeLineCheck() { 068 codeRecognizer = new CodeRecognizer(THRESHOLD, new JavaFootprint()); 069 } 070 071 @Override 072 public List<Integer> getWantedTokens() { 073 return WANTED_TOKENS; 074 } 075 076 /** 077 * Creates candidates for commented-out code - all comment blocks. 078 */ 079 @Override 080 public void visitFile(DetailAST ast) { 081 comments = Sets.newHashSet(); 082 for (TextBlock comment : getFileContents().getCppComments().values()) { 083 comments.add(comment); 084 } 085 for (List<TextBlock> listOfComments : getFileContents().getCComments().values()) { 086 // This list contains not only comments in C style, but also documentation comments and JSNI comments 087 comments.addAll(listOfComments); 088 } 089 } 090 091 /** 092 * Removes documentation comments and JSNI comments from candidates for commented-out code in order to prevent false-positives. 093 */ 094 @Override 095 public void visitToken(DetailAST ast) { 096 if (canBeDocumented(ast)) { 097 TextBlock javadoc = getFileContents().getJavadocBefore(ast.getLineNo()); 098 if (javadoc != null) { 099 comments.remove(javadoc); 100 } 101 } 102 removeJSNIComments(ast); 103 } 104 105 /** 106 * From documentation for Javadoc-tool: 107 * Documentation comments should be recognized only when placed 108 * immediately before class, interface, constructor, method, or field declarations. 109 */ 110 private static boolean canBeDocumented(DetailAST ast) { 111 if (AstUtils.isType(ast, TokenTypes.VARIABLE_DEF)) { 112 return AstUtils.isClassVariable(ast); 113 } 114 return true; 115 } 116 117 /** 118 * Detects commented-out code in remaining candidates. 119 */ 120 @Override 121 public void leaveFile(DetailAST ast) { 122 SourceFile sourceFile = (SourceFile) peekSourceCode(); 123 for (TextBlock comment : comments) { 124 String[] lines = comment.getText(); 125 for (int i = 0; i < lines.length; i++) { 126 if (codeRecognizer.isLineOfCode(lines[i])) { 127 CheckMessage message = new CheckMessage(this, "This block of commented-out lines of code should be removed."); 128 message.setLine(comment.getStartLineNo() + i); 129 sourceFile.log(message); 130 break; 131 } 132 } 133 } 134 comments = null; 135 } 136 137 /** 138 * From GWT documentation: 139 * JSNI methods are declared native and contain JavaScript code in a specially formatted comment block 140 * between the end of the parameter list and the trailing semicolon. 141 * A JSNI comment block begins with the exact token {@link #START_JSNI} and ends with the exact token {@link #END_JSNI}. 142 */ 143 private void removeJSNIComments(DetailAST ast) { 144 if (AstUtils.isType(ast, TokenTypes.METHOD_DEF) && AstUtils.isModifier(ast, TokenTypes.LITERAL_NATIVE)) { 145 DetailAST endOfParameterList = ast.findFirstToken(TokenTypes.PARAMETERS).getNextSibling(); 146 DetailAST trailingSemicolon = ast.getLastChild(); 147 148 for (int lineNumber = endOfParameterList.getLineNo(); lineNumber <= trailingSemicolon.getLineNo(); lineNumber++) { 149 List<TextBlock> listOfComments = getFileContents().getCComments().get(lineNumber); 150 if (listOfComments != null) { 151 for (TextBlock comment : listOfComments) { 152 if (isJSNI(comment) && isCommentBetween(comment, endOfParameterList, trailingSemicolon)) { 153 comments.remove(comment); 154 } 155 } 156 } 157 } 158 } 159 } 160 161 private static boolean isCommentBetween(TextBlock comment, DetailAST start, DetailAST end) { 162 return comment.intersects(start.getLineNo(), start.getColumnNo(), end.getLineNo(), end.getColumnNo()); 163 } 164 165 private static final String START_JSNI = "/*-{"; 166 private static final String END_JSNI = "}-*/"; 167 168 private boolean isJSNI(TextBlock comment) { 169 String[] lines = comment.getText(); 170 String firstLine = lines[0]; 171 String lastLine = lines[lines.length - 1]; 172 return StringUtils.startsWith(firstLine, START_JSNI) && StringUtils.endsWith(lastLine, END_JSNI); 173 } 174 175 }