net.sourceforge.pmd.lang.test.ast.NodePrinters.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pmd-lang-test Show documentation
Show all versions of pmd-lang-test Show documentation
Module containing utilities to test language implementations,
including parsers and ASTs. This module uses Kotlin.
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.test.ast
import net.sourceforge.pmd.lang.ast.Node
import net.sourceforge.pmd.lang.document.Chars
import net.sourceforge.pmd.lang.rule.xpath.Attribute
import net.sourceforge.pmd.util.StringUtil
import net.sourceforge.pmd.util.treeexport.TextTreeRenderer
/**
* Prints just the structure, like so:
*
* └── LocalVariableDeclaration
* ├── Type
* │ └── PrimitiveType
* └── VariableDeclarator
* ├── VariableId
* └── VariableInitializer
* └── 1 child not shown
*
*/
val SimpleNodePrinter = TextTreeRenderer(true, -1)
open class RelevantAttributePrinter : BaseNodeAttributePrinter() {
private val defaultIgnoredAttributes = setOf("BeginLine", "EndLine", "BeginColumn", "EndColumn", "FindBoundary", "SingleLine")
override fun ignoreAttribute(node: Node, attribute: Attribute): Boolean =
defaultIgnoredAttributes.contains(attribute.name) || attribute.name == "Image" && attribute.value == null
}
/**
* Only prints the begin/end coordinates.
*/
object CoordinatesPrinter : BaseNodeAttributePrinter() {
private val Considered = setOf("BeginLine", "EndLine", "BeginColumn", "EndColumn")
override fun fillAttributes(node: Node, result: MutableList) {
result += AttributeInfo("BeginLine", node.beginLine)
result += AttributeInfo("EndLine", node.endLine)
result += AttributeInfo("EndColumn", node.endColumn)
result += AttributeInfo("BeginColumn", node.beginColumn)
}
override fun ignoreAttribute(node: Node, attribute: Attribute): Boolean =
attribute.name !in Considered
}
/**
* Base attribute printer, subclass to filter attributes.
*/
open class BaseNodeAttributePrinter : TextTreeRenderer(true, -1) {
data class AttributeInfo(val name: String, val value: Any?)
protected open fun ignoreAttribute(node: Node, attribute: Attribute): Boolean = true
protected open fun fillAttributes(node: Node, result: MutableList) {
node.xPathAttributesIterator
.asSequence()
.filterNot { ignoreAttribute(node, it) }
.map { AttributeInfo(it.name, it.value) }
.forEach { result += it }
}
override fun appendNodeInfoLn(out: Appendable, node: Node) {
out.append(node.xPathNodeName)
val attrs = mutableListOf().also { fillAttributes(node, it) }
// sort to get deterministic results
attrs.sortBy { it.name }
attrs.joinTo(buffer = out, prefix = "[", postfix = "]") {
"@${it.name} = ${valueToString(it.value)}"
}
out.append("\n")
}
protected open fun valueToString(value: Any?): String? {
return when (value) {
is String, is Chars -> "\"" + StringUtil.escapeJava(value.toString()) + "\""
is Char -> '\''.toString() + value.toString().replace("'".toRegex(), "\\'") + '\''.toString()
is Enum<*> -> value.enumDeclaringClass.simpleName + "." + value.name
is Class<*> -> value.canonicalName?.let { "$it.class" }
is Number, is Boolean -> value.toString()
is Collection<*> -> value.joinToString(prefix = "(", postfix = ")", separator = ", ") {
"${valueToString(it)}"
}
null -> "null"
else -> null
}
}
private val Enum<*>.enumDeclaringClass: Class<*>
get() = this.javaClass.let {
when {
it.isEnum -> it
else -> it.enclosingClass.takeIf { clazz -> clazz.isEnum }
?: throw IllegalStateException()
}
}
}