diff --git a/build.gradle.kts b/build.gradle.kts index 68b1ae5..193be6e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,11 +9,6 @@ buildscript { dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21") classpath("org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2") - classpath("com.novoda:bintray-release:0.9") - classpath("com.novoda:bintray-release:0.9") - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files } } diff --git a/markdown/build.gradle.kts b/markdown/build.gradle.kts index 1290c34..6e97908 100644 --- a/markdown/build.gradle.kts +++ b/markdown/build.gradle.kts @@ -45,8 +45,6 @@ dependencies { api("com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.40.20") api("com.vladsch.flexmark:flexmark-ext-autolink:0.40.20") - implementation("com.puppycrawl.tools:checkstyle:8.18") - testImplementation(kotlin("test")) testImplementation(gradleTestKit()) testImplementation("junit:junit:4.12") diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/AuditEvent.kt b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/AuditEvent.kt new file mode 100644 index 0000000..473ed45 --- /dev/null +++ b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/AuditEvent.kt @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2019 the original author or authors. +// +// This library 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 2.1 of the License, or (at your option) any later version. +// +// This library 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 library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// + +package com.appmattus.markdown.checkstyle + +/** + * Raw event for audit. + * + * I'm not very satisfied about the design of this event since there are + * optional methods that will return null in most of the case. This will + * need some work to clean it up especially if we want to introduce + * a more sequential reporting action rather than a packet error + * reporting. This will allow for example to follow the process quickly + * in an interface or a servlet (yep, that's cool to run a check via + * a web interface in a source repository ;-) + * + * Creates a new `AuditEvent` instance. + * + * @param fileName file associated with the event + * @param localizedMessage the actual message + * @see AuditListener + */ +class AuditEvent( + val fileName: String, + private val localizedMessage: LocalizedMessage? = null +) { + + /** + * Return the line number on the source file where the event occurred. + * This may be 0 if there is no relation to a file content. + * + * @return an integer representing the line number in the file source code. + */ + val line: Int + get() = localizedMessage!!.lineNo + + /** + * Return the message associated to the event. + * + * @return the event message + */ + val message: String + get() = localizedMessage!!.message + + /** + * Gets the column associated with the message. + * + * @return the column associated with the message + */ + val column: Int + get() = localizedMessage!!.columnNo + + /** + * Gets the audit event severity level. + * + * @return the audit event severity level + */ + val severityLevel: SeverityLevel + get() = localizedMessage?.severityLevel ?: SeverityLevel.INFO + + /** + * Gets the name of the source for the message. + * + * @return the name of the source for the message + */ + val sourceName: String + get() = localizedMessage!!.sourceName +} diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/AuditListener.kt b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/AuditListener.kt new file mode 100644 index 0000000..5a3b845 --- /dev/null +++ b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/AuditListener.kt @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2019 the original author or authors. +// +// This library 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 2.1 of the License, or (at your option) any later version. +// +// This library 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 library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// + +package com.appmattus.markdown.checkstyle + +/** + * Listener in charge of receiving events from the Checker. + * Typical events sequence is: + * + * ``` + * auditStarted + * (fileStarted + * (addError) + * fileFinished) + * auditFinished + * ``` + */ +interface AuditListener { + + /** + * Notify that the audit is about to start. + */ + fun auditStarted() + + /** + * Notify that the audit is finished. + */ + fun auditFinished() + + /** + * Notify that audit is about to start on a specific file. + * + * @param event the event details + */ + fun fileStarted(event: AuditEvent) + + /** + * Notify that audit is finished on a specific file. + * + * @param event the event details + */ + fun fileFinished(event: AuditEvent) + + /** + * Notify that an audit error was discovered on a specific file. + * + * @param event the event details + */ + fun addError(event: AuditEvent) +} diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/LocalizedMessage.kt b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/LocalizedMessage.kt new file mode 100644 index 0000000..3d81cff --- /dev/null +++ b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/LocalizedMessage.kt @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2019 the original author or authors. +// +// This library 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 2.1 of the License, or (at your option) any later version. +// +// This library 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 library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// + +package com.appmattus.markdown.checkstyle + +/** + * Represents a message that can be localised. The translations come from + * message.properties files. The underlying implementation uses + * java.text.MessageFormat. + * + * @param lineNo line number associated with the message + * @param columnNo column number associated with the message + * @param message the error message + * @param severityLevel severity level for the message + * @param sourceClass the Class that is the source of the message + */ +data class LocalizedMessage( + val lineNo: Int, + val columnNo: Int, + val message: String, + val severityLevel: SeverityLevel, + private val sourceClass: Class<*> +) : Comparable { + + /** + * Gets the name of the source for this LocalizedMessage. + * + * @return the name of the source for this LocalizedMessage + */ + val sourceName: String = sourceClass.name + + //////////////////////////////////////////////////////////////////////////// + // Interface Comparable methods + //////////////////////////////////////////////////////////////////////////// + + @Suppress("ComplexMethod") + override fun compareTo(other: LocalizedMessage): Int { + return if (lineNo == other.lineNo) { + if (columnNo == other.columnNo) { + message.compareTo(other.message) + } else { + Integer.compare(columnNo, other.columnNo) + } + } else { + Integer.compare(lineNo, other.lineNo) + } + } +} diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/OutputStreamOptions.kt b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/OutputStreamOptions.kt new file mode 100644 index 0000000..0c2299a --- /dev/null +++ b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/OutputStreamOptions.kt @@ -0,0 +1,17 @@ +package com.appmattus.markdown.checkstyle + +/** + * Enum to specify behaviour regarding ignored modules. + */ +enum class OutputStreamOptions { + + /** + * Close stream in the end. + */ + CLOSE, + + /** + * Do nothing in the end. + */ + NONE +} diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/SeverityLevel.kt b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/SeverityLevel.kt new file mode 100644 index 0000000..b93fd8d --- /dev/null +++ b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/SeverityLevel.kt @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2019 the original author or authors. +// +// This library 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 2.1 of the License, or (at your option) any later version. +// +// This library 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 library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// + +package com.appmattus.markdown.checkstyle + +import java.util.Locale + +/** + * Severity level for a check violation. + * + * Each violation of an audit check is assigned one of the severity levels + * defined here. + * + */ +enum class SeverityLevel { + + /** + * Severity level ignore. + */ + IGNORE, + /** + * Severity level info. + */ + INFO, + /** + * Severity level warning. + */ + WARNING, + /** + * Severity level error. + */ + ERROR; + + /** + * Returns name of severity level. + * + * @return the name of this severity level. + */ + val value: String + get() = name.toLowerCase(Locale.ENGLISH) + + override fun toString(): String { + return value + } +} diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/XMLLogger.kt b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/XMLLogger.kt new file mode 100644 index 0000000..e60b7c5 --- /dev/null +++ b/markdown/src/main/kotlin/com/appmattus/markdown/checkstyle/XMLLogger.kt @@ -0,0 +1,286 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2019 the original author or authors. +// +// This library 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 2.1 of the License, or (at your option) any later version. +// +// This library 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 library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// + +package com.appmattus.markdown.checkstyle + +import java.io.OutputStream +import java.io.PrintWriter +import java.io.StringWriter +import java.util.Collections +import java.util.concurrent.ConcurrentHashMap + +/** + * Simple XML logger. + * It outputs everything in UTF-8 (default XML encoding is UTF-8) in case + * we want to localize error messages or simply that file names are + * localized and takes care about escaping as well. + */ +// -@cs[AbbreviationAsWordInName] We can not change it as, +// check's name is part of API (used in configurations). +@Suppress("TooManyFunctions") +class XMLLogger : AuditListener { + + /** + * Close output stream in auditFinished. + */ + private val closeStream: Boolean + + /** + * The writer lock object. + */ + private val writerLock = Any() + + /** + * Holds all messages for the given file. + */ + private val fileMessages = ConcurrentHashMap() + + /** + * Helper writer that allows easy encoding and printing. + */ + private val writer: PrintWriter + + /** + * Creates a new `XMLLogger` instance. + * Sets the output to a defined stream. + * + * @param outputStream the stream to write logs to. + * @param closeStream close oS in auditFinished + * @noinspection BooleanParameter + */ + @Deprecated("in order to fulfill demands of BooleanParameter IDEA check.") + constructor(outputStream: OutputStream, closeStream: Boolean) { + writer = PrintWriter(outputStream.writer()) + this.closeStream = closeStream + } + + /** + * Creates a new `XMLLogger` instance. + * Sets the output to a defined stream. + * + * @param outputStream the stream to write logs to. + * @param outputStreamOptions if `CLOSE` stream should be closed in auditFinished() + */ + constructor(outputStream: OutputStream, outputStreamOptions: OutputStreamOptions?) { + writer = PrintWriter(outputStream.writer()) + if (outputStreamOptions == null) { + throw IllegalArgumentException("Parameter outputStreamOptions can not be null") + } + closeStream = outputStreamOptions == OutputStreamOptions.CLOSE + } + + override fun auditStarted() { + writer.println("") + + val version = "8.18" + + writer.println("") + } + + override fun auditFinished() { + writer.println("") + if (closeStream) { + writer.close() + } else { + writer.flush() + } + } + + override fun fileStarted(event: AuditEvent) { + fileMessages[event.fileName] = FileMessages() + } + + override fun fileFinished(event: AuditEvent) { + val fileName = event.fileName + val messages = fileMessages[fileName] + + synchronized(writerLock) { + writeFileMessages(fileName, messages) + } + + fileMessages.remove(fileName) + } + + /** + * Prints the file section with all file errors and exceptions. + * + * @param fileName The file name, as should be printed in the opening file tag. + * @param messages The file messages. + */ + private fun writeFileMessages(fileName: String, messages: FileMessages?) { + writeFileOpeningTag(fileName) + if (messages != null) { + for (errorEvent in messages.getErrors()) { + writeFileError(errorEvent) + } + for (exception in messages.getExceptions()) { + writeException(exception) + } + } + writeFileClosingTag() + } + + /** + * Prints the "file" opening tag with the given filename. + * + * @param fileName The filename to output. + */ + private fun writeFileOpeningTag(fileName: String) { + writer.println("") + } + + /** + * Prints the "file" closing tag. + */ + private fun writeFileClosingTag() { + writer.println("") + } + + override fun addError(event: AuditEvent) { + if (event.severityLevel != SeverityLevel.IGNORE) { + val fileName = event.fileName + if (!fileMessages.containsKey(fileName)) { + synchronized(writerLock) { + writeFileError(event) + } + } else { + fileMessages.getValue(fileName).addError(event) + } + } + } + + /** + * Outputs the given event to the writer. + * + * @param event An event to print. + */ + private fun writeFileError(event: AuditEvent) { + writer.print(" 0) { + writer.print(" column=\"" + event.column + "\"") + } + writer.print( + " severity=\"" + + event.severityLevel.value + + "\"" + ) + writer.print( + " message=\"" + + encode(event.message) + + "\"" + ) + writer.print(" source=\"") + writer.print(encode(event.sourceName)) + writer.println("\"/>") + } + + /** + * Writes the exception event to the print writer. + * + * @param throwable The + */ + private fun writeException(throwable: Throwable) { + writer.println("") + writer.println("") + writer.println("") + } + + /** + * Escape <, > & ' and " as their entities. + * + * @param value the value to escape. + * @return the escaped value if necessary. + */ + @Suppress("ComplexMethod") + private fun encode(value: String): String { + val sb = StringBuilder() + for (i in 0 until value.length) { + when (val chr = value[i]) { + '<' -> sb.append("<") + '>' -> sb.append(">") + '\'' -> sb.append("'") + '\"' -> sb.append(""") + '&' -> sb.append("&") + '\r' -> { + } + '\n' -> sb.append(" ") + else -> if (Character.isISOControl(chr)) { + // true escape characters need '&' before but it also requires XML 1.1 + // until https://github.com/checkstyle/checkstyle/issues/5168 + sb.append("#x") + sb.append(Integer.toHexString(chr.toInt())) + sb.append(';') + } else { + sb.append(chr) + } + } + } + return sb.toString() + } + + /** + * The registered file messages. + */ + private class FileMessages { + + /** + * The file error events. + */ + private val errors = Collections.synchronizedList(mutableListOf()) + + /** + * The file exceptions. + */ + private val exceptions = Collections.synchronizedList(mutableListOf()) + + /** + * Returns the file error events. + * + * @return the file error events. + */ + fun getErrors(): List { + return Collections.unmodifiableList(errors) + } + + /** + * Adds the given error event to the messages. + * + * @param event the error event. + */ + fun addError(event: AuditEvent) { + errors.add(event) + } + + /** + * Returns the file exceptions. + * + * @return the file exceptions. + */ + fun getExceptions(): List { + return Collections.unmodifiableList(exceptions) + } + } +} diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/dsl/Report.kt b/markdown/src/main/kotlin/com/appmattus/markdown/dsl/Report.kt index 6b080a3..4722a9c 100644 --- a/markdown/src/main/kotlin/com/appmattus/markdown/dsl/Report.kt +++ b/markdown/src/main/kotlin/com/appmattus/markdown/dsl/Report.kt @@ -1,4 +1,4 @@ -package com.appmattus.markdown.dsl; +package com.appmattus.markdown.dsl enum class Report { Html, Checkstyle; diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/processing/ParserFactory.kt b/markdown/src/main/kotlin/com/appmattus/markdown/processing/ParserFactory.kt index 63c91a7..f505e7d 100644 --- a/markdown/src/main/kotlin/com/appmattus/markdown/processing/ParserFactory.kt +++ b/markdown/src/main/kotlin/com/appmattus/markdown/processing/ParserFactory.kt @@ -10,18 +10,15 @@ internal object ParserFactory { private val options by lazy { MutableDataSet().apply { set(Parser.HEADING_NO_ATX_SPACE, true) - //set(Parser.HEADING_NO_EMPTY_HEADING_WITHOUT_SPACE, true) - //set(Parser.HEADING_NO_LEAD_SPACE, true) } } - val parser by lazy { + val parser: Parser by lazy { Parser.builder(options).extensions( listOf( TablesExtension.create(), StrikethroughExtension.create(), AutolinkExtension.create() - //GfmIssuesExtension.create() ) ).build() } diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/processing/RuleProcessor.kt b/markdown/src/main/kotlin/com/appmattus/markdown/processing/RuleProcessor.kt index 81a21da..bc59a73 100644 --- a/markdown/src/main/kotlin/com/appmattus/markdown/processing/RuleProcessor.kt +++ b/markdown/src/main/kotlin/com/appmattus/markdown/processing/RuleProcessor.kt @@ -1,14 +1,14 @@ package com.appmattus.markdown.processing +import com.appmattus.markdown.checkstyle.AuditEvent +import com.appmattus.markdown.checkstyle.LocalizedMessage +import com.appmattus.markdown.checkstyle.OutputStreamOptions +import com.appmattus.markdown.checkstyle.SeverityLevel +import com.appmattus.markdown.checkstyle.XMLLogger import com.appmattus.markdown.dsl.MarkdownLintConfig import com.appmattus.markdown.dsl.Report import com.appmattus.markdown.errors.Error import com.appmattus.markdown.rules.AllRules -import com.puppycrawl.tools.checkstyle.XMLLogger -import com.puppycrawl.tools.checkstyle.api.AuditEvent -import com.puppycrawl.tools.checkstyle.api.AutomaticBean -import com.puppycrawl.tools.checkstyle.api.LocalizedMessage -import com.puppycrawl.tools.checkstyle.api.SeverityLevel import java.io.ByteArrayOutputStream import java.io.File import java.io.PrintStream @@ -120,30 +120,29 @@ class RuleProcessor { private fun Map>.mapErrorsToCheckstyleXmlBytes(): ByteArray { return ByteArrayOutputStream().use { - val logger = XMLLogger(it, AutomaticBean.OutputStreamOptions.NONE) + val logger = XMLLogger(it, OutputStreamOptions.NONE) - logger.auditStarted(null) + logger.auditStarted() this.forEach { file, errors -> val filePath = file.path - logger.fileStarted(AuditEvent(this, filePath)) + logger.fileStarted(AuditEvent(filePath)) errors.forEach { error -> val message = LocalizedMessage( error.lineNumber, error.columnNumber, - "messages.properties", error.errorMessage, null, SeverityLevel.ERROR, null, - error.ruleClass, null + error.errorMessage, SeverityLevel.ERROR, error.ruleClass ) - logger.addError(AuditEvent(this, filePath, message)) + logger.addError(AuditEvent(filePath, message)) } - logger.fileFinished(AuditEvent(this, filePath)) + logger.fileFinished(AuditEvent(filePath)) } - logger.auditFinished(null) + logger.auditFinished() it.toByteArray() } diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/rules/BlanksAroundListsRule.kt b/markdown/src/main/kotlin/com/appmattus/markdown/rules/BlanksAroundListsRule.kt index ca7ef81..6f43984 100644 --- a/markdown/src/main/kotlin/com/appmattus/markdown/rules/BlanksAroundListsRule.kt +++ b/markdown/src/main/kotlin/com/appmattus/markdown/rules/BlanksAroundListsRule.kt @@ -52,7 +52,7 @@ class BlanksAroundListsRule( override val tags = listOf("bullet", "ul", "ol", "blank_lines") private val fenceRegEx = Regex("^(`{3,}|~{3,})") - private val listRegEx = Regex("^([\\*\\+\\-]|(\\d+\\.))\\s") + private val listRegEx = Regex("^([*+\\-]|(\\d+\\.))\\s") private val emptyRegEx = Regex("^(\\s|$)") override fun visitDocument(document: MarkdownDocument, errorReporter: ErrorReporter) { diff --git a/markdown/src/main/kotlin/com/appmattus/markdown/rules/ConsistentUlStyleRule.kt b/markdown/src/main/kotlin/com/appmattus/markdown/rules/ConsistentUlStyleRule.kt index 4dac005..9392cdb 100644 --- a/markdown/src/main/kotlin/com/appmattus/markdown/rules/ConsistentUlStyleRule.kt +++ b/markdown/src/main/kotlin/com/appmattus/markdown/rules/ConsistentUlStyleRule.kt @@ -1,4 +1,4 @@ -package com.appmattus.markdown.rules; +package com.appmattus.markdown.rules import com.appmattus.markdown.dsl.RuleSetup import com.appmattus.markdown.errors.ErrorReporter