From 3f48031353478a62f486215e3433982319f7a7d0 Mon Sep 17 00:00:00 2001 From: Andris Raugulis Date: Wed, 8 Jul 2015 12:11:57 +0300 Subject: [PATCH] Support for @typedoc. Separate parser. --- .../elixir/language/ElixirMeasureSensor.java | 119 +++------------ .../plugins/elixir/language/ElixirParser.java | 142 ++++++++++++++++++ .../language/ElixirMeasureSensorTest.java | 6 +- .../src/test/resources/test_doc.ex | 4 + 4 files changed, 169 insertions(+), 102 deletions(-) create mode 100644 sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirParser.java diff --git a/sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensor.java b/sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensor.java index 2c5bf6f..1d26d57 100644 --- a/sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensor.java +++ b/sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensor.java @@ -51,12 +51,6 @@ public class ElixirMeasureSensor implements Sensor { private final FileSystem fileSystem; private final FilePredicate mainFilePredicate; - private final Pattern defPattern = Pattern.compile("^\\s*def(|p|module|struct)\\s(.*)$"); - private final Matcher defMatcher = defPattern.matcher(""); - private final Pattern heredocPattern = Pattern.compile("^.*\"\"\"\\s*$"); - private final Matcher heredocMatcher = heredocPattern.matcher(""); - private final Pattern docPattern = Pattern.compile("^\\s*@(moduledoc|doc)([\"\\s].*)$"); - private final Matcher docMatcher = docPattern.matcher(""); public ElixirMeasureSensor(FileSystem fileSystem) { this.fileSystem = fileSystem; @@ -79,105 +73,32 @@ public boolean shouldExecuteOnProject(Project project) { } private void processMainFile(InputFile inputFile, SensorContext context) { - int lineCount = 0; - int emptyLineCount = 0; - int commentLineCount = 0; - int classCount = 0; - int publicFunctionCount = 0; - int privateFunctionCount = 0; - int documentedClassCount = 0; - int documentedPrivateFunctionCount = 0; - int documentedPublicFunctionCount = 0; - boolean hasDoc = false; - boolean inClass = false; - - List lines = null; + List lines; try { lines = Files.readAllLines(Paths.get(inputFile.absolutePath()), fileSystem.encoding()); - lineCount = lines.size(); - for (int i = 0; i < lineCount; i++) { - String line = lines.get(i); - if (StringUtils.isBlank(line)) { - emptyLineCount++; - } - docMatcher.reset(line); - boolean inDoc = docMatcher.find(); - if (inDoc) { - commentLineCount++; - String docText = docMatcher.group(2).trim(); - if (! (StringUtils.equalsIgnoreCase(docText, "false") || StringUtils.equalsIgnoreCase(docText, "nil"))) { - switch (docMatcher.group(1)) { - case "doc": - hasDoc = true; - break; - case "moduledoc": - if (inClass) { - documentedClassCount++; - } - break; - } - } - } - heredocMatcher.reset(line); - if (heredocMatcher.find()) { - while (i < lineCount - 1) { - if (inDoc) { - commentLineCount++; - } - i++; - line = lines.get(i); - if (line.matches("^\\s*\"\"\"\\s*$")) { - break; - } - } - continue; - } - - defMatcher.reset(line); - if (defMatcher.find()) { - switch (defMatcher.group(1)) { - case "module": - classCount++; - inClass = true; - break; - case "": - publicFunctionCount++; - if (hasDoc) { - documentedPublicFunctionCount++; - } - break; - case "p": - privateFunctionCount++; - if (hasDoc) { - documentedPrivateFunctionCount++; - } - break; - } - hasDoc = false; - } - if (line.matches("^\\s*#.*$")) { - commentLineCount++; - } - } } catch (IOException e) { LOG.warn(LOG_PREFIX + "could not process file: " + inputFile.toString()); + return; } - if (lines != null) { - LOG.debug(LOG_PREFIX + "processing file: " + inputFile.toString()); - context.saveMeasure(inputFile, CoreMetrics.LINES, (double) lineCount); - context.saveMeasure(inputFile, CoreMetrics.NCLOC, (double)(lineCount - emptyLineCount - commentLineCount)); - context.saveMeasure(inputFile, CoreMetrics.COMMENT_LINES, (double)commentLineCount); + ElixirParser parser = new ElixirParser(); + parser.parse(lines); - double publicApi = publicFunctionCount + classCount; - double documentedApi = documentedPublicFunctionCount + documentedClassCount; - double undocumentedApi = publicApi - documentedApi; - double documentedApiDensity = (publicApi == 0 ? 100.0 : ParsingUtils.scaleValue(documentedApi / publicApi * 100, 2)); - context.saveMeasure(inputFile, CoreMetrics.PUBLIC_API, publicApi); - context.saveMeasure(inputFile, CoreMetrics.PUBLIC_UNDOCUMENTED_API, undocumentedApi); - context.saveMeasure(inputFile, CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY, documentedApiDensity); + LOG.debug(LOG_PREFIX + "processing file: " + inputFile.toString()); + double linesOfCode = parser.getLineCount() - parser.getEmptyLineCount() - parser.getCommentLineCount(); + context.saveMeasure(inputFile, CoreMetrics.LINES, (double)parser.getLineCount()); + context.saveMeasure(inputFile, CoreMetrics.NCLOC, (double)linesOfCode); + context.saveMeasure(inputFile, CoreMetrics.COMMENT_LINES, (double)parser.getCommentLineCount()); - context.saveMeasure(inputFile, CoreMetrics.CLASSES, (double)classCount); - context.saveMeasure(inputFile, CoreMetrics.FUNCTIONS, (double)(publicFunctionCount + privateFunctionCount)); - } + double publicApi = parser.getPublicFunctionCount() + parser.getClassCount(); + double documentedApi = parser.getDocumentedPublicFunctionCount() + parser.getDocumentedClassCount(); + double undocumentedApi = publicApi - documentedApi; + double documentedApiDensity = (publicApi == 0 ? 100.0 : ParsingUtils.scaleValue(documentedApi / publicApi * 100, 2)); + context.saveMeasure(inputFile, CoreMetrics.PUBLIC_API, publicApi); + context.saveMeasure(inputFile, CoreMetrics.PUBLIC_UNDOCUMENTED_API, undocumentedApi); + context.saveMeasure(inputFile, CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY, documentedApiDensity); + + double functionCount = parser.getPublicFunctionCount() + parser.getPrivateFunctionCount(); + context.saveMeasure(inputFile, CoreMetrics.CLASSES, (double)parser.getClassCount()); + context.saveMeasure(inputFile, CoreMetrics.FUNCTIONS, (double)(functionCount)); } } diff --git a/sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirParser.java b/sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirParser.java new file mode 100644 index 0000000..cc1342b --- /dev/null +++ b/sonar-elixir-plugin/src/main/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirParser.java @@ -0,0 +1,142 @@ +/* + * SonarQube Elixir plugin + * Copyright (C) 2015 Andris Raugulis + * moo@arthepsy.eu + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package eu.arthepsy.sonar.plugins.elixir.language; + +import org.apache.commons.lang.StringUtils; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ElixirParser { + private int lineCount = 0; + private int emptyLineCount = 0; + private int commentLineCount = 0; + private int classCount = 0; + private int publicFunctionCount = 0; + private int privateFunctionCount = 0; + private int documentedClassCount = 0; + private int documentedPrivateFunctionCount = 0; + private int documentedPublicFunctionCount = 0; + + private final Pattern defPattern = Pattern.compile("^\\s*def(|p|module|struct)\\s(.*)$"); + private final Matcher defMatcher = defPattern.matcher(""); + private final Pattern heredocPattern = Pattern.compile("^.*\"\"\"\\s*$"); + private final Matcher heredocMatcher = heredocPattern.matcher(""); + private final Pattern docPattern = Pattern.compile("^\\s*@(doc|moduledoc|typedoc)([\"\\s].*)$"); + private final Matcher docMatcher = docPattern.matcher(""); + + private boolean hasDoc = false; + private boolean inClass = false; + + public ElixirParser() { } + + public int getLineCount() { return lineCount; } + public int getEmptyLineCount() { return emptyLineCount; } + public int getCommentLineCount() { return commentLineCount; } + public int getClassCount() { return classCount; } + public int getPublicFunctionCount() { return publicFunctionCount; } + public int getPrivateFunctionCount() { return privateFunctionCount; } + public int getDocumentedClassCount() { return documentedClassCount; } + public int getDocumentedPublicFunctionCount() { return documentedPublicFunctionCount; } + public int getDocumentedPrivateFunctionCount() { return documentedPrivateFunctionCount; } + + public void parse(List lines) { + this.parseLines(lines); + } + + private void parseLines(List lines) { + hasDoc = false; + inClass = false; + + lineCount = lines.size(); + for (int i = 0; i < lineCount; i++) { + String line = lines.get(i); + if (StringUtils.isBlank(line)) { + emptyLineCount++; + } + docMatcher.reset(line); + boolean inDoc = docMatcher.find(); + if (inDoc) { + commentLineCount++; + String docHead = docMatcher.group(2).trim(); + if (! (StringUtils.equalsIgnoreCase(docHead, "false") || StringUtils.equalsIgnoreCase(docHead, "nil"))) { + switch (docMatcher.group(1)) { + case "doc": + hasDoc = true; + break; + case "moduledoc": + if (inClass) { + documentedClassCount++; + } + break; + case "typedoc": + break; + } + } + } + heredocMatcher.reset(line); + if (heredocMatcher.find()) { + while (i < lineCount - 1) { + if (inDoc) { + commentLineCount++; + } + i++; + line = lines.get(i); + if (line.matches("^\\s*\"\"\"\\s*$")) { + break; + } + } + continue; + } + + defMatcher.reset(line); + if (defMatcher.find()) { + switch (defMatcher.group(1)) { + case "module": + classCount++; + inClass = true; + break; + case "": + publicFunctionCount++; + if (hasDoc) { + documentedPublicFunctionCount++; + } + break; + case "p": + privateFunctionCount++; + if (hasDoc) { + documentedPrivateFunctionCount++; + } + break; + } + hasDoc = false; + } + if (line.matches("^\\s*#.*$")) { + commentLineCount++; + } + } + } + +} diff --git a/sonar-elixir-plugin/src/test/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensorTest.java b/sonar-elixir-plugin/src/test/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensorTest.java index f54ccc2..1f95579 100644 --- a/sonar-elixir-plugin/src/test/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensorTest.java +++ b/sonar-elixir-plugin/src/test/java/eu/arthepsy/sonar/plugins/elixir/language/ElixirMeasureSensorTest.java @@ -75,9 +75,9 @@ public void testDocAnnotation() throws IOException { sensor.analyse(project, context); - verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.LINES), eq(33.0)); - verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.NCLOC), eq(14.0)); - verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.COMMENT_LINES), eq(13.0)); + verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.LINES), eq(37.0)); + verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.NCLOC), eq(15.0)); + verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.COMMENT_LINES), eq(14.0)); verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.CLASSES), eq(1.0)); verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.FUNCTIONS), eq(6.0)); verify(context).saveMeasure(any(InputFile.class), eq(CoreMetrics.PUBLIC_API), eq(5.0)); diff --git a/sonar-elixir-plugin/src/test/resources/test_doc.ex b/sonar-elixir-plugin/src/test/resources/test_doc.ex index 9a40d97..e2a4a66 100644 --- a/sonar-elixir-plugin/src/test/resources/test_doc.ex +++ b/sonar-elixir-plugin/src/test/resources/test_doc.ex @@ -30,4 +30,8 @@ defmodule Docs do # private function doc6 defp doc6() do end + + @typedoc "type doc example" + @type doc7 :: any + end