diff --git a/custom-checks/checkstyle/pom.xml b/custom-checks/checkstyle/pom.xml
index 09171a36..2310b601 100644
--- a/custom-checks/checkstyle/pom.xml
+++ b/custom-checks/checkstyle/pom.xml
@@ -48,9 +48,9 @@
- com.atlassian.commonmark
- commonmark
- ${commonmark.version}
+ com.vladsch.flexmark
+ flexmark-all
+ ${flexmark.version}
org.apache.ivy
diff --git a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/api/AbstractStaticCheck.java b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/api/AbstractStaticCheck.java
index 2c7064d6..72c533b0 100644
--- a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/api/AbstractStaticCheck.java
+++ b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/api/AbstractStaticCheck.java
@@ -17,7 +17,6 @@
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Properties;
-import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -31,9 +30,6 @@
import org.apache.commons.logging.LogFactory;
import org.apache.ivy.osgi.core.BundleInfo;
import org.apache.ivy.osgi.core.ManifestParser;
-import org.commonmark.node.Block;
-import org.commonmark.node.Node;
-import org.commonmark.parser.Parser;
import org.eclipse.core.internal.filebuffers.SynchronizableDocument;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.IDocument;
@@ -47,6 +43,9 @@
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
+import com.vladsch.flexmark.ast.Node;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.util.options.MutableDataSet;
/**
* Provides common functionality for different static code analysis checks
@@ -256,11 +255,11 @@ protected void logMessage(String filePath, int line, String fileName, String mes
* Parsed the content of a markdown file.
*
* @param fileText - Represents the text contents of a file
- * @param blockTypes - the enabled block types
+ * @param parsingOptions - parsing options
* @return The markdown node
*/
- protected Node parseMarkdown(FileText fileText, Set> blockTypes) {
- Parser parser = Parser.builder().enabledBlockTypes(blockTypes).build();
+ protected Node parseMarkdown(FileText fileText, MutableDataSet parsingOptions) {
+ Parser parser = Parser.builder(parsingOptions).build();
return parser.parse(fileText.getFullText().toString());
}
diff --git a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownCheck.java b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownCheck.java
index dc3e600d..ebd515e8 100644
--- a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownCheck.java
+++ b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownCheck.java
@@ -15,23 +15,16 @@
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.README_MD_FILE_NAME;
import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import org.commonmark.node.Block;
-import org.commonmark.node.FencedCodeBlock;
-import org.commonmark.node.Heading;
-import org.commonmark.node.IndentedCodeBlock;
-import org.commonmark.node.ListBlock;
-import org.commonmark.node.Node;
import org.eclipse.pde.core.build.IBuild;
import org.eclipse.pde.core.build.IBuildEntry;
import org.openhab.tools.analysis.checkstyle.api.AbstractStaticCheck;
-import org.openhab.tools.analysis.checkstyle.api.NoResultException;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.FileText;
+import com.vladsch.flexmark.ast.Node;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.util.options.MutableDataSet;
/**
* Checks the README.md files for:
@@ -51,6 +44,7 @@
* info
*
* @author Erdoan Hadzhiyusein - Initial contribution
+ * @author Lyubomir Papazov - Change the Markdown parser to flexmark
*/
public class MarkdownCheck extends AbstractStaticCheck {
private static final String ADDED_README_FILE_IN_BUILD_PROPERTIES_MSG = "README.MD file must not be added to the bin.includes property";
@@ -90,25 +84,21 @@ private void checkBuildProperties(FileText fileText) throws CheckstyleException
}
private void checkReadMe(FileText fileText) {
- // Don't need all block types visited that's why only these are enabled
- Set> enabledBlockTypes = new HashSet<>(
- Arrays.asList(Heading.class, ListBlock.class, FencedCodeBlock.class, IndentedCodeBlock.class));
- Node readmeMarkdownNode = parseMarkdown(fileText, enabledBlockTypes);
- // CallBack is used in order to use the protected methods of the AbstractStaticCheck in the Visitor
- MarkdownVisitorCallback callBack = new MarkdownVisitorCallback() {
- @Override
- public int findLineNumber(FileText fileContent, String searchedText, int startLineNumber)
- throws NoResultException {
- return MarkdownCheck.this.findLineNumber(fileText, searchedText, startLineNumber);
- }
+ MutableDataSet options = new MutableDataSet();
+ // By setting this option to true, the parser provides line numbers in the original markdown text for each node
+ options.set(Parser.TRACK_DOCUMENT_LINES, true);
+
+ Node readmeMarkdownNode = parseMarkdown(fileText, options);
+ // CallBack is used in order to use the protected log method of the AbstractStaticCheck in the Visitor
+ MarkdownVisitorCallback callBack = new MarkdownVisitorCallback() {
@Override
public void log(int line, String message) {
- MarkdownCheck.this.log(line, message);
+ MarkdownCheck.this.log(line + 1, message);
}
};
MarkdownVisitor visitor = new MarkdownVisitor(callBack, fileText);
- readmeMarkdownNode.accept(visitor);
+ visitor.visit(readmeMarkdownNode);
}
/**
diff --git a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownListVisitor.java b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownListVisitor.java
deleted file mode 100644
index 27b29e49..00000000
--- a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownListVisitor.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Copyright (c) 2010-2018 by the respective copyright holders.
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- */
-package org.openhab.tools.analysis.checkstyle.readme;
-
-import org.commonmark.node.AbstractVisitor;
-import org.commonmark.node.ListBlock;
-import org.commonmark.node.ListItem;
-import org.commonmark.node.Node;
-
-/**
- * This visitor is used in the {@link MarkdownVisitor} to get the count of list items in a Markdown list.
- *
- * @author Erdoan Hadzhiyusein - Initial contribution
- */
-public class MarkdownListVisitor extends AbstractVisitor {
- private int listLenght;
-
- /**
- * Each list has ListItem nodes and it is usually enough to know the count of them.
- * This of course doesn't guarantee finding the exact size of the list because there can be multiline list items,
- * but it will give an approximate lineNumber where to expect the end of the list.
- * (For the cases when the last line's literal can be found in the list earlier.)
- */
- @Override
- public void visit(ListItem listItem) {
- this.listLenght++;
- // using lastChild because if there is a nested list it will be represented as last child node of the ListItem
- Node lastChildNode = listItem.getLastChild();
- if (lastChildNode instanceof ListBlock) {
- // if there is a nested list in the current list the visitor visits it too
- lastChildNode.accept(this);
- }
- }
-
- public int getListLenght() {
- return listLenght;
- }
-}
diff --git a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitor.java b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitor.java
index 937ae10d..eea773b7 100644
--- a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitor.java
+++ b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitor.java
@@ -8,34 +8,28 @@
*/
package org.openhab.tools.analysis.checkstyle.readme;
-import java.text.MessageFormat;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.StringUtils;
-import org.commonmark.node.AbstractVisitor;
-import org.commonmark.node.BulletList;
-import org.commonmark.node.Code;
-import org.commonmark.node.FencedCodeBlock;
-import org.commonmark.node.Heading;
-import org.commonmark.node.IndentedCodeBlock;
-import org.commonmark.node.ListBlock;
-import org.commonmark.node.Node;
-import org.commonmark.node.OrderedList;
-import org.commonmark.node.Paragraph;
-import org.commonmark.node.Text;
import org.openhab.tools.analysis.checkstyle.api.AbstractStaticCheck;
-import org.openhab.tools.analysis.checkstyle.api.NoResultException;
import com.puppycrawl.tools.checkstyle.api.FileText;
+import com.vladsch.flexmark.ast.BulletList;
+import com.vladsch.flexmark.ast.FencedCodeBlock;
+import com.vladsch.flexmark.ast.Heading;
+import com.vladsch.flexmark.ast.ListBlock;
+import com.vladsch.flexmark.ast.ListItem;
+import com.vladsch.flexmark.ast.Node;
+import com.vladsch.flexmark.ast.NodeVisitorBase;
+import com.vladsch.flexmark.ast.OrderedList;
+import com.vladsch.flexmark.ast.Paragraph;
/**
* This visitor processes headers, lists and code sections and logs errors when
* needed.
*
* @author Erdoan Hadzhiyusein - Initial contribution
+ * @author Lyubomir Papazov - Change the parsering library to flexmark and adjust the code to work with it
*/
-class MarkdownVisitor extends AbstractVisitor {
+class MarkdownVisitor extends NodeVisitorBase {
private static final String EMPTY_LINE_AFTER_HEADER_MSG = "Missing an empty line after the Markdown header ('#').";
private static final String EMPTY_LINE_BEFORE_LIST_MSG = "The line before a Markdown list must be empty.";
@@ -44,14 +38,8 @@ class MarkdownVisitor extends AbstractVisitor {
private static final String EMPTY_LINE_AFTER_CODE_MSG = "The line after code formatting section must be empty.";
private static final String EMPTY_CODE_BLOCK_WARNING = "There is an empty or unclosed code formatting section. Please correct it.";
private static final String HEADER_AT_END_OF_FILE = "There is a header at the end of the Markdown file. Please consider adding some content below.";
- private final Log logger = LogFactory.getLog(MarkdownVisitor.class);
- /**
- * This field stores the line number where the processing of the source is up to.
- * Every time a MarkDown element is visited the pointer stores its starting line number in the source.
- * And when it is processed the pointer stores its ending line.
- */
- private int currentLinePointer = 0;
+ private static final String REGEX_NEW_LINES = "\\\r?\\\n";
/**
* A callback is used in order to use the protected methods of {@link AbstractStaticCheck}
@@ -68,41 +56,23 @@ public MarkdownVisitor(MarkdownVisitorCallback callBack, FileText fileText) {
/**
* Example of heading: #HomeMatic Binding
*/
- @Override
public void visit(Heading heading) {
- String headerContent = getLiteralOfElement(heading);
- validateHeader(headerContent);
+ validateHeadingPosition(heading.getLineNumber());
}
- /**
- * This method processes the Headings in the README which the Markdown
- * parser returns.
- *
- * @param headerValue - a String containing the literal of found Heading node
- */
- private void validateHeader(String headerValue) {
- if (headerValue != null) {
- int headerLineNumber;
- try {
- headerLineNumber = callback.findLineNumber(fileText, headerValue, currentLinePointer);
- currentLinePointer = headerLineNumber;
- boolean isHeaderAtEndOfFile = headerLineNumber == fileText.size();
- if (isHeaderAtEndOfFile) {
- callback.log(headerLineNumber, HEADER_AT_END_OF_FILE);
- } else {
- boolean isNextLineEmpty = StringUtils.isBlank(fileText.get(headerLineNumber));
- if (!isNextLineEmpty) {
- callback.log(headerLineNumber, EMPTY_LINE_AFTER_HEADER_MSG);
- }
- }
- } catch (NoResultException e) {
- logger.error("A header cannot be processed properly: " + headerValue, e);
- }
+ private void validateHeadingPosition(int zeroBasedHeaderLineNumber) {
+
+ boolean isHeaderAtEndOfFile = zeroBasedHeaderLineNumber == fileText.size() - 1;
+ if (isHeaderAtEndOfFile) {
+ // log the one=based line number
+ callback.log(zeroBasedHeaderLineNumber, HEADER_AT_END_OF_FILE);
} else {
- String message = MessageFormat.format(
- "Occurred an error while processing the Markdown file {0}. The header value is null.",
- fileText.getFile().getAbsolutePath());
- logger.warn(message);
+ // FileText uses zero-based indexes
+ boolean isNextLineEmpty = StringUtils.isBlank(fileText.get(zeroBasedHeaderLineNumber + 1));
+ if (!isNextLineEmpty) {
+ // log the one=based line number
+ callback.log(zeroBasedHeaderLineNumber, EMPTY_LINE_AFTER_HEADER_MSG);
+ }
}
}
@@ -110,64 +80,33 @@ private void validateHeader(String headerValue) {
* A fencedCodeBlock is a text-formatted like a programming code, example:
* private String name=null;
*/
- @Override
public void visit(FencedCodeBlock fencedCodeBlock) {
- String codeLiteral = fencedCodeBlock.getLiteral();
- validateCodeSection(codeLiteral);
+ validateCodeSectionPosition(fencedCodeBlock.getLineNumber(), fencedCodeBlock.getEndLineNumber(),
+ fencedCodeBlock);
}
- /**
- * Processes FencedCodeBlock nodes the parser returns.
- *
- * @param codeBlock - multiLine String containing the literal of the code block
- */
- private void validateCodeSection(String codeBlock) {
- int codeStartingLineNumber = 0;
- int codeEndingLineNumber = 0;
+ private void validateCodeSectionPosition(int zeroBasedStartLineNumber, int zeroBasedEndLineNumber,
+ Node codeBlockText) {
- // Splitting the String in case it is multiline, else the array would
- // have only one element
- String codeSectionLines[] = codeBlock.split("\\r?\\n");
- if (!StringUtils.isBlank(codeBlock)) {
- try {
- codeStartingLineNumber = callback.findLineNumber(fileText, codeSectionLines[0], currentLinePointer);
- codeStartingLineNumber--;
- currentLinePointer = codeStartingLineNumber;
- // Start from the line above the code section
- verifyBeforeCodeSection(codeStartingLineNumber);
- codeEndingLineNumber = codeStartingLineNumber + codeSectionLines.length;
- codeEndingLineNumber++;
- currentLinePointer = codeEndingLineNumber;
- verifyAfterCodeSection(codeEndingLineNumber);
- } catch (NoResultException e) {
- logger.error("A code section wasn't processed properly! " + codeBlock, e);
- }
- } else {
- callback.log(currentLinePointer, EMPTY_CODE_BLOCK_WARNING);
- }
- }
+ Node codeSection = codeBlockText.getFirstChild();
- private void verifyBeforeCodeSection(int codeStartingLineNumber) {
- if (codeStartingLineNumber == 1) {
- callback.log(codeStartingLineNumber, EMPTY_LINE_BEFORE_CODE_MSG);
- } else {
- // -2 because the line before the code is occupied with code section opening- ```
- int lineBeforeCodeSection = codeStartingLineNumber - 2;
- boolean isLineBeforeCodeSectionEmpty = StringUtils.isBlank(fileText.get(lineBeforeCodeSection));
- if (!isLineBeforeCodeSectionEmpty) {
- callback.log(codeStartingLineNumber, EMPTY_LINE_BEFORE_CODE_MSG);
+ // Check if the code section is empty or blank
+ if (codeSection != null && !StringUtils.isBlank(codeSection.getChars().toString())) {
+
+ // The code block is not the first line, and the previous line is not empty
+ if (zeroBasedStartLineNumber == 0 || !StringUtils.isBlank(fileText.get(zeroBasedStartLineNumber - 1))) {
+ // log the one-based line number
+ callback.log(zeroBasedStartLineNumber, EMPTY_LINE_BEFORE_CODE_MSG);
}
- }
- }
- private void verifyAfterCodeSection(int codeEndingLineNumber) {
- boolean isCodeSectionAtEndOfFile = (codeEndingLineNumber == fileText.size());
- // There is another check that logs errors if there is not an empty line at the end of file
- // named NewLineAtEndOfFileCheck
- if (!isCodeSectionAtEndOfFile) {
- if (!StringUtils.isBlank(fileText.get(codeEndingLineNumber))) {
- callback.log(codeEndingLineNumber, EMPTY_LINE_AFTER_CODE_MSG);
+ if (zeroBasedEndLineNumber != fileText.size() - 1
+ && !StringUtils.isBlank(fileText.get(zeroBasedEndLineNumber + 1))) {
+ // log the one-based line number
+ callback.log(zeroBasedEndLineNumber, EMPTY_LINE_AFTER_CODE_MSG);
}
+ } else {
+ // log the one-based line number
+ callback.log(zeroBasedStartLineNumber, EMPTY_CODE_BLOCK_WARNING);
}
}
@@ -177,162 +116,56 @@ private void verifyAfterCodeSection(int codeEndingLineNumber) {
* @param listBlock - a block type which is common parent of {@link BulletList } and {@link OrderedList}
*/
private void processListBlock(ListBlock listBlock) {
- String firstLineOfList = getFirstLineInList(listBlock);
- String lastLineOfList = getLastLineInList(listBlock);
- if (lastLineOfList == null) {
- // in case it is one lined list (last line would be null)
- lastLineOfList = firstLineOfList;
- }
- int listLenght = getListLenght(listBlock);
- markDownListProcessing(firstLineOfList, lastLineOfList, listLenght);
- }
-
- private int getListLenght(ListBlock listBlock) {
- MarkdownListVisitor listVisitor = new MarkdownListVisitor();
- listBlock.accept(listVisitor);
- return listVisitor.getListLenght();
- }
-
- @Override
- public void visit(BulletList bulletList) {
- processListBlock(bulletList);
+ checkEmptyLineBefore(listBlock);
+ checkEmptyLineAfterList(listBlock);
}
- @Override
- public void visit(OrderedList orderedList) {
- processListBlock(orderedList);
- }
+ private void checkEmptyLineBefore(ListBlock listBlock) {
+ int firstLineOfList = listBlock.getLineNumber();
- /**
- * This method processes list blocks in the specified Markdown file.
- *
- * @param firstLineOfList - the literal of the first list item
- * @param lastLineOfList - the literal of the last list item
- * @param listLenght - the number of list items (note that they can be multiLine)
- */
- private void markDownListProcessing(String firstLineOfList, String lastLineOfList, int listLenght) {
- int listStartingLineNumber = 0;
- int listEndingLineNumber = 0;
- try {
- listStartingLineNumber = callback.findLineNumber(fileText, firstLineOfList, currentLinePointer);
- } catch (NoResultException e) {
- logger.error("A list starting cannot be processed properly: " + firstLineOfList, e);
- }
- if (listStartingLineNumber == 1) {
- callback.log(listStartingLineNumber, EMPTY_LINE_BEFORE_LIST_MSG);
+ boolean isInnerList = listBlock.getParent() instanceof ListItem;
+ if (isInnerList) {
+ // Not checking if the line above is empty if it's another list item
+ return;
} else {
- // pointer goes before the list ending to narrow the searching scope
- // sometimes there could be 2 list items with same literal
- currentLinePointer = listStartingLineNumber + listLenght - 2;
- try {
- listEndingLineNumber = callback.findLineNumber(fileText, lastLineOfList, currentLinePointer);
- currentLinePointer = listEndingLineNumber;
- verifyLineBeforeListBlock(listStartingLineNumber);
- verifyLineAfterListBlock(listEndingLineNumber);
- } catch (NoResultException e) {
- logger.error("A list ending cannot be processed properly: " + lastLineOfList, e);
- }
- }
-
- }
-
- private void verifyLineBeforeListBlock(int listStartingLineNumber) {
- // Starting number is decreased with 2 lines because previous line is get and there is 0 indexation
- boolean isPreviousLineEmpty = !StringUtils.isBlank(fileText.get(listStartingLineNumber - 2));
- if (isPreviousLineEmpty) {
- // the -1 is used when logging the error to mark the exact line which have to be empty
- callback.log(listStartingLineNumber - 1, EMPTY_LINE_BEFORE_LIST_MSG);
- }
- }
-
- private void verifyLineAfterListBlock(int listEndingLineNumber) {
- boolean isListAtEndOfFile = (listEndingLineNumber == fileText.size());
- if (!isListAtEndOfFile) {
- boolean isNextLineEmpty = StringUtils.isBlank(fileText.get(listEndingLineNumber));
- if (!isNextLineEmpty) {
- callback.log(listEndingLineNumber, EMPTY_LINE_AFTER_LIST_MSG);
+ boolean isListfirstLineInFile = firstLineOfList == 0;
+ // The first line of the file can NOT be list
+ if (isListfirstLineInFile || !StringUtils.isBlank(fileText.get(firstLineOfList - 1))) {
+ // Log the one-based first line of the list
+ callback.log(firstLineOfList, EMPTY_LINE_BEFORE_LIST_MSG);
}
}
- // If the list block is the last entry in the markdown file, the check for an empty
- // line after the list will not be performed in order to avoid reporting false positives.
}
- /**
- * If there is a specific type of header or list item this method recursively gets its literal
- *
- * @param node - The node that is being processed to get its literal
- * @return - returns null if the literal wasn't processed properly
- */
- private String getLiteralOfElement(Node node) {
- if (node != null) {
- if (node instanceof Text) {
- Text text = (Text) node;
- return text.getLiteral();
- } else if (node instanceof Code) {
- // return the literal of a code-formatted list items
- Code code = (Code) node;
- return code.getLiteral();
- } else if (node instanceof IndentedCodeBlock) {
- // In case there is a multiline code block as a child of list item
- IndentedCodeBlock code = (IndentedCodeBlock) node;
- String codeSectionLines[] = code.getLiteral().split("(\\r?\\n)|(\\r)");
- if (codeSectionLines.length != 0) {
- return codeSectionLines[codeSectionLines.length - 1];
- }
- } else {
+ private void checkEmptyLineAfterList(ListBlock listBlock) {
+ ListItem lastListItem = (ListItem) listBlock.getLastChild();
+ Node lastListItemContent = lastListItem.getLastChild();
- // The children are processed recursively till text or code node is found
- // First child is used because Text and Code nodes are always first child nodes
- return getLiteralOfElement(node.getFirstChild());
+ boolean isListEnd = lastListItemContent instanceof Paragraph;
+ if (isListEnd) {
+ String[] lastListItemlines = lastListItemContent.getChars().toString().split(REGEX_NEW_LINES);
+ if (lastListItemlines.length > 1) {
+ // Log the one-based line where there is an empty line
+ callback.log(lastListItemContent.getLineNumber(), EMPTY_LINE_AFTER_LIST_MSG);
}
}
- return null;
}
- /**
- * The first child represents the first list item in the list block. Then recursively check if there are
- * any child nodes of this item and get the literal of it. Recursion is needed because there could be lists with
- * different structure and formatting.
- *
- * @param node - the listblock which first line is wanted
- * @return - returns the literal of the first line in the list
- **/
- private String getFirstLineInList(Node node) {
- Node firstChildNode = node.getFirstChild();
- // A paragraph is always the first parent of the leaf node
- boolean isLeafListItem = node instanceof Paragraph;
- String literalOfFirstChild = null;
- if (firstChildNode != null && !isLeafListItem) {
- // recursively searching the first list item and its nested lists
- literalOfFirstChild = getFirstLineInList(firstChildNode);
- } else {
- literalOfFirstChild = getLiteralOfElement(node);
- }
- return literalOfFirstChild;
+ public void visit(ListBlock list) {
+ list.getChildIterator().forEachRemaining(listItem -> visit(listItem));
+ processListBlock(list);
}
- /**
- * The last child represents the last list item in the list block. Then recursively check if there are
- * any child nodes of this item and get the literal of it. Recursion is needed because there could be lists with
- * different structure and formatting.
- *
- * @param node - the listblock of which last line is wanted
- * @return - returns the literal of the last line in the list
- **/
- private String getLastLineInList(Node node) {
- Node lastChildNode = node.getLastChild();
- boolean isLeafListItem = node instanceof Paragraph;
- String literalOfLastChild = null;
- if (lastChildNode != null && !isLeafListItem) {
- // recursively searching the last list item and its nested lists
- // if there is a nested code block its last line is taken
- literalOfLastChild = getLastLineInList(lastChildNode);
-
+ @Override
+ protected void visit(Node node) {
+ if (node instanceof FencedCodeBlock) {
+ visit((FencedCodeBlock) node);
+ } else if (node instanceof Heading) {
+ visit((Heading) node);
+ } else if (node instanceof ListBlock) {
+ visit((ListBlock) node);
} else {
- // this method is used to get the literal of leaf items or items which
- // could be nested inside typical leaf items
- literalOfLastChild = getLiteralOfElement(node);
+ visitChildren(node);
}
- return literalOfLastChild;
}
}
diff --git a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitorCallback.java b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitorCallback.java
index bed50dc1..2ecab5ae 100644
--- a/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitorCallback.java
+++ b/custom-checks/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/readme/MarkdownVisitorCallback.java
@@ -9,9 +9,6 @@
package org.openhab.tools.analysis.checkstyle.readme;
import org.openhab.tools.analysis.checkstyle.api.AbstractStaticCheck;
-import org.openhab.tools.analysis.checkstyle.api.NoResultException;
-
-import com.puppycrawl.tools.checkstyle.api.FileText;
/**
* This Interface is used to make a callback in {@link MarkdownCheck}.
@@ -19,18 +16,6 @@
* @author Erdoan Hadzhiyusein - Initial contribution
*/
public interface MarkdownVisitorCallback {
- /**
- * This method is implemented in The {@link MarkdownCheck} class calling the protected findLineNumber() of
- * {@link AbstractStaticCheck}.
- *
- * @param fileContent - the file content represented in a list
- * @param searchedText - the searched text in the source
- * @param startLineNumber - the line number to start the search from
- * @throws NoResultException when no match was found
- * @return - returns the line number in the source file
- */
- public int findLineNumber(FileText fileContent, String searchedText, int startLineNumber) throws NoResultException;
-
/**
* This method is implemented in The {@link MarkdownCheck} class calling the protected log() of
* {@link AbstractStaticCheck}.
diff --git a/custom-checks/checkstyle/src/test/java/org/openhab/tools/analysis/checkstyle/test/MarkdownCheckTest.java b/custom-checks/checkstyle/src/test/java/org/openhab/tools/analysis/checkstyle/test/MarkdownCheckTest.java
index d8f3db48..95314808 100644
--- a/custom-checks/checkstyle/src/test/java/org/openhab/tools/analysis/checkstyle/test/MarkdownCheckTest.java
+++ b/custom-checks/checkstyle/src/test/java/org/openhab/tools/analysis/checkstyle/test/MarkdownCheckTest.java
@@ -10,20 +10,23 @@
import static com.puppycrawl.tools.checkstyle.utils.CommonUtils.EMPTY_STRING_ARRAY;
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.README_MD_FILE_NAME;
-import org.openhab.tools.analysis.checkstyle.api.AbstractStaticCheckTest;
-import org.openhab.tools.analysis.checkstyle.readme.MarkdownCheck;
-import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
-import com.puppycrawl.tools.checkstyle.api.Configuration;
+
import java.io.File;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
+import org.openhab.tools.analysis.checkstyle.api.AbstractStaticCheckTest;
+import org.openhab.tools.analysis.checkstyle.readme.MarkdownCheck;
+
+import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
+import com.puppycrawl.tools.checkstyle.api.Configuration;
/**
* Tests for {@link MarkdownCheck}
*
* @author Erdoan Hadzhiyusein - Initial implementation
+ * @author Lyubomir Papazov - Added more tests
*/
public class MarkdownCheckTest extends AbstractStaticCheckTest {
private static final String README_MD_CHECK_TEST_DIRECTORY_NAME = "markdownCheckTest";
@@ -42,7 +45,6 @@ public void testHeader() throws Exception {
verifyMarkDownFile("testHeader", expectedMessages);
}
-
@Test
public void testForbiddNodeVisit() throws Exception {
verifyMarkDownFile("testForbiddenNodeVisit", noMessagesExpected());
@@ -55,7 +57,8 @@ private String[] noMessagesExpected() {
@Test
public void headerAtEndOfFile() throws Exception {
- String[] expectedMessages = generateExpectedMessages(6, "There is a header at the end of the Markdown file. Please consider adding some content below.");
+ String[] expectedMessages = generateExpectedMessages(6,
+ "There is a header at the end of the Markdown file. Please consider adding some content below.");
verifyMarkDownFile("testHeaderAtEndOfFile", expectedMessages);
}
@@ -66,7 +69,7 @@ public void testSpecialHeader() throws Exception {
@Test
public void testEmptyLineBeforeList() throws Exception {
- String[] expectedMessages = generateExpectedMessages(1, "The line before a Markdown list must be empty.");
+ String[] expectedMessages = generateExpectedMessages(2, "The line before a Markdown list must be empty.");
verifyMarkDownFile("testEmptyLineBeforeList", expectedMessages);
}
@@ -86,6 +89,31 @@ public void testCodeFormattedListBlock() throws Exception {
verifyMarkDownFile("testCodeFormattedListBlock", noMessagesExpected());
}
+ @Test
+ public void testEscapedAsterisk() throws Exception {
+ verifyMarkDownFile("testEscapedAsterisk", noMessagesExpected());
+ }
+
+ @Test
+ public void testEscapedUnderscore() throws Exception {
+ verifyMarkDownFile("testEscapedUnderscore", noMessagesExpected());
+ }
+
+ @Test
+ public void testEscapedBrackets() throws Exception {
+ verifyMarkDownFile("testEscapedBrackets", noMessagesExpected());
+ }
+
+ @Test
+ public void testEscapedCopyrightSymbol() throws Exception {
+ verifyMarkDownFile("testEscapedCopyrightSymbol", noMessagesExpected());
+ }
+
+ @Test
+ public void testEscapedHeader() throws Exception {
+ verifyMarkDownFile("testEscapedHeader", noMessagesExpected());
+ }
+
@Test
public void testEmptyLinedCodeBlock() throws Exception {
verifyMarkDownFile("testEmptyLinedCodeBlock", noMessagesExpected());
@@ -128,12 +156,27 @@ public void testPreCodeSection() throws Exception {
@Test
public void testCodeSectionLineNumberError() throws Exception {
- verifyMarkDownFile("testCodeSectionLineNumberError",noMessagesExpected());
+ verifyMarkDownFile("testCodeSectionLineNumberError", noMessagesExpected());
+ }
+
+ @Test
+ public void testListFirstLineSameAsParagraph() throws Exception {
+ verifyMarkDownFile("testListFirstLineSameAsParagraph", noMessagesExpected());
+ }
+
+ @Test
+ public void testListBeginingSameAsAnotherLineBegining() throws Exception {
+ verifyMarkDownFile("testListBeginingSameAsAnotherLineBegining", noMessagesExpected());
+ }
+
+ @Test
+ public void testListLastLineSameAsParagraph() throws Exception {
+ verifyMarkDownFile("testListLastLineSameAsParagraph", noMessagesExpected());
}
@Test
public void testEmptyCodeSection() throws Exception {
- String[] expectedMessages = generateExpectedMessages(1,
+ String[] expectedMessages = generateExpectedMessages(3,
"There is an empty or unclosed code formatting section. Please correct it.");
verifyMarkDownFile("testEmptyCodeSection", expectedMessages);
}
@@ -209,8 +252,14 @@ public void testAddedReadmeAndDocInBuildProperties() throws Exception {
verifyBuildProperties(expectedMessages, testDirectoryName);
}
+ @Test
+ public void testOpenhabBindingExec() throws Exception {
+ String testDirectoryName = "org.openhab.binding.exec";
+ verifyMarkDownFile(testDirectoryName, noMessagesExpected());
+ }
+
private void verifyBuildProperties(String[] expectedMessages, String testDirectoryName)
- throws IOException, Exception {
+ throws IOException, Exception {
String testDirectoryAbsolutePath = getPath(README_MD_CHECK_TEST_DIRECTORY_NAME + File.separator + testDirectoryName);
String messageFilePath = testDirectoryAbsolutePath + File.separator + "build.properties";
verify(createChecker(config), messageFilePath, expectedMessages);
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/org.openhab.binding.exec/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/org.openhab.binding.exec/README.md
new file mode 100644
index 00000000..605ad9de
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/org.openhab.binding.exec/README.md
@@ -0,0 +1,144 @@
+# Exec Binding
+
+This binding integrates the possibility to execute arbitrary shell commands.
+
+## Supported Things
+
+Currently, the binding supports a single type of Thing, being the `command` Thing.
+
+## Binding Configuration
+
+The binding does not require any specific configuration.
+
+Note that on Unix systems the commands are executed in the context and with the privileges of the process running the java virtual machine, and it is not advised to run the virtual machine as superuser/root. It is advised to test the execution of the command in a shell using the user owning the JVM process, for example
+
+```
+sudo -u openhab
+```
+
+The execution of the commands is triggered by either sending a Command or State Update to the input Channel of the `command` Thing, if configured so, or by sending the ON Command to the run Channel of the `command` Thing (see below)
+
+## Thing Configuration
+
+The `command` Thing requires the following mandatory configuration parameters:
+
+- [command] the command to execute on the shell.
+
+Optionally one can specify:
+
+- [transform] a transformation to apply on the execution result,
+- [interval] an interval, in seconds, the [command] will be repeatedly executed,
+- [timeout] a time-out, in seconds, after which the execution of the [command] will time out,
+- [runOnInput] a boolean parameter to make the [command] execute immediately every time a Command or a State Update is sent to the input channel of the `command` Thing, and lastly,
+- [repeatEnabled] a boolean parameter to allow the [command] to be executed repeatedly, e.g. when the same Command or State Update is sent to input channel repeatedly. This parameter only makes sense when used in combination with [runOnInput] set to true.
+
+For each [command] a separate Thing has to be defined. For example,
+
+```
+Thing exec:command:apc [command="/usr/local/bin/apcaccess status", interval=15, timeout=5, runOnInput=true, repeatEnabled=true]
+```
+
+[command] itself can be enhanced using some specific qualifiers that will be substituted by actual values at runtime. The qualifiers have the following syntax:
+
+`${::}`
+
+whereby
+
+- can be either
+ - the name of an Item
+ - `exec-input`, denoting the current State of the input Channel of the `command` Thing
+ - `exec-time`, denoting the current date (as java.util.Date)
+- is any valid [Transformation](https://docs.openhab.org/addons/transformations.html) service expression, e.g. REGEX((.*?)). is mandatory for the `exec-time` key
+- is a formatting string using the well known syntax of the [java.util.Formatter](http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax) class
+
+At runtime the binding will grab the value of the , transform it with the expression, and finally format the transformation result using the format. For example,
+
+```
+Thing exec:command:lightcontroller [command="/usr/local/bin/light.sh ${lightSwitch:MAP(en.map):%1$s}"]
+```
+
+When the [command] of the lightcontroller item is executed then the value of the lightSwitch Item is retrieved, transformed using the en.map MAP Transform, and passed on as a literal string (i.e. %1$s takes the first argument of the result of the Transform, and formats it as a String) to the ligh.sh script
+
+Nesting of substitution keys is supported, e.g. `${lightSwitch_${lightCounter}}` will resolve the lightCounter Item first, and then subsequently resolve the second substitution key. For example, if the value of lighCounter would happen to be 3, then the second substitution resolved to the value of Item `lighSwitch_3`
+
+```
+Thing exec:command:lightcontroller [command="/usr/local/bin/light.sh ${exec-input}"]
+```
+
+In the above example, exec-input is substituted with the actual State of the input Channel of the `exec:command:lightcontroller` Thing
+
+The Channels themselves are defined as custom State Channels (https://github.com/eclipse/smarthome/blob/master/docs/documentation/features/dsl.md#defining-channels), so one can freely define the Type of each Channel. The `command` Thing supports both an `input` and `output` Channel to set an input for the [command] and get the output of the command execution.
+
+## Channels
+
+All Things support the following channels:
+
+| Channel Type ID | Item Type | Description |
+|-----------------|--------------|-------------------------------------------|
+| input | custom | Input parameter to provide to the command |
+| output | custom | Output of the last execution of the command |
+| exit | Number | The exit value of the last execution of the command |
+| run | Switch | Send ON to execute the command. the current State of this channel tells whether the command is running or not |
+| lastexecution | DateTime | Time/Date the command was last executed, in yyyy-MM-dd'T'HH:mm:ss.SSSZ format |
+
+## Full Example
+
+Some additional examples can be found on the [OpenHAB community forum](https://community.openhab.org/t/1-openhab-433mhz-radio-transmitter-tutorial/34977)
+
+**demo.things**
+
+```
+Thing exec:command:switch_monitor [command="switch_control.sh check", interval=1]
+{
+Channels:
+ Switch : output
+}
+Thing exec:command:switch_control [command="switch_control.sh ${exec-input}", runOnInput=true, repeatEnabled=true] {
+Channels:
+ Switch : input
+ String : output
+}
+```
+
+**demo.items**
+
+```
+Switch LampSwitch {channel="exec:command:switch_control:input", channel="exec:command:switch_monitor:output"}
+String LampResult {channel="exec:command:switch_control:output"}
+Switch LampSwitching {channel="exec:comamnd:switch_control:run"}
+Number YourNumber "Your Number [%.1f �C]"
+```
+
+**demo.rules**
+
+```
+rule "Your Execution"
+ when
+ Item someTrigger changed
+ then
+ // set the additional command line arguments to the switch_control.sh script
+ if(YourTrigger.state == ON){
+ LampSwitch.sendCommand(ON)
+ }else{
+ LampSwitch.sendCommand(OFF)
+ }
+
+ // Trigger execution
+ LampSwitching.sendCommand(ON)
+
+ // wait for the command to complete
+ // State will be NULL if not used before or ON while command is executed
+ while(LampSwitching.state != OFF){
+ Thread::sleep(500)
+ }
+
+ // Logging of command line result
+ logInfo("Switching the lamp", "Result:" + LampResult.state )
+
+ // If the returned string is just a number it can be parsed
+ // If not a transformation service can be used
+ YourNumber.postUpdate(
+ (Integer::parseInt(LampResult.state.toString) as Number )
+ )
+end
+```
\ No newline at end of file
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedAsterisk/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedAsterisk/README.md
new file mode 100644
index 00000000..fdf5e929
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedAsterisk/README.md
@@ -0,0 +1,4 @@
+#### Title
+
+ * List
+ * Escaped\*Asterisk
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedBrackets/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedBrackets/README.md
new file mode 100644
index 00000000..887325ed
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedBrackets/README.md
@@ -0,0 +1,3 @@
+#### Title
+
+\[This is not a link\](not a link)
\ No newline at end of file
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedCopyrightSymbol/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedCopyrightSymbol/README.md
new file mode 100644
index 00000000..923289cb
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedCopyrightSymbol/README.md
@@ -0,0 +1,4 @@
+#### Example list
+
+ * first
+ * \©
\ No newline at end of file
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedHeader/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedHeader/README.md
new file mode 100644
index 00000000..1acdd510
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedHeader/README.md
@@ -0,0 +1,3 @@
+#### Configuration Options
+
+\#### Not a header in the last line
\ No newline at end of file
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedUnderscore/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedUnderscore/README.md
new file mode 100644
index 00000000..39aeb9d4
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testEscapedUnderscore/README.md
@@ -0,0 +1,25 @@
+#### Configuration Options
+
+ * deviceId - Device Id
+ * Device Id. House code + unit code, separated by dot. Example A.1
+
+ * subType - Sub Type
+ * Specifies device sub type.
+
+ * X10 - X10 lighting
+ * ARC - ARC
+ * AB400D - ELRO AB400D (Flamingo)
+ * WAVEMAN - Waveman
+ * EMW200 - Chacon EMW200
+ * IMPULS - IMPULS
+ * RISINGSUN - RisingSun
+ * PHILIPS - Philips SBC
+ * ENERGENIE - Energenie ENER010
+ * ENERGENIE\_5 - Energenie 5-gang
+ * COCO - COCO GDR2-2000R
+ * HQ\_COCO20 - HQ COCO-20
+
+
+### lighting2 - RFXCOM Lighting2 Actuator
+
+A Lighting2 device.
\ No newline at end of file
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListBeginingSameAsAnotherLineBegining/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListBeginingSameAsAnotherLineBegining/README.md
new file mode 100644
index 00000000..d7f9d3b1
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListBeginingSameAsAnotherLineBegining/README.md
@@ -0,0 +1,11 @@
+# Heading
+
+A casual paragraph starts here
+This is _row number two_
+
+1. This is _a totally different text_
+2. ordered element two
+3. ordered element four
+ - ordered sublist element
+ - ordered sublist element two
+
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListFirstLineSameAsParagraph/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListFirstLineSameAsParagraph/README.md
new file mode 100644
index 00000000..14517831
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListFirstLineSameAsParagraph/README.md
@@ -0,0 +1,12 @@
+# Heading
+
+This is some paragraph text
+This shouldn't cause a problem
+
+1. This shouldn't cause a problem
+2. ordered element two
+3. ordered element four
+ - ordered sublist element
+ - ordered sublist element two
+
+
\ No newline at end of file
diff --git a/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListLastLineSameAsParagraph/README.md b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListLastLineSameAsParagraph/README.md
new file mode 100644
index 00000000..f2f7ed36
--- /dev/null
+++ b/custom-checks/checkstyle/src/test/resources/checkstyle/markdownCheckTest/testListLastLineSameAsParagraph/README.md
@@ -0,0 +1,12 @@
+# Heading
+
+This is some paragraph text
+This shouldn't cause a problem
+Some more irrelevant text
+
+1. ordered element one
+2. ordered element two
+3. ordered element four
+ - ordered sublist element
+ - This shouldn't cause a problem
+
diff --git a/pom.xml b/pom.xml
index 51b5e57b..4ca371e2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
1.7.1
3.11.1
0.3.0
- 0.9.0
+ 0.28.6
2.12.4
9.3.14.v20161028
9.1.0.8