Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 51 additions & 12 deletions cxx-checks/src/main/java/org/sonar/cxx/checks/FileHeaderCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,41 @@
import org.sonar.squidbridge.annotations.ActivatedByDefault;
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
import org.sonar.squidbridge.annotations.SqaleSubCharacteristic;
import org.sonar.squidbridge.api.AnalysisException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Rule(
key = "FileHeader",
name = "Copyright and license headers should be defined in all source files",
priority = Priority.BLOCKER)
priority = Priority.BLOCKER,
tags = {})
@ActivatedByDefault
@SqaleSubCharacteristic(RulesDefinition.SubCharacteristics.READABILITY)
@SqaleConstantRemediation("5min")
//similar Vera++ rule T013 "No copyright notice found"
public class FileHeaderCheck extends SquidCheck<Grammar> implements CxxCharsetAwareVisitor {

private static final String DEFAULT_HEADER_FORMAT = "";

private static final String MESSAGE = "Add or update the header of this file.";

@RuleProperty(
key = "headerFormat",
description = "Expected copyright and license header (plain text)",
type = "TEXT",
defaultValue = DEFAULT_HEADER_FORMAT)
public String headerFormat = DEFAULT_HEADER_FORMAT;

@RuleProperty(
key = "isRegularExpression",
description = "Whether the headerFormat is a regular expression",
defaultValue = "false")
public boolean isRegularExpression = false;

private Charset charset;
private String[] expectedLines;
private Pattern searchPattern = null;

@Override
public void setCharset(Charset charset) {
Expand All @@ -65,23 +78,49 @@ public void setCharset(Charset charset) {

@Override
public void init() {
expectedLines = headerFormat.split("(?:\r)?\n|\r");
if (isRegularExpression) {
if (searchPattern == null) {
try {
searchPattern = Pattern.compile(headerFormat, Pattern.DOTALL);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("[" + getClass().getSimpleName() + "] Unable to compile the regular expression: " + headerFormat, e);
}
}
} else {
expectedLines = headerFormat.split("(?:\r)?\n|\r");
}
}

@Override
public void visitFile(AstNode astNode) {
List<String> lines;
try {
lines = Files.readLines(getContext().getFile(), charset);
} catch (IOException e) {
throw new IllegalStateException(e);
}
if (isRegularExpression) {
String fileContent;
try {
fileContent = Files.toString(getContext().getFile(), charset);
} catch (IOException e) {
throw new AnalysisException(e);
}
checkRegularExpression(fileContent);
} else { List<String> lines;
try {
lines = Files.readLines(getContext().getFile(), charset);
} catch (IOException e) {
throw new IllegalStateException(e);
}

if (!matches(expectedLines, lines)) {
getContext().createFileViolation(this, "Add or update the header of this file.");
if (!matches(expectedLines, lines)) {
getContext().createFileViolation(this, MESSAGE);
}
}
}

private void checkRegularExpression(String fileContent) {
Matcher matcher = searchPattern.matcher(fileContent);
if (!matcher.find() || matcher.start() != 0) {
getContext().createFileViolation(this, MESSAGE);
}
}

private static boolean matches(String[] expectedLines, List<String> lines) {
boolean result;

Expand All @@ -102,5 +141,5 @@ private static boolean matches(String[] expectedLines, List<String> lines) {

return result;
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,20 @@
*/
package org.sonar.cxx.checks;

import java.util.List;

import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.check.Priority;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.cxx.api.CxxMetric;
import org.sonar.cxx.api.CppPunctuator;
import org.sonar.cxx.parser.CxxGrammarImpl;
import org.sonar.cxx.tag.Tag;
import org.sonar.squidbridge.annotations.ActivatedByDefault;
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
import org.sonar.squidbridge.annotations.SqaleSubCharacteristic;
import org.sonar.squidbridge.api.SourceFunction;

import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.Grammar;
import org.sonar.squidbridge.checks.ChecksHelper;
import org.sonar.squidbridge.checks.SquidCheck;

@Rule(key = "TooManyLinesOfCodeInFunction",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test for this check?

Expand All @@ -44,37 +43,48 @@
@SqaleSubCharacteristic(RulesDefinition.SubCharacteristics.READABILITY)
@SqaleConstantRemediation("1h")
public class TooManyLinesOfCodeInFunctionCheck extends SquidCheck<Grammar> {
private static final int DEFAULT_MAXIMUM = 50;
private static final int DEFAULT_MAXIMUM = 200;

@RuleProperty(
key = "max",
description = "Maximum code lines allowed",
defaultValue = "" + DEFAULT_MAXIMUM)
@RuleProperty(
key = "max",
description = "Maximum code lines allowed",
defaultValue = "" + DEFAULT_MAXIMUM)

private int max = DEFAULT_MAXIMUM;
private int max = DEFAULT_MAXIMUM;

@Override
public void init() {
subscribeTo(CxxGrammarImpl.functionDefinition);
}

@Override
public void leaveNode(AstNode node) {
SourceFunction sourceFunction = (SourceFunction) getContext().peekSourceCode();
int lineCount = ChecksHelper.getRecursiveMeasureInt(sourceFunction, CxxMetric.LINES_OF_CODE);
// LINES_OF_CODE not correct == 7 and should be only 5
if (lineCount > max) {
getContext().createLineViolation(this,
"The number of code lines in this function is {0,number,integer} which is greater than {1,number,integer} authorized.",
node,
lineCount,
max);
}
}
@Override
public void init() {
subscribeTo(CxxGrammarImpl.functionBody);
}

public void setMax(int max) {
this.max = max;
}
@Override
public void leaveNode(AstNode node) {
int lineCount = getNumberOfLine(node);
if (lineCount > max) {
getContext().createLineViolation(this,
"The number of code lines in this function is {0,number,integer} which is greater than {1,number,integer} authorized.",
node, lineCount, max);
}
}

}
public static int getNumberOfLine(AstNode node) {
List<AstNode> allChilds = node.getDescendants(CxxGrammarImpl.statement, CppPunctuator.CURLBR_LEFT, CppPunctuator.CURLBR_RIGHT);
int lines = 1;
int firstLine = node.getTokenLine();
if (allChilds != null && !allChilds.isEmpty()) {
int previousLine = firstLine;
for (AstNode child : allChilds) {
if (child.getTokenLine()!= previousLine ) {
lines++;
previousLine = child.getTokenLine();
}
}
}
return lines;
}

public void setMax(int max) {
this.max = max;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@

import java.io.File;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.cxx.CxxAstScanner;
import org.sonar.squidbridge.api.SourceFile;
import org.sonar.squidbridge.checks.CheckMessagesVerifier;

public class FileHeaderCheckTest {

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void test() {
FileHeaderCheck check = new FileHeaderCheck();
Expand Down Expand Up @@ -101,4 +106,59 @@ public void test() {
.noMore();
}

@Test
public void regex() {
FileHeaderCheck check = new FileHeaderCheck();
check.headerFormat = "// copyright \\d\\d\\d";
check.isRegularExpression = true;
SourceFile file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FileHeaderCheck/Regex1.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages()).next().atLine(null).withMessage("Add or update the header of this file.");
// Check that the regular expression is compiled once
check = new FileHeaderCheck();
file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FileHeaderCheck/Regex1.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages()).next().atLine(null).withMessage("Add or update the header of this file.");

check = new FileHeaderCheck();
check.headerFormat = "// copyright \\d{4}\\n// mycompany";
check.isRegularExpression = true;

file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FileHeaderCheck/Regex2.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages()).next().atLine(null).withMessage("Add or update the header of this file.");

check = new FileHeaderCheck();
check.headerFormat = "// copyright \\d{4}\\r?\\n// mycompany";
check.isRegularExpression = true;
file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FileHeaderCheck/Regex3.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages()).noMore();

check = new FileHeaderCheck();
check.headerFormat = "// copyright \\d{4}\\n// mycompany";
check.isRegularExpression = true;
file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FileHeaderCheck/Regex4.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages()).next().atLine(null).withMessage("Add or update the header of this file.");

check = new FileHeaderCheck();
check.headerFormat = "^(?=.*?\\bCopyright\\b)(?=.*?\\bVendor\\b)(?=.*?\\d{4}(-\\d{4})?).*$";
check.isRegularExpression = true;
file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FileHeaderCheck/Regex5.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages()).noMore();

check = new FileHeaderCheck();
file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FileHeaderCheck/Regex6.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages()).next().atLine(null).withMessage("Add or update the header of this file.");

}

@Test
public void should_fail_with_bad_regular_expression() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("[" + FileHeaderCheck.class.getSimpleName() + "] Unable to compile the regular expression: *");

FileHeaderCheck check = new FileHeaderCheck();
check.headerFormat = "*";
check.isRegularExpression = true;
check.init();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class TooManyLinesOfCodeInFunctionCheckTest {

@Test
public void test() {
check.setMax(7);
check.setMax(6);
SourceFile file = CxxAstScanner.scanSingleFile(new File("src/test/resources/checks/FunctionLength.cc"), check);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(21)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// bad copyright

public class Regex1 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// copyright 2005
// mycompan

public class Regex2 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// copyright 2005
// mycompany

public class Regex3 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// prefix
// copyright 2005
// mycompany

public class Regex4 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// prefix
// Copyright (c) Vendor AG 2015-2016
// All Rights Reserved.

public class Regex5 {

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// prefix
// Copyright (c) AG 2015-2016
// All Rights Reserved.

public class Regex5 {

}