From b66783578ec8d52d8c91dcfdcfc15db4474ace41 Mon Sep 17 00:00:00 2001 From: Ludovic Cintrat Date: Sun, 23 Nov 2014 18:36:50 +0100 Subject: [PATCH] Add CxxPublicApiVisitor which counts documented and undocumented public API items. Add UndocumentedApiCheck that creates issue for undocumented API. --- .../java/org/sonar/cxx/checks/CheckList.java | 1 + .../cxx/checks/UndocumentedApiCheck.java | 92 +++ .../resources/org/sonar/l10n/cxx.properties | 1 + .../l10n/cxx/rules/cxx/UndocumentedApi.html | 24 + .../org/sonar/cxx/checks/CheckListTest.java | 2 +- .../cxx/checks/UndocumentedApiCheckTest.java | 57 ++ .../checks/UndocumentedApiCheck/no_doc.h | 79 +++ .../java/org/sonar/cxx/CxxAstScanner.java | 6 +- .../java/org/sonar/cxx/CxxConfiguration.java | 15 + .../java/org/sonar/cxx/api/CxxMetric.java | 4 +- .../visitors/AbstractCxxPublicApiVisitor.java | 540 ++++++++++++++++++ .../cxx/visitors/CxxPublicApiVisitor.java | 106 ++++ .../sonar/cxx/CxxPublicApiVisitorTest.java | 231 ++++++++ .../java/org/sonar/cxx/api/CxxMetricTest.java | 2 +- .../test/resources/metrics/doxygen_example.h | 61 ++ cxx-squid/src/test/resources/metrics/no_doc.h | 79 +++ .../src/test/resources/metrics/public_api.h | 139 +++++ .../src/test/resources/metrics/template.h | 11 + .../java/org/sonar/plugins/cxx/CxxPlugin.java | 2 +- .../plugins/cxx/squid/CxxSquidSensor.java | 3 + sslr-cxx-toolkit/pom.xml | 2 +- 21 files changed, 1451 insertions(+), 6 deletions(-) create mode 100644 cxx-checks/src/main/java/org/sonar/cxx/checks/UndocumentedApiCheck.java create mode 100644 cxx-checks/src/main/resources/org/sonar/l10n/cxx/rules/cxx/UndocumentedApi.html create mode 100644 cxx-checks/src/test/java/org/sonar/cxx/checks/UndocumentedApiCheckTest.java create mode 100644 cxx-checks/src/test/resources/checks/UndocumentedApiCheck/no_doc.h create mode 100644 cxx-squid/src/main/java/org/sonar/cxx/visitors/AbstractCxxPublicApiVisitor.java create mode 100644 cxx-squid/src/main/java/org/sonar/cxx/visitors/CxxPublicApiVisitor.java create mode 100644 cxx-squid/src/test/java/org/sonar/cxx/CxxPublicApiVisitorTest.java create mode 100644 cxx-squid/src/test/resources/metrics/doxygen_example.h create mode 100644 cxx-squid/src/test/resources/metrics/no_doc.h create mode 100644 cxx-squid/src/test/resources/metrics/public_api.h create mode 100644 cxx-squid/src/test/resources/metrics/template.h diff --git a/cxx-checks/src/main/java/org/sonar/cxx/checks/CheckList.java b/cxx-checks/src/main/java/org/sonar/cxx/checks/CheckList.java index 3aa8f2b202..d899352541 100644 --- a/cxx-checks/src/main/java/org/sonar/cxx/checks/CheckList.java +++ b/cxx-checks/src/main/java/org/sonar/cxx/checks/CheckList.java @@ -60,6 +60,7 @@ public static List getChecks() { TooLongLineCheck.class, TooManyLinesOfCodeInFileCheck.class, TooManyStatementsPerLineCheck.class, + UndocumentedApiCheck.class, UnnamedNamespaceInHeaderCheck.class, UselessParenthesesCheck.class, UseCorrectTypeCheck.class, diff --git a/cxx-checks/src/main/java/org/sonar/cxx/checks/UndocumentedApiCheck.java b/cxx-checks/src/main/java/org/sonar/cxx/checks/UndocumentedApiCheck.java new file mode 100644 index 0000000000..4a6467460e --- /dev/null +++ b/cxx-checks/src/main/java/org/sonar/cxx/checks/UndocumentedApiCheck.java @@ -0,0 +1,92 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2011 Waleri Enns and CONTACT Software GmbH + * dev@sonar.codehaus.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.cxx.checks; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.check.Priority; +import org.sonar.check.Rule; +import org.sonar.cxx.visitors.AbstractCxxPublicApiVisitor; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.api.Token; + +/** + * Check that generates issue for undocumented API items.
+ * Following items are counted as public API: + * + *

+ * Public API items are considered documented if they have Doxygen comments.
+ * Function arguments are not counted since they can be documented in function + * documentation and this visitor does not parse Doxygen comments.
+ * This visitor should be applied only on header files.
+ * Currently, no filtering is applied using preprocessing directive.
+ *

+ * Limitation: only "in front of the declaration" comments are considered. + * + * @see + * Doxygen Manual: Documenting the code + * + * @author Ludovic Cintrat + * + * @param + */ +@Rule(key = "UndocumentedApi", + description = "All public APIs should be documented", + priority = Priority.MINOR) +public class UndocumentedApiCheck extends AbstractCxxPublicApiVisitor { + + private static final Logger LOG = LoggerFactory + .getLogger("UndocumentedApiCheck"); + + private static final List DEFAULT_NAME_SUFFIX = Arrays.asList(".h", + ".hh", ".hpp", ".H"); + + public UndocumentedApiCheck() { + super(); + withHeaderFileSuffixes(DEFAULT_NAME_SUFFIX); + } + + @Override + protected void onPublicApi(AstNode node, String id, List comments) { + boolean commented = !comments.isEmpty(); + + LOG.debug("node: " + node.getType() + " line: " + node.getTokenLine() + + " id: '" + id + "' documented: " + commented); + + if (!commented) { + getContext().createLineViolation(this, "Undocumented API: " + id, + node); + } + } +} diff --git a/cxx-checks/src/main/resources/org/sonar/l10n/cxx.properties b/cxx-checks/src/main/resources/org/sonar/l10n/cxx.properties index 6ddd122fd5..86fafbc965 100644 --- a/cxx-checks/src/main/resources/org/sonar/l10n/cxx.properties +++ b/cxx-checks/src/main/resources/org/sonar/l10n/cxx.properties @@ -44,6 +44,7 @@ rule.cxx.TooManyLinesOfCodeInFile.param.max=Maximum code lines allowed. rule.cxx.TooManyStatementsPerLine.name=Statements should be on separate lines rule.cxx.TooManyStatementsPerLine.param.excludeCaseBreak=Exclude 'break' statement if it is on the same line as the switch label (case: or default:). rule.cxx.UnnamedNamespaceInHeader.name=Unnamed namespaces are not allowed in header files +rule.cxx.UndocumentedApi.name=Public APIs should be documented rule.cxx.UseCorrectType.name=C++ type(s) shall be used rule.cxx.UseCorrectType.param.message=Use C++ types whenever possible rule.cxx.UseCorrectType.param.regularExpression=Type regular expression rule diff --git a/cxx-checks/src/main/resources/org/sonar/l10n/cxx/rules/cxx/UndocumentedApi.html b/cxx-checks/src/main/resources/org/sonar/l10n/cxx/rules/cxx/UndocumentedApi.html new file mode 100644 index 0000000000..c275564c4a --- /dev/null +++ b/cxx-checks/src/main/resources/org/sonar/l10n/cxx/rules/cxx/UndocumentedApi.html @@ -0,0 +1,24 @@ +

+Public APIs have to be documented in order to be used by customers.
+APIs documentation is typically generated using a tool, such as Doxygen. +

+ +

The following code illustrates a well documented class:

+ +
+/**
+  class documentation
+/*
+class DocumentedClass {
+  public:
+    int commentedVar; ///< documentation
+
+    /**
+      apiMethod documentation
+     */
+    void apiMethod();
+
+  private:
+    void notInApiMethod();
+};
+
diff --git a/cxx-checks/src/test/java/org/sonar/cxx/checks/CheckListTest.java b/cxx-checks/src/test/java/org/sonar/cxx/checks/CheckListTest.java index 102c17c425..96d0537e5f 100644 --- a/cxx-checks/src/test/java/org/sonar/cxx/checks/CheckListTest.java +++ b/cxx-checks/src/test/java/org/sonar/cxx/checks/CheckListTest.java @@ -27,6 +27,6 @@ public class CheckListTest { @Test public void count() { - assertThat(CheckList.getChecks().size()).isEqualTo(33); + assertThat(CheckList.getChecks().size()).isEqualTo(34); } } diff --git a/cxx-checks/src/test/java/org/sonar/cxx/checks/UndocumentedApiCheckTest.java b/cxx-checks/src/test/java/org/sonar/cxx/checks/UndocumentedApiCheckTest.java new file mode 100644 index 0000000000..ddbd167408 --- /dev/null +++ b/cxx-checks/src/test/java/org/sonar/cxx/checks/UndocumentedApiCheckTest.java @@ -0,0 +1,57 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2011 Waleri Enns and CONTACT Software GmbH + * dev@sonar.codehaus.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.cxx.checks; + +import java.io.File; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.squid.api.CheckMessage; +import org.sonar.squid.api.SourceFile; + +import static org.fest.assertions.Assertions.assertThat; + +import com.sonar.sslr.squid.checks.CheckMessagesVerifierRule; + +public class UndocumentedApiCheckTest { + + @Rule + public CheckMessagesVerifierRule checkMessagesVerifier = new CheckMessagesVerifierRule(); + + @SuppressWarnings("unchecked") + @Test + public void detected() { + SourceFile file = CxxAstScanner.scanSingleFile(new File( + "src/test/resources/checks/UndocumentedApiCheck/no_doc.h"), new UndocumentedApiCheck()); + + checkMessagesVerifier.verify(file.getCheckMessages()).next().atLine(6) + .next().atLine(11).next().atLine(13).next().atLine(14).next() + .atLine(17).next().atLine(19).next().atLine(21).next() + .atLine(23).next().atLine(26).next().atLine(48).next() + .atLine(52).next().atLine(53).next().atLine(56).next() + .atLine(58).next().atLine(60).next().atLine(63).next() + .atLine(65).next().atLine(68).next().atLine(74).next().atLine(77); + + for (CheckMessage msg : file.getCheckMessages()) { + assertThat(msg.formatDefaultMessage()).isNotEmpty(); + } + } +} diff --git a/cxx-checks/src/test/resources/checks/UndocumentedApiCheck/no_doc.h b/cxx-checks/src/test/resources/checks/UndocumentedApiCheck/no_doc.h new file mode 100644 index 0000000000..b3c38eced2 --- /dev/null +++ b/cxx-checks/src/test/resources/checks/UndocumentedApiCheck/no_doc.h @@ -0,0 +1,79 @@ +#define TEST_MACRO() \ + macro + +#define EXPORT + +class testClass +{ + void defaultMethod(); + +public: + void publicMethod(); + + enum classEnum { + classEnumValue + } + + enumVar; + + EXPORT int publicAttribute; + + int inlineCommentedAttr; + + void inlinePublicMethod(); + +protected: + virtual void protectedMethod(); + +private: + void privateMethod(); + + enum privateEnum { + privateEnumVal + }; + + struct privateStruct { + int privateStructField; + }; + + class privateClass { + int i; + }; + + union privateUnion { + int u; + }; + +public: + int inlineCommentedLastAttr; +}; + + +struct testStruct { + int testField; +}; + +extern int globalVar; + +void testFunction(); + +enum emptyEnum +{}; + +enum testEnum +{ + enum_val +}; + +union testUnion +{ + +}; + +template +struct tmplStruct +{}; + +void func() { + for (int i = 0; i < 10; i++) {} +} diff --git a/cxx-squid/src/main/java/org/sonar/cxx/CxxAstScanner.java b/cxx-squid/src/main/java/org/sonar/cxx/CxxAstScanner.java index c039b39271..9548935c67 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/CxxAstScanner.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/CxxAstScanner.java @@ -25,7 +25,6 @@ import com.sonar.sslr.api.Token; import com.sonar.sslr.impl.Parser; import com.sonar.sslr.api.Grammar; - import com.sonar.sslr.squid.AstScanner; import com.sonar.sslr.squid.SourceCodeBuilderCallback; import com.sonar.sslr.squid.SourceCodeBuilderVisitor; @@ -35,6 +34,7 @@ import com.sonar.sslr.squid.metrics.ComplexityVisitor; import com.sonar.sslr.squid.metrics.CounterVisitor; import com.sonar.sslr.squid.metrics.LinesVisitor; + import org.sonar.cxx.api.CxxKeyword; import org.sonar.cxx.api.CxxMetric; import org.sonar.cxx.api.CxxPunctuator; @@ -44,6 +44,7 @@ import org.sonar.cxx.visitors.CxxFileVisitor; import org.sonar.cxx.visitors.CxxLinesOfCodeVisitor; import org.sonar.cxx.visitors.CxxParseErrorLoggerVisitor; +import org.sonar.cxx.visitors.CxxPublicApiVisitor; import org.sonar.squid.api.SourceClass; import org.sonar.squid.api.SourceCode; import org.sonar.squid.api.SourceFile; @@ -153,6 +154,9 @@ public SourceCode createSourceCode(SourceCode parentSourceCode, AstNode astNode) /* Metrics */ builder.withSquidAstVisitor(new LinesVisitor(CxxMetric.LINES)); builder.withSquidAstVisitor(new CxxLinesOfCodeVisitor(CxxMetric.LINES_OF_CODE)); + builder.withSquidAstVisitor(new CxxPublicApiVisitor(CxxMetric.PUBLIC_API, + CxxMetric.PUBLIC_UNDOCUMENTED_API) + .withHeaderFileSuffixes(conf.getHeaderFileSuffixes())); builder.withSquidAstVisitor(CommentsVisitor. builder().withCommentMetric(CxxMetric.COMMENT_LINES) .withNoSonar(true) diff --git a/cxx-squid/src/main/java/org/sonar/cxx/CxxConfiguration.java b/cxx-squid/src/main/java/org/sonar/cxx/CxxConfiguration.java index 8abb27ba86..962c53c59b 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/CxxConfiguration.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/CxxConfiguration.java @@ -32,6 +32,7 @@ public class CxxConfiguration extends SquidConfiguration { private List defines = new ArrayList(); private List includeDirectories = new ArrayList(); private List forceIncludeFiles = new ArrayList(); + private List headerFileSuffixes = new ArrayList(); private String baseDir; private boolean errorRecoveryEnabled = true; @@ -107,4 +108,18 @@ public void setErrorRecoveryEnabled(boolean errorRecoveryEnabled){ public boolean getErrorRecoveryEnabled(){ return this.errorRecoveryEnabled; } + + public void setHeaderFileSuffixes(List headerFileSuffixes) { + this.headerFileSuffixes = headerFileSuffixes; + } + + public void setHeaderFileSuffixes(String[] headerFileSuffixes) { + if (headerFileSuffixes != null) { + setHeaderFileSuffixes(Arrays.asList(headerFileSuffixes)); + } + } + + public List getHeaderFileSuffixes() { + return this.headerFileSuffixes; + } } diff --git a/cxx-squid/src/main/java/org/sonar/cxx/api/CxxMetric.java b/cxx-squid/src/main/java/org/sonar/cxx/api/CxxMetric.java index 749fae4572..784368796a 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/api/CxxMetric.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/api/CxxMetric.java @@ -31,7 +31,9 @@ public enum CxxMetric implements MetricDef { CLASSES, COMPLEXITY, COMMENT_LINES, - COMMENT_BLANK_LINES; + COMMENT_BLANK_LINES, + PUBLIC_API, + PUBLIC_UNDOCUMENTED_API; public String getName() { return name(); diff --git a/cxx-squid/src/main/java/org/sonar/cxx/visitors/AbstractCxxPublicApiVisitor.java b/cxx-squid/src/main/java/org/sonar/cxx/visitors/AbstractCxxPublicApiVisitor.java new file mode 100644 index 0000000000..5aa3feba82 --- /dev/null +++ b/cxx-squid/src/main/java/org/sonar/cxx/visitors/AbstractCxxPublicApiVisitor.java @@ -0,0 +1,540 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2011 Waleri Enns and CONTACT Software GmbH + * dev@sonar.codehaus.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.cxx.visitors; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.cxx.api.CxxKeyword; +import org.sonar.cxx.api.CxxPunctuator; +import org.sonar.cxx.parser.CxxGrammarImpl; + +import com.sonar.sslr.api.AstAndTokenVisitor; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.GenericTokenType; +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; +import com.sonar.sslr.impl.ast.AstXmlPrinter; +import com.sonar.sslr.squid.checks.SquidCheck; + +/** + * Abstract visitor that visits public API items.
+ * Following items are considered as public API: + *
    + *
  • classes/structures
  • + *
  • class members (public and protected)
  • + *
  • structure members
  • + *
  • enumerations
  • + *
  • enumeration values
  • + *
  • typedefs
  • + *
  • functions
  • + *
  • variables
  • + *
+ *

+ * Public API items are considered documented if they have Doxygen comments.
+ * Function arguments are not counted since they can be documented in function + * documentation and this visitor does not parse Doxygen comments.
+ * This visitor should be applied only on header files.
+ * Currently, no filtering is applied using preprocessing directive, + * e.g #define DLLEXPORT.
+ *

+ * Limitation: only "in front of the declaration" comments are considered. + * + * @see + * Doxygen Manual: Documenting the code + * + * @author Ludovic Cintrat + * + * @param + */ +// @Rule(key = "UndocumentedApi", description = +// "All public APIs should be documented", priority = Priority.MINOR) +public abstract class AbstractCxxPublicApiVisitor + extends SquidCheck implements AstAndTokenVisitor { + + abstract protected void onPublicApi(AstNode node, String id, + List comments); + + private static final Logger LOG = LoggerFactory + .getLogger("AbstractCxxPublicApiVisitor"); + + private static final boolean DEBUG = false; + /** + * Dump the AST of the file is true. + */ + private static final boolean DUMP = false; + + public interface PublicApiHandler { + void onPublicApi(AstNode node, String id, List comments); + }; + + private List headerFileSuffixes; + + private boolean skipFile = true; + + @Override + public void init() { + subscribeTo(CxxGrammarImpl.classSpecifier); + subscribeTo(CxxGrammarImpl.memberDeclaration); + subscribeTo(CxxGrammarImpl.functionDefinition); + subscribeTo(CxxGrammarImpl.enumSpecifier); + subscribeTo(CxxGrammarImpl.initDeclaratorList); + } + + @Override + public void visitFile(AstNode astNode) { + logDebug("API File: " + getContext().getFile().getName()); + + logDebug("Header file suffixes: " + headerFileSuffixes); + + skipFile = true; + + if (headerFileSuffixes != null) + for (String suffix : headerFileSuffixes) { + if (getContext().getFile().getName().endsWith(suffix)) { + skipFile = false; + break; + } + } + + if (DUMP) { + System.out.println(AstXmlPrinter.print(astNode)); + } + + } + + public void visitToken(Token token) { + } + + @Override + public void visitNode(AstNode astNode) { + if (skipFile) + return; + + logDebug("***** Node: " + astNode); + + switch ((CxxGrammarImpl) astNode.getType()) { + case classSpecifier: + visitClassSpecifier(astNode); + break; + case memberDeclaration: + visitMemberDeclaration(astNode); + break; + case functionDefinition: + visitFunctionDefinition(astNode); + break; + case enumSpecifier: + visitEnumSpecifier(astNode); + break; + case initDeclaratorList: + visitDeclaratorList(astNode); + break; + default: + // should not happen + LOG.error("Visiting unknown node: " + astNode.getType()); + break; + } + } + + private void visitPublicApi(AstNode node, String id, List comments) { + List doxygenComments = new ArrayList(); + + for (Token token : comments) { + String comment = token.getValue(); + if (isDoxygenInlineComment(comment) + || isDoxygenCommentBlock(comment)) { + doxygenComments.add(token); + logDebug("Doc: " + comment.replace("\r\n", "")); + } + } + + logDebug("Public API: " + id); + onPublicApi(node, id, doxygenComments); + } + + private void visitDeclaratorList(AstNode declaratorList) { + + // do not handle declaration in function body + if (declaratorList.getFirstAncestor(CxxGrammarImpl.functionBody) != null) + return; + + AstNode declaration = declaratorList + .getFirstAncestor(CxxGrammarImpl.declaration); + + LOG.trace("declaration: " + declaration); + + List declarators = declaratorList + .getChildren(CxxGrammarImpl.initDeclarator); + + if (declarators.size() == 1) { + visitDeclarator(declaration); + } else { + for (AstNode declarator : declarators) { + visitDeclarator(declarator); + } + } + } + + private void visitDeclarator(AstNode declarator) { + // look for block documentation + List comments = getBlockDocumentation(declarator); + + // documentation may be inlined + if (comments.size() == 0) { + comments = getDeclaratorInlineComment(declarator); + } + + AstNode declaratorId = declarator + .getFirstDescendant(CxxGrammarImpl.declaratorId); + + if (declaratorId == null) { + LOG.error("null declaratorId: " + AstXmlPrinter.print(declarator)); + } else + visitPublicApi(declaratorId, declaratorId.getTokenValue(), comments); + } + + private void visitClassSpecifier(AstNode classSpecifier) { + + if (!isPublicApiMember(classSpecifier)) { + logDebug(classSpecifier + .getFirstDescendant(CxxGrammarImpl.className) + .getTokenValue() + + " not in public API"); + return; + } + + AstNode template = classSpecifier + .getFirstAncestor(CxxGrammarImpl.templateDeclaration); + AstNode docNode = (template != null) ? template : classSpecifier; + + AstNode id = classSpecifier + .getFirstDescendant(CxxGrammarImpl.className); + + visitPublicApi(id, id.getTokenValue(), getBlockDocumentation(docNode)); + } + + private void visitMemberDeclaration(AstNode memberDeclaration) { + + AstNode declaratorList = memberDeclaration + .getFirstDescendant(CxxGrammarImpl.memberDeclaratorList); + + if (!isPublicApiMember(memberDeclaration)) { + // if not part of the API, nothing to measure + return; + } + + AstNode subclassSpecifier = memberDeclaration + .getFirstDescendant(CxxGrammarImpl.classSpecifier); + + if (subclassSpecifier != null) { + // sub classes are handled by subscription + return; + } + + AstNode functionDef = memberDeclaration + .getFirstDescendant(CxxGrammarImpl.functionDefinition); + + if (functionDef != null) { + // functionDef are handled by subscription + return; + } + + if (declaratorList != null) { + List declarators = declaratorList + .getChildren(CxxGrammarImpl.memberDeclarator); + + // if only one declarator, the doc should be placed before the + // memberDeclaration, or inlined + if (declarators.size() == 1) { + visitMemberDeclarator(memberDeclaration); + } + // if several declarators, doc should be placed before each + // declarator, or inlined + else { + for (AstNode declarator : declarators) { + visitMemberDeclarator(declarator); + } + } + } + } + + private void visitFunctionDefinition(AstNode functionDef) { + visitMemberDeclarator(functionDef); + } + + private void visitMemberDeclarator(AstNode node) { + + AstNode container = node.getFirstAncestor( + CxxGrammarImpl.templateDeclaration, + CxxGrammarImpl.classSpecifier); + + AstNode docNode; + + if (container == null + || container.getType() == CxxGrammarImpl.classSpecifier) { + docNode = node; + } else { + docNode = (container != null) ? container : node; + } + + // look for block documentation + List comments = getBlockDocumentation(docNode); + + // documentation may be inlined + if (comments.size() == 0) { + comments = getDeclaratorInlineComment(node); + } + + AstNode idNode = node.getFirstDescendant(CxxGrammarImpl.declaratorId); + String id; + + AstNode operatorFunctionId = node + .getFirstDescendant(CxxGrammarImpl.operatorFunctionId); + + if (operatorFunctionId != null) { + id = getOperatorId(operatorFunctionId); + } else { + id = idNode.getTokenValue(); + } + + visitPublicApi(idNode, id, comments); + } + + private void visitEnumSpecifier(AstNode enumSpecifierNode) { + if (!isPublicApiMember(enumSpecifierNode)) { + logDebug(enumSpecifierNode.getFirstDescendant( + GenericTokenType.IDENTIFIER).getTokenValue() + + " not in public API"); + return; + } + + visitPublicApi( + enumSpecifierNode, + enumSpecifierNode.getFirstDescendant( + GenericTokenType.IDENTIFIER).getTokenValue(), + getBlockDocumentation(enumSpecifierNode)); + + // deal with enumeration values + AstNode enumeratorList = enumSpecifierNode + .getFirstDescendant(CxxGrammarImpl.enumeratorList); + + if (enumeratorList != null) { + for (AstNode definition : enumeratorList + .getChildren(CxxGrammarImpl.enumeratorDefinition)) { + + // look for block documentation + List comments = getBlockDocumentation(definition); + + // look for inlined doc + if (comments.size() == 0) { + AstNode next = definition.getNextAstNode(); + + // inline documentation may be on the next definition token + // or next curly brace + if (next != null) { + // discard COMMA + if (next.getToken().getType() == CxxPunctuator.COMMA) { + next = next.getNextAstNode(); + } + + comments = getInlineDocumentation(next.getToken(), + definition.getTokenLine()); + } + } + + visitPublicApi( + definition, + definition.getFirstDescendant( + GenericTokenType.IDENTIFIER).getTokenValue(), + comments); + } + } + } + + // XXX may go to a utility class + private String getOperatorId(AstNode operatorFunctionId) { + + StringBuilder builder = new StringBuilder( + operatorFunctionId.getTokenValue()); + AstNode operator = operatorFunctionId + .getFirstDescendant(CxxGrammarImpl.operator); + + if (operator != null) { + + AstNode opNode = operator.getFirstChild(); + while (opNode != null) { + builder.append(opNode.getTokenValue()); + opNode = opNode.getNextSibling(); + } + } + + return builder.toString(); + } + + private static List getDeclaratorInlineComment(AstNode declarator) { + List comments; + + // inline comments are attached to the next AST node (not sibling, + // because the last attribute inline comment is attached to the next + // node of the parent) + AstNode next = declarator.getNextAstNode(); + + // inline documentation may be on the next definition token + // or next curly brace + if (next != null) { + // discard COMMA and SEMICOLON + if (next.getToken().getType() == CxxPunctuator.COMMA + || next.getToken().getType() == CxxPunctuator.SEMICOLON) { + next = next.getNextAstNode(); + } + + comments = getInlineDocumentation(next.getToken(), + declarator.getTokenLine()); + } else { + // could happen on parse error ? + comments = new ArrayList(); + } + + return comments; + } + + private static boolean isPublicApiMember(AstNode node) { + AstNode access = node; + + // retrieve the accessSpecifier + do { + access = access.getPreviousAstNode(); + } while (access != null + && access.getType() != CxxGrammarImpl.accessSpecifier); + + if (access != null) { + return access.getToken().getType() == CxxKeyword.PUBLIC + || access.getToken().getType() == CxxKeyword.PROTECTED; + } else { + AstNode classSpecifier = node + .getFirstAncestor(CxxGrammarImpl.classSpecifier); + + if (classSpecifier != null) { + + AstNode enclosingSpecifierNode = classSpecifier + .getFirstDescendant(CxxKeyword.STRUCT, + CxxKeyword.CLASS, CxxKeyword.ENUM); + + if (enclosingSpecifierNode != null) { + switch ((CxxKeyword) enclosingSpecifierNode.getToken() + .getType()) { + case STRUCT: + // struct members have public access, thus access level + // is the access level of the enclosing classSpecifier + return isPublicApiMember(classSpecifier); + case CLASS: + // default access in classes is private + return false; + default: + LOG.error("isPublicApiMember unhandled case: " + + enclosingSpecifierNode.getType()); + return false; + } + } else { + LOG.error("isPublicApiMember: not a member"); + return false; + } + } + + // global member + return true; + } + } + + /** + * Check if inline Doxygen documentation is attached to the given token at + * specified line + * + * @param token + * the token to inspect + * @param line + * line of the inlined documentation + * @return true if documentation is found for specified line, false + * otherwise + */ + private static List getInlineDocumentation(Token token, int line) { + List comments = new ArrayList(); + + for (Trivia trivia : token.getTrivia()) { + if (trivia.isComment()) { + Token triviaToken = trivia.getToken(); + if (triviaToken != null) + if (triviaToken.getLine() == line) { + if (isDoxygenInlineComment(triviaToken.getValue())) { + comments.add(triviaToken); + LOG.trace("Inline doc: " + triviaToken.getValue()); + } + } + } + } + return comments; + } + + private static List getBlockDocumentation(AstNode node) { + List commentTokens = new ArrayList(); + + Token token = node.getToken(); + for (Trivia trivia : token.getTrivia()) { + if (trivia.isComment()) { + Token triviaToken = trivia.getToken(); + if (triviaToken != null) { + String comment = triviaToken.getValue(); + LOG.trace("Doc: {}\n", comment); + if (isDoxygenCommentBlock(comment) + && !isDoxygenInlineComment(comment)) + commentTokens.add(triviaToken); + } + } + } + + return commentTokens; + } + + private static boolean isDoxygenInlineComment(String comment) { + + return comment.startsWith("/*!<") || comment.startsWith("/**<") + || comment.startsWith("//!<") || comment.startsWith("///<"); + } + + private static boolean isDoxygenCommentBlock(String comment) { + + return comment.startsWith("/**") || comment.startsWith("/*!") + || comment.startsWith("///") || comment.startsWith("//!"); + } + + private void logDebug(String msg) { + if (DEBUG) + LOG.debug(msg); + } + + public AbstractCxxPublicApiVisitor withHeaderFileSuffixes( + List headerFileSuffixes) { + this.headerFileSuffixes = headerFileSuffixes; + return this; + } +} diff --git a/cxx-squid/src/main/java/org/sonar/cxx/visitors/CxxPublicApiVisitor.java b/cxx-squid/src/main/java/org/sonar/cxx/visitors/CxxPublicApiVisitor.java new file mode 100644 index 0000000000..d9cedfcdce --- /dev/null +++ b/cxx-squid/src/main/java/org/sonar/cxx/visitors/CxxPublicApiVisitor.java @@ -0,0 +1,106 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2011 Waleri Enns and CONTACT Software GmbH + * dev@sonar.codehaus.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.cxx.visitors; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.squid.measures.MetricDef; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.api.Token; + +/** + * Visitor that counts documented and undocumented API items.
+ * Following items are counted as public API: + *

    + *
  • classes/structures
  • + *
  • class members (public and protected)
  • + *
  • structure members
  • + *
  • enumerations
  • + *
  • enumeration values
  • + *
  • typedefs
  • + *
  • functions
  • + *
  • variables
  • + *
+ *

+ * Public API items are considered documented if they have Doxygen comments.
+ * Function arguments are not counted since they can be documented in function + * documentation and this visitor does not parse Doxygen comments.
+ * This visitor should be applied only on header files.
+ * Currently, no filtering is applied using preprocessing directive.
+ *

+ * Limitation: only "in front of the declaration" comments are considered. + * + * @see + * Doxygen Manual: Documenting the code + * + * @author Ludovic Cintrat + * + * @param + */ +// @Rule(key = "UndocumentedApi", description = +// "All public APIs should be documented", priority = Priority.MINOR) +public class CxxPublicApiVisitor extends + AbstractCxxPublicApiVisitor { + + private static final Logger LOG = LoggerFactory + .getLogger("CxxPublicApiVisitor"); + + private final MetricDef undocumented; + private final MetricDef api; + + public interface PublicApiHandler { + void onPublicApi(AstNode node, String id, List comments); + }; + + private PublicApiHandler handler; + + public CxxPublicApiVisitor(MetricDef publicDocumentedApi, + MetricDef publicUndocumentedApi) { + super(); + api = publicDocumentedApi; + undocumented = publicUndocumentedApi; + } + + @Override + protected void onPublicApi(AstNode node, String id, List comments) { + boolean commented = !comments.isEmpty(); + + LOG.debug("node: " + node.getType() + " line: " + node.getTokenLine() + + " id: '" + id + "' documented: " + commented); + + if (handler != null) { + handler.onPublicApi(node, id, comments); + } + + if (!commented) { + getContext().peekSourceCode().add(undocumented, 1); + } + + getContext().peekSourceCode().add(api, 1); + } + + public void setHandler(PublicApiHandler handler) { + this.handler = handler; + } +} diff --git a/cxx-squid/src/test/java/org/sonar/cxx/CxxPublicApiVisitorTest.java b/cxx-squid/src/test/java/org/sonar/cxx/CxxPublicApiVisitorTest.java new file mode 100644 index 0000000000..43e9f89075 --- /dev/null +++ b/cxx-squid/src/test/java/org/sonar/cxx/CxxPublicApiVisitorTest.java @@ -0,0 +1,231 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2011 Waleri Enns and CONTACT Software GmbH + * dev@sonar.codehaus.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.cxx; + +import static org.fest.assertions.Assertions.assertThat; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.fest.assertions.Fail; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.cxx.api.CxxMetric; +import org.sonar.cxx.visitors.CxxPublicApiVisitor; +import org.sonar.cxx.visitors.CxxPublicApiVisitor.PublicApiHandler; +import org.sonar.squid.api.SourceFile; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.api.Token; + +public class CxxPublicApiVisitorTest { + + private static final Logger LOG = LoggerFactory + .getLogger("CxxPublicApiVisitorTest"); + + private static String getFileExtension(String fileName) { + int lastIndexOf = fileName.lastIndexOf("."); + if (lastIndexOf == -1) { + return ""; + } + return fileName.substring(lastIndexOf); + } + + /** + * Check that CxxPublicApiVisitor correctly counts API for given file. + * + * @param fileName + * the file to use for test + * @param expectedApi + * expected number of API + * @param expectedUndoc + * expected number of undocumented API + * @param checkDouble + * if true, fails the test if two items with the same id are + * counted.. + */ + @SuppressWarnings("unchecked") + private void testFile(String fileName, int expectedApi, int expectedUndoc, + boolean checkDouble) { + + CxxPublicApiVisitor visitor = new CxxPublicApiVisitor( + CxxMetric.PUBLIC_API, CxxMetric.PUBLIC_UNDOCUMENTED_API); + + if (checkDouble) { + final Map> idCommentMap = new HashMap>(); + + visitor.setHandler(new PublicApiHandler() { + public void onPublicApi(AstNode node, String id, + List comments) { + if (idCommentMap.containsKey(id)) + Fail.fail("DOUBLE ID: " + id); + + // store and compare later in order to not break the parsing + idCommentMap.put(id, comments); + } + }); + } + + visitor.withHeaderFileSuffixes(Arrays + .asList(getFileExtension(fileName))); + + SourceFile file = CxxAstScanner.scanSingleFile(new File(fileName), + visitor); + + LOG.debug("#API: " + file.getInt(CxxMetric.PUBLIC_API) + " UNDOC: " + + file.getInt(CxxMetric.PUBLIC_UNDOCUMENTED_API)); + + assertThat(file.getInt(CxxMetric.PUBLIC_API)).isEqualTo(expectedApi); + assertThat(file.getInt(CxxMetric.PUBLIC_UNDOCUMENTED_API)).isEqualTo( + expectedUndoc); + } + + @SuppressWarnings("unchecked") + @Test + public void test_no_matching_suffix() { + // this file does contain public API + SourceFile file = CxxAstScanner.scanSingleFile(new File( + "src/test/resources/metrics/doxygen_example.h"), + new CxxPublicApiVisitor(CxxMetric.PUBLIC_API, + CxxMetric.PUBLIC_UNDOCUMENTED_API) + .withHeaderFileSuffixes(Arrays.asList(".hpp"))); + + assertThat(file.getInt(CxxMetric.PUBLIC_API)).isEqualTo(0); + assertThat(file.getInt(CxxMetric.PUBLIC_UNDOCUMENTED_API)).isEqualTo(0); + } + + @Test + public void doxygen_example() { + testFile("src/test/resources/metrics/doxygen_example.h", 13, 0, false); + } + + @Test + public void to_delete() { + testFile("src/test/resources/metrics/public_api.h", 34, 0, true); + } + + @Test + public void no_doc() { + testFile("src/test/resources/metrics/no_doc.h", 20, 20, true); + } + + @Test + public void template() { + testFile("src/test/resources/metrics/template.h", 3, 2, true); + } + + @SuppressWarnings("unchecked") + @Test + public void public_api() { + CxxPublicApiVisitor visitor = new CxxPublicApiVisitor( + CxxMetric.PUBLIC_API, CxxMetric.PUBLIC_UNDOCUMENTED_API); + + final Map> idCommentMap = new HashMap>(); + + visitor.setHandler(new PublicApiHandler() { + public void onPublicApi(AstNode node, String id, + List comments) { + if (idCommentMap.containsKey(id)) + Fail.fail("DOUBLE ID: " + id); + + // store and compare later in order to not break the parsing + idCommentMap.put(id, comments); + } + }); + + visitor.withHeaderFileSuffixes(Arrays.asList(".h")); + + SourceFile file = CxxAstScanner.scanSingleFile(new File( + "src/test/resources/metrics/public_api.h"), visitor); // + + LOG.debug("DOC: " + file.getInt(CxxMetric.PUBLIC_API) + " UNDOC: " + + file.getInt(CxxMetric.PUBLIC_UNDOCUMENTED_API)); + + final Map expectedIdCommentMap = new HashMap(); + + expectedIdCommentMap.put("publicMethod", "publicMethod"); + expectedIdCommentMap.put("testStruct", "testStruct"); + expectedIdCommentMap.put("testUnion", "testUnion"); + expectedIdCommentMap.put("inlineCommentedAttr", "inlineCommentedAttr"); + expectedIdCommentMap.put("inlineCommentedLastAttr", + "inlineCommentedLastAttr"); + expectedIdCommentMap.put("enumVar", "classEnum"); // only one + // declarator, then + // doc should precede + // decl + expectedIdCommentMap.put("classEnum", "classEnum"); + expectedIdCommentMap.put("classEnumValue", "classEnumValue"); + expectedIdCommentMap.put("protectedMethod", "protectedMethod"); + expectedIdCommentMap.put("testTypeDef", "testTypeDef"); + expectedIdCommentMap.put("testField", "testField"); + expectedIdCommentMap.put("inlinePublicMethod", "inlinePublicMethod"); + expectedIdCommentMap.put("publicAttribute", "publicAttribute"); + expectedIdCommentMap.put("testEnum", "testEnum"); + expectedIdCommentMap.put("testClass", "testClass"); + expectedIdCommentMap.put("enum_val", "enum_val"); + expectedIdCommentMap.put("testFunction", "testFunction"); + expectedIdCommentMap.put("testFunction2", "testFunction2"); + expectedIdCommentMap.put("globalVar", "globalVar"); + expectedIdCommentMap.put("globalVarInline", "globalVarInline"); + expectedIdCommentMap.put("globalVar1", "globalVar1"); + expectedIdCommentMap.put("globalVar2", "globalVar2"); + expectedIdCommentMap.put("globalVar3", "globalVar3"); + expectedIdCommentMap.put("testType", "testType"); + expectedIdCommentMap.put("enumVar1", "enumVar1"); + expectedIdCommentMap.put("enumVar2", "enumVar2"); + expectedIdCommentMap.put("attr1", "attr1"); + expectedIdCommentMap.put("attr2", "attr2"); + expectedIdCommentMap.put("lastVar", "lastVar"); + expectedIdCommentMap.put("protectedStruct", "protectedStruct"); + expectedIdCommentMap + .put("protectedStructField", "protectedStructField"); + expectedIdCommentMap.put("protectedStructField2", + "protectedStructField2"); + expectedIdCommentMap.put("protectedClass", "protectedClass"); + expectedIdCommentMap.put("operator[]", "operator"); + + // check completeness + for (final String id : expectedIdCommentMap.keySet()) { + LOG.debug("id: " + id); + List comments = idCommentMap.get(id); + assertThat(comments).isNotEmpty(); + assertThat(comments.get(0).getValue()).contains( + expectedIdCommentMap.get(id)); + assertThat(idCommentMap.keySet()).contains(id); + } + + // check correction + for (final String id : idCommentMap.keySet()) { + LOG.debug("id: " + id); + List comments = idCommentMap.get(id); + assertThat(comments).isNotEmpty(); + assertThat(expectedIdCommentMap.keySet()).contains(id); + } + + assertThat(file.getInt(CxxMetric.PUBLIC_API)).isEqualTo( + expectedIdCommentMap.keySet().size()); + assertThat(file.getInt(CxxMetric.PUBLIC_UNDOCUMENTED_API)).isEqualTo(0); + } +} diff --git a/cxx-squid/src/test/java/org/sonar/cxx/api/CxxMetricTest.java b/cxx-squid/src/test/java/org/sonar/cxx/api/CxxMetricTest.java index 65738c4af0..0c5c31166f 100644 --- a/cxx-squid/src/test/java/org/sonar/cxx/api/CxxMetricTest.java +++ b/cxx-squid/src/test/java/org/sonar/cxx/api/CxxMetricTest.java @@ -27,7 +27,7 @@ public class CxxMetricTest { @Test public void test() { - assertThat(CxxMetric.values()).hasSize(9); + assertThat(CxxMetric.values()).hasSize(11); for (CxxMetric metric : CxxMetric.values()) { assertThat(metric.getName()).isEqualTo(metric.name()); diff --git a/cxx-squid/src/test/resources/metrics/doxygen_example.h b/cxx-squid/src/test/resources/metrics/doxygen_example.h new file mode 100644 index 0000000000..6b4cf5ce31 --- /dev/null +++ b/cxx-squid/src/test/resources/metrics/doxygen_example.h @@ -0,0 +1,61 @@ +//! A test class. +/*! + A more elaborate class description. +*/ +class Test +{ + public: + //! An enum. + /*! More detailed enum description. */ + enum TEnum { + TVal1, /*!< Enum value TVal1. */ + TVal2, /*!< Enum value TVal2. */ + TVal3 /*!< Enum value TVal3. */ + } + //! Enum pointer. + /*! Details. */ + *enumPtr, + //! Enum variable. + /*! Details. */ + enumVar; + + //! A constructor. + /*! + A more elaborate description of the constructor. + */ + Test(); + //! A destructor. + /*! + A more elaborate description of the destructor. + */ + ~Test(); + + //! A normal member taking two arguments and returning an integer value. + /*! + \param a an integer argument. + \param s a constant character pointer. + \return The test results + \sa Test(), ~Test(), testMeToo() and publicVar() + */ + int testMe(int a,const char *s); + + //! A pure virtual member. + /*! + \sa testMe() + \param c1 the first argument. + \param c2 the second argument. + */ + virtual void testMeToo(char c1,char c2) = 0; + + //! A public variable. + /*! + Details. + */ + int publicVar; + + //! A function variable. + /*! + Details. + */ + int (*handler)(int a,int b); +}; diff --git a/cxx-squid/src/test/resources/metrics/no_doc.h b/cxx-squid/src/test/resources/metrics/no_doc.h new file mode 100644 index 0000000000..b3c38eced2 --- /dev/null +++ b/cxx-squid/src/test/resources/metrics/no_doc.h @@ -0,0 +1,79 @@ +#define TEST_MACRO() \ + macro + +#define EXPORT + +class testClass +{ + void defaultMethod(); + +public: + void publicMethod(); + + enum classEnum { + classEnumValue + } + + enumVar; + + EXPORT int publicAttribute; + + int inlineCommentedAttr; + + void inlinePublicMethod(); + +protected: + virtual void protectedMethod(); + +private: + void privateMethod(); + + enum privateEnum { + privateEnumVal + }; + + struct privateStruct { + int privateStructField; + }; + + class privateClass { + int i; + }; + + union privateUnion { + int u; + }; + +public: + int inlineCommentedLastAttr; +}; + + +struct testStruct { + int testField; +}; + +extern int globalVar; + +void testFunction(); + +enum emptyEnum +{}; + +enum testEnum +{ + enum_val +}; + +union testUnion +{ + +}; + +template +struct tmplStruct +{}; + +void func() { + for (int i = 0; i < 10; i++) {} +} diff --git a/cxx-squid/src/test/resources/metrics/public_api.h b/cxx-squid/src/test/resources/metrics/public_api.h new file mode 100644 index 0000000000..05c9a54814 --- /dev/null +++ b/cxx-squid/src/test/resources/metrics/public_api.h @@ -0,0 +1,139 @@ +#define TEST_MACRO() \ + macro + +/** + testClass doc + */ +class testClass +{ + void defaultMethod(); + +public: + // comment + + /** publicMethod doc */ + void publicMethod(); + + /// classEnum doc + enum classEnum { + classEnumValue ///< classEnumValue doc + } + /// block enumVar doc + enumVar; ///< inline enumVar doc, invalid ? + + /** + publicAttribute doc + */ + int publicAttribute; + + int inlineCommentedAttr; //!< inlineCommentedAttr comment + + void inlinePublicMethod(); //!< inlinePublicMethod comment + + int attr1, //!< attr1 doc + attr2; ///< attr2 doc +protected: + /** + protectedMethod doc + */ + virtual void protectedMethod(); + + /** + protectedStruct doc + */ + struct protectedStruct { + int protectedStructField; ///< protectedStructField doc + int protectedStructField2; ///< protectedStructField2 doc + }; + + /*! + protectedClass doc + */ + class protectedClass { + }; + + /** + operator doc + */ + value_t& operator[](std::size_t idx); + +private: + void privateMethod(); + + enum privateEnum { + privateEnumVal + }; + + struct privateStruct { + int privateStructField; + }; + + class privateClass { + int i; + }; + + union privateUnion { + int u; + }; + +public: + int inlineCommentedLastAttr; //!< inlineCommentedLastAttr comment +}; + +/// testStruct doc +struct testStruct { + int testField; /**< inline testField comment */ + + /// testTypeDef + /// + typedef toto testTypeDef; +}; + +/** + globalVar doc +*/ +extern int globalVar; + +int globalVarInline; //!< globalVarInline doc + +int +/** +globalVar1 doc +*/ +globalVar1, +globalVar2, ///< globalVar2 doc +globalVar3; /*!< globalVar3 doc */ + +/// testFunction doc +void testFunction(); + +void testFunction2(); //!< testFunction2 doc + +typedef int testType; ///< testType doc + +/** + testEnum doc +*/ +enum testEnum +{ + /** + enum_val doc + */ + enum_val +}; + +enum testEnum enumVar1, ///< enumVar1 doc +/** + enumVar2 doc +*/ +enumVar2; + +/*! + testUnion doc +*/ +union testUnion +{ + +}; + +int lastVar; ///< lastVar doc \ No newline at end of file diff --git a/cxx-squid/src/test/resources/metrics/template.h b/cxx-squid/src/test/resources/metrics/template.h new file mode 100644 index 0000000000..7b1aa4e47c --- /dev/null +++ b/cxx-squid/src/test/resources/metrics/template.h @@ -0,0 +1,11 @@ +/** + testClass +*/ +template +class testClass +{ +public: + void publicMethod(); + + int publicAttr; +}; diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java index 27840ece11..41f6e3ff53 100644 --- a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java @@ -55,7 +55,7 @@ public final class CxxPlugin extends SonarPlugin { static final String SOURCE_FILE_SUFFIXES_KEY = "sonar.cxx.suffixes.sources"; - static final String HEADER_FILE_SUFFIXES_KEY = "sonar.cxx.suffixes.headers"; + public static final String HEADER_FILE_SUFFIXES_KEY = "sonar.cxx.suffixes.headers"; public static final String DEFINES_KEY = "sonar.cxx.defines"; public static final String INCLUDE_DIRECTORIES_KEY = "sonar.cxx.includeDirectories"; public static final String ERROR_RECOVERY_KEY = "sonar.cxx.errorRecoveryEnabled"; diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java index 576dd972d7..8437bc04c5 100644 --- a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java @@ -116,6 +116,7 @@ private CxxConfiguration createConfiguration(Project project, Settings conf) { cxxConf.setIncludeDirectories(conf.getStringArray(CxxPlugin.INCLUDE_DIRECTORIES_KEY)); cxxConf.setErrorRecoveryEnabled(conf.getBoolean(CxxPlugin.ERROR_RECOVERY_KEY)); cxxConf.setForceIncludeFiles(conf.getStringArray(CxxPlugin.FORCE_INCLUDE_FILES_KEY)); + cxxConf.setHeaderFileSuffixes(conf.getStringArray(CxxPlugin.HEADER_FILE_SUFFIXES_KEY)); return cxxConf; } @@ -151,6 +152,8 @@ private void saveMeasures(org.sonar.api.resources.File sonarFile, SourceFile squ context.saveMeasure(sonarFile, CoreMetrics.COMPLEXITY, squidFile.getDouble(CxxMetric.COMPLEXITY)); context.saveMeasure(sonarFile, CoreMetrics.COMMENT_BLANK_LINES, squidFile.getDouble(CxxMetric.COMMENT_BLANK_LINES)); context.saveMeasure(sonarFile, CoreMetrics.COMMENT_LINES, squidFile.getDouble(CxxMetric.COMMENT_LINES)); + context.saveMeasure(sonarFile, CoreMetrics.PUBLIC_API, squidFile.getDouble(CxxMetric.PUBLIC_API)); + context.saveMeasure(sonarFile, CoreMetrics.PUBLIC_UNDOCUMENTED_API, squidFile.getDouble(CxxMetric.PUBLIC_UNDOCUMENTED_API)); } private void saveFunctionsComplexityDistribution(org.sonar.api.resources.File sonarFile, SourceFile squidFile) { diff --git a/sslr-cxx-toolkit/pom.xml b/sslr-cxx-toolkit/pom.xml index 5bd281cee8..f21854e556 100644 --- a/sslr-cxx-toolkit/pom.xml +++ b/sslr-cxx-toolkit/pom.xml @@ -97,7 +97,7 @@ - 3100000 + 3200000 2900000 ${project.build.directory}/${project.build.finalName}.jar