Added Scala AST Parser

This commit is contained in:
Chris Smith 2019-08-08 18:45:26 -04:00
parent 1a5486dc30
commit 0f7d5f1ec0
38 changed files with 1268 additions and 382 deletions

View File

@ -95,7 +95,7 @@ public class BinaryDistributionIT {
ExecutionResult result;
result = PMDExecutor.runPMD(tempDir, "-h");
result.assertExecutionResult(0, "apex, ecmascript, java, jsp, plsql, pom, vf, vm, wsdl, xml, xsl");
result.assertExecutionResult(0, "apex, ecmascript, java, jsp, plsql, pom, scala, vf, vm, wsdl, xml, xsl");
result = PMDExecutor.runPMDRules(tempDir, srcDir, "src/test/resources/rulesets/sample-ruleset.xml");
result.assertExecutionResult(4, "JumbledIncrementer.java:8:");

View File

@ -1,116 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>pmd-scala</artifactId>
<name>PMD Scala</name>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>pmd-scala</artifactId>
<name>PMD Scala</name>
<parent>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd</artifactId>
<version>6.18.0-SNAPSHOT</version>
</parent>
<parent>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd</artifactId>
<version>6.18.0-SNAPSHOT</version>
</parent>
<properties>
<scala.version>2.12.4</scala.version>
</properties>
<properties>
<scalameta.version>4.2.0</scalameta.version>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.3.1</version>
</plugin>
</plugins>
</pluginManagement>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<useDefaultDelimiters>false</useDefaultDelimiters>
<delimiters>
<delimiter>${*}</delimiter>
</delimiters>
</configuration>
</plugin>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<useDefaultDelimiters>false</useDefaultDelimiters>
<delimiters>
<delimiter>${*}</delimiter>
</delimiters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<suppressionsLocation>pmd-scala-checkstyle-suppressions.xml</suppressionsLocation>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<configuration>
<addScalacArgs>-deprecation</addScalacArgs>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>doc-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Disabling the default javadoc plugin - we use scala-maven-plugin
instead -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>none</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<suppressionsLocation>pmd-scala-checkstyle-suppressions.xml</suppressionsLocation>
</configuration>
</plugin>
<dependencies>
<dependency>
<groupId>org.scalameta</groupId>
<artifactId>scalameta_2.13</artifactId>
<version>${scalameta.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-core</artifactId>
</dependency>
<!-- Disabling the default javadoc plugin - we use scala-maven-plugin instead -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>none</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-core</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -4,10 +4,8 @@
package net.sourceforge.pmd.cpd;
import org.sonar.plugins.scala.cpd.ScalaTokenizer;
/**
* Language implementation for Scala
* Language implementation for Scala.
*/
public class ScalaLanguage extends AbstractLanguage {

View File

@ -0,0 +1,119 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.cpd;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.scala.ScalaLanguageHandler;
import net.sourceforge.pmd.lang.scala.ScalaLanguageModule;
import scala.collection.JavaConverters;
import scala.meta.Dialect;
import scala.meta.inputs.Input;
import scala.meta.internal.tokenizers.ScalametaTokenizer;
/**
* Scala Tokenizer class. Uses the Scala Meta Tokenizer.
*/
public class ScalaTokenizer implements Tokenizer {
/**
* Denotes the version of the scala dialect to use. Based on the values in
* {@linkplain ScalaLanguageModule#getVersions()}
*/
public static final String SCALA_VERSION_PROPERTY = "scala_version";
private final Dialect dialect;
/**
* Create the Tokenizer using properties from the system environment.
*/
public ScalaTokenizer() {
this(System.getProperties());
}
/**
* Create the Tokenizer given a set of properties.
*
* @param properties
* the {@linkplain Properties} object to use
*/
public ScalaTokenizer(Properties properties) {
String scalaVersion = properties.getProperty(SCALA_VERSION_PROPERTY);
LanguageVersion langVer;
if (scalaVersion == null) {
langVer = LanguageRegistry.getLanguage(ScalaLanguageModule.NAME).getDefaultVersion();
} else {
langVer = LanguageRegistry.getLanguage(ScalaLanguageModule.NAME).getVersion(scalaVersion);
}
dialect = ((ScalaLanguageHandler) langVer.getLanguageVersionHandler()).getDialect();
}
@Override
public void tokenize(SourceCode sourceCode, Tokens tokenEntries) throws IOException {
String filename = sourceCode.getFileName();
// create the full code file
String fullCode = StringUtils.join(sourceCode.getCode(), "\n");
// create the input file for scala
Input.VirtualFile vf = new Input.VirtualFile(filename, fullCode);
ScalametaTokenizer tokenizer = new ScalametaTokenizer(vf, dialect);
// tokenize with a filter
scala.meta.tokens.Tokens tokens = tokenizer.tokenize();
List<scala.meta.tokens.Token> tokenList = JavaConverters.asJava(tokens.toList());
ScalaTokenFilter filter = new ScalaTokenFilter(tokenList);
scala.meta.tokens.Token token;
while ((token = filter.getNextToken()) != null) {
String tokenText = token.text() != null ? token.text() : token.name();
TokenEntry cpdToken = new TokenEntry(tokenText, filename, token.pos().startLine());
tokenEntries.add(cpdToken);
}
}
/**
* Token Filter skips un-helpful tokens to only register important tokens
* and patterns.
*/
private static class ScalaTokenFilter {
Iterator<scala.meta.tokens.Token> tokenListIter;
ScalaTokenFilter(List<scala.meta.tokens.Token> tokenList) {
this.tokenListIter = tokenList.iterator();
}
scala.meta.tokens.Token getNextToken() {
if (!tokenListIter.hasNext()) {
return null;
}
scala.meta.tokens.Token token;
do {
token = tokenListIter.next();
} while (token != null && skipToken(token) && tokenListIter.hasNext());
return token;
}
private boolean skipToken(scala.meta.tokens.Token token) {
boolean skip = false;
if (token.text() != null) {
// skip any token that is whitespaces
skip |= token instanceof scala.meta.tokens.Token.Space || token instanceof scala.meta.tokens.Token.Tab
|| token instanceof scala.meta.tokens.Token.CR || token instanceof scala.meta.tokens.Token.LF
|| token instanceof scala.meta.tokens.Token.FF || token instanceof scala.meta.tokens.Token.LFLF;
}
return skip;
}
}
}

View File

@ -0,0 +1,66 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala;
import java.io.Writer;
import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
import net.sourceforge.pmd.lang.scala.ast.DumpFacade;
import net.sourceforge.pmd.lang.scala.ast.ScalaNode;
import net.sourceforge.pmd.lang.scala.rule.ScalaRuleViolationFactory;
import scala.meta.Dialect;
/**
* The Scala Language Handler implementation.
*/
public class ScalaLanguageHandler extends AbstractLanguageVersionHandler {
private final Dialect dialect;
/**
* Create the Language Handler using the given Scala Dialect.
*
* @param scalaDialect
* the language version to use while parsing etc
*/
public ScalaLanguageHandler(Dialect scalaDialect) {
this.dialect = scalaDialect;
}
/**
* Get the Scala Dialect used in this language version choice.
*
* @return the Scala Dialect for this handler
*/
public Dialect getDialect() {
return this.dialect;
}
@Override
public RuleViolationFactory getRuleViolationFactory() {
return ScalaRuleViolationFactory.INSTANCE;
}
@Override
public ScalaParser getParser(ParserOptions parserOptions) {
return new ScalaParser(dialect, parserOptions);
}
@Override
public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) {
return new VisitorStarter() {
@Override
public void start(Node rootNode) {
new DumpFacade().dump(writer, prefix, recurse, (ScalaNode) rootNode);
}
};
}
}

View File

@ -5,13 +5,11 @@
package net.sourceforge.pmd.lang.scala;
import net.sourceforge.pmd.lang.BaseLanguageModule;
import net.sourceforge.pmd.lang.scala.rule.ScalaRuleChainVisitor;
/**
* Language Module for Scala
*
* @deprecated There is no full PMD support for Scala.
* Language Module for Scala.
*/
@Deprecated
public class ScalaLanguageModule extends BaseLanguageModule {
/** The name. */
@ -23,7 +21,10 @@ public class ScalaLanguageModule extends BaseLanguageModule {
* Create a new instance of Scala Language Module.
*/
public ScalaLanguageModule() {
super(NAME, null, TERSE_NAME, null, "scala");
addVersion("", null, true);
super(NAME, null, TERSE_NAME, ScalaRuleChainVisitor.class, "scala");
addVersion("2.13", new ScalaLanguageHandler(scala.meta.dialects.package$.MODULE$.Scala213()), true);
addVersion("2.12", new ScalaLanguageHandler(scala.meta.dialects.package$.MODULE$.Scala212()), false);
addVersion("2.11", new ScalaLanguageHandler(scala.meta.dialects.package$.MODULE$.Scala211()), false);
addVersion("2.10", new ScalaLanguageHandler(scala.meta.dialects.package$.MODULE$.Scala210()), false);
}
}

View File

@ -0,0 +1,103 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import net.sourceforge.pmd.lang.AbstractParser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.TokenManager;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.scala.ast.ASTSourceNode;
import net.sourceforge.pmd.lang.scala.ast.ScalaWrapperNode;
import scala.meta.Dialect;
import scala.meta.Source;
import scala.meta.Tree;
import scala.meta.inputs.Input;
import scala.meta.internal.parsers.ScalametaParser;
/**
* Scala's Parser implementation. Defers parsing to the scala compiler via
* Scalameta. This parser then wraps all of ScalaMeta's Nodes in Java versions
* for compatibility.
*/
public class ScalaParser extends AbstractParser {
private final Dialect dialect;
private Map<Tree, ScalaWrapperNode> nodeCache = new HashMap<>();
/**
* Create a parser using the given Scala Dialect and set of parser options.
*
* @param scalaDialect
* the Scala Dialect for this parser
* @param parserOptions
* any additional options for this parser
*/
public ScalaParser(Dialect scalaDialect, ParserOptions parserOptions) {
super(parserOptions);
this.dialect = scalaDialect;
}
@Override
public boolean canParse() {
return true;
}
@Override
public Node parse(String fileName, Reader source) throws ParseException {
nodeCache.clear();
Input.VirtualFile virtualFile;
try {
String sourceString = IOUtils.toString(source);
virtualFile = new Input.VirtualFile(fileName, sourceString);
} catch (IOException e) {
throw new ParseException(e);
}
Source src = new ScalametaParser(virtualFile, dialect).parseSource();
ASTSourceNode srcNode = new ASTSourceNode(this, src);
nodeCache.put(src, srcNode);
return srcNode;
}
/**
* Creates a wrapper around the given node so that we can interact with PMD
* systems using the underlying scala node.
*
* @param scalaNode
* a node from Scala's parsing
* @return A Java-wrapped version of the given node, using a cache if this
* has been previously wrapped, or null, if the given node is null
*/
public ScalaWrapperNode wrapNode(Tree scalaNode) {
if (scalaNode == null) {
return null;
}
ScalaWrapperNode node = nodeCache.get(scalaNode);
if (node == null) {
node = new ScalaWrapperNode(this, scalaNode);
nodeCache.put(scalaNode, node);
}
return node;
}
@Override
public Map<Integer, String> getSuppressMap() {
return new HashMap<>(); // FIXME;
}
@Override
protected TokenManager createTokenManager(Reader source) {
return null;
}
}

View File

@ -0,0 +1,28 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.ast;
import net.sourceforge.pmd.lang.ast.RootNode;
import net.sourceforge.pmd.lang.scala.ScalaParser;
import scala.meta.Source;
/**
* The root node for a Scala AST.
*/
public class ASTSourceNode extends ScalaWrapperNode implements RootNode {
/**
* Create a new root node wrapper for the Scala root AST node.
*
* @param scalaParser
* the ScalaParser used to generate the node
* @param scalaNode
* the scalaNode node to wrap
*/
public ASTSourceNode(ScalaParser scalaParser, Source scalaNode) {
super(scalaParser, scalaNode);
}
}

View File

@ -0,0 +1,78 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.ast;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Iterator;
import net.sourceforge.pmd.lang.ast.xpath.Attribute;
/**
* A Dump Facade for Scala for testing purposes.
*/
public class DumpFacade extends ScalaParserVisitorAdapter {
private PrintWriter writer;
private boolean recurse;
/**
* Write the nodes of the tree to the given writer recursively.
*
* @param outWriter
* the writer to write the tree data to
* @param prefix
* a string prefix to use before each line in the writer
* @param shouldRecurse
* should this recurse below the root node?
* @param node
* the node to start with. Not necessarily a tree root.
*/
public void dump(Writer outWriter, String prefix, boolean shouldRecurse, ScalaNode node) {
this.writer = outWriter instanceof PrintWriter ? (PrintWriter) outWriter : new PrintWriter(outWriter);
this.recurse = shouldRecurse;
this.visit(node, prefix);
this.writer.flush();
}
@Override
public Object visit(ScalaNode node, Object data) {
dump(node, (String) data);
if (recurse) {
return super.visit(node, data + " ");
} else {
return data;
}
}
private void dump(ScalaNode node, String prefix) {
writer.print(prefix);
writer.print(node.getXPathNodeName());
String image = node.getImage();
String attrs = null;
Iterator<Attribute> attributeIter = node.getXPathAttributesIterator();
if (attributeIter.hasNext()) {
StringBuilder sb = new StringBuilder();
while (attributeIter.hasNext()) {
Attribute attr = attributeIter.next();
sb.append(attr.getName()).append("=").append(attr.getStringValue()).append(",");
}
attrs = sb.deleteCharAt(sb.length()).toString();
}
if (image != null) {
writer.print(":" + image);
}
if (attrs != null) {
writer.print("[");
writer.print(attrs);
writer.print("]");
}
writer.println();
}
}

View File

@ -0,0 +1,45 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.ast;
import net.sourceforge.pmd.lang.ast.Node;
import scala.meta.Tree;
/**
* A Base interface of a Scala Node. Defines several required methods of all
* nodes.
*/
public interface ScalaNode extends Node {
/**
* Accept a visitor and traverse this node.
*
* @param visitor
* the visitor to visit this node with
* @param data
* context-specific data to pass along
* @return context-specific data for this Visitor pattern
*/
Object accept(ScalaParserVisitor visitor, Object data);
/**
* Accept the visitor against all children of this node if there are any and
* return.
*
* @param visitor
* the visitor to visit this node's children with
* @param data
* context-specific data to pass along
* @return context-specific data for this Visitor pattern
*/
Object childrenAccept(ScalaParserVisitor visitor, Object data);
/**
* Get the underlying Scala Node.
*
* @return the Scala Node for this node
*/
Tree getNode();
}

View File

@ -0,0 +1,32 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.ast;
/**
* A Visitor Pattern Interface for the Scala AST.
*/
public interface ScalaParserVisitor {
/**
* Visit the Source Node (the root node of the tree).
*
* @param node
* the root node of the tree
* @param data
* context-specific data
* @return context-specific data
*/
Object visit(ASTSourceNode node, Object data);
/**
* Visit an arbitrary Scala Node (any node in the tree).
*
* @param node
* the node of the tree
* @param data
* context-specific data
* @return context-specific data
*/
Object visit(ScalaNode node, Object data);
}

View File

@ -0,0 +1,20 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.ast;
/**
* An Adapter for the Scala Parser that implements the Visitor Pattern.
*/
public class ScalaParserVisitorAdapter implements ScalaParserVisitor {
@Override
public Object visit(ScalaNode node, Object data) {
return node.childrenAccept(this, data);
}
@Override
public Object visit(ASTSourceNode node, Object data) {
return visit((ScalaNode) node, data);
}
}

View File

@ -0,0 +1,182 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import net.sourceforge.pmd.lang.ast.AbstractNode;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.xpath.Attribute;
import net.sourceforge.pmd.lang.scala.ScalaParser;
import net.sourceforge.pmd.util.CompoundIterator;
import scala.meta.Tree;
import scala.meta.inputs.Position;
/**
* The Java-wrapper for Scala Nodes to allow interoperability with PMD.
*/
public class ScalaWrapperNode extends AbstractNode implements ScalaNode {
private Tree node;
private ScalaParser parser;
/**
* Create a new Java Wrapper around a Scala node.
*
* @param scalaParser
* the ScalaParser used to generate the node
* @param scalaNode
* the scalaNode node to wrap
*/
public ScalaWrapperNode(ScalaParser scalaParser, Tree scalaNode) {
super(0);
this.parser = scalaParser;
this.node = scalaNode;
Position pos = node.pos();
beginLine = pos.startLine() + 1;
endLine = pos.endLine();
beginColumn = pos.startColumn();
endColumn = pos.endColumn() + 1;
}
@Override
public Tree getNode() {
return node;
}
@Override
public Object accept(ScalaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public Object childrenAccept(ScalaParserVisitor visitor, Object data) {
int numChildren = jjtGetNumChildren();
for (int i = 0; i < numChildren; ++i) {
jjtGetChild(i).accept(visitor, data);
}
return data;
}
@Override
public String getXPathNodeName() {
return "AST" + node.productPrefix().replace(".", "") + "Node";
}
@Override
public void jjtClose() {
throw new UnsupportedOperationException();
}
@Override
public void jjtSetParent(Node parent) {
throw new UnsupportedOperationException();
}
@Override
public ScalaWrapperNode jjtGetParent() {
if (node.parent().isEmpty()) {
return null;
}
return parser.wrapNode(node.parent().get());
}
@Override
public void jjtAddChild(Node child, int index) {
throw new UnsupportedOperationException();
}
@Override
public void jjtSetChildIndex(int index) {
throw new UnsupportedOperationException();
}
@Override
public int jjtGetChildIndex() {
int idx = node.parent().get().children().indexOf(node);
if (idx == -1) {
throw new IllegalStateException("This node is not a child of its parent: " + node);
}
return idx;
}
@Override
public ScalaWrapperNode jjtGetChild(int index) {
return parser.wrapNode(node.children().apply(index));
}
@Override
public int jjtGetNumChildren() {
return node.children().size();
}
@Override
public int jjtGetId() {
return 0;
}
@Override
public String getImage() {
String image = null;
if (node instanceof scala.meta.Lit) {
image = String.valueOf(((scala.meta.Lit) node).value());
} else if (node instanceof scala.meta.Name) {
image = ((scala.meta.Name) node).value().toString();
} else if (node instanceof scala.meta.Type.Name) {
image = ((scala.meta.Type.Name) node).value();
} else if (node instanceof scala.meta.Term.Name) {
image = ((scala.meta.Term.Name) node).value();
} else if (node instanceof scala.meta.Type.Var.Name) {
image = ((scala.meta.Type.Var.Name) node).value();
}
return image;
}
@Override
public void setImage(String image) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasImageEqualTo(String image) {
return Objects.equals(image, getImage());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void removeChildAtIndex(int childIndex) {
throw new UnsupportedOperationException();
}
@Override
public Iterator<Attribute> getXPathAttributesIterator() {
List<Iterator<Attribute>> iterators = new ArrayList<>();
// Possible things we would want to expose to the XPath AST
//
// JavaConverters.asJava(node.productElementNames()).forEachRemaining(System.out::print);
// JavaConverters.asJava(node.productFields()).forEach(System.out::print);
// JavaConverters.asJava(node.productIterator()).forEachRemaining(System.out::print);
String image = getImage();
if (image != null) {
iterators.add(Collections.singletonList(new Attribute(this, "Image", image)).iterator());
}
@SuppressWarnings("unchecked")
Iterator<Attribute>[] it = new Iterator[iterators.size()];
return new CompoundIterator<>(iterators.toArray(it));
}
}

View File

@ -0,0 +1,50 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.rule;
import java.util.List;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.AbstractRule;
import net.sourceforge.pmd.lang.scala.ScalaLanguageModule;
import net.sourceforge.pmd.lang.scala.ast.ASTSourceNode;
import net.sourceforge.pmd.lang.scala.ast.ScalaNode;
import net.sourceforge.pmd.lang.scala.ast.ScalaParserVisitor;
/**
* The default base implementation of a PMD Rule for Scala. Uses the Visitor
* Pattern to traverse the AST.
*/
public class ScalaRule extends AbstractRule implements ScalaParserVisitor {
/**
* Create a new Scala Rule.
*/
public ScalaRule() {
super.setLanguage(LanguageRegistry.getLanguage(ScalaLanguageModule.NAME));
}
@Override
public void apply(List<? extends Node> nodes, RuleContext ctx) {
for (Node node : nodes) {
if (node instanceof ASTSourceNode) {
visit((ASTSourceNode) node, ctx);
}
}
}
@Override
public Object visit(ASTSourceNode node, Object data) {
return visit((ScalaNode) node, data);
}
@Override
public Object visit(ScalaNode node, Object data) {
return node.childrenAccept(this, data);
}
}

View File

@ -0,0 +1,49 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.rule;
import java.util.List;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor;
import net.sourceforge.pmd.lang.rule.XPathRule;
import net.sourceforge.pmd.lang.scala.ast.ASTSourceNode;
import net.sourceforge.pmd.lang.scala.ast.ScalaNode;
import net.sourceforge.pmd.lang.scala.ast.ScalaParserVisitor;
import net.sourceforge.pmd.lang.scala.ast.ScalaParserVisitorAdapter;
import net.sourceforge.pmd.lang.scala.ast.ScalaWrapperNode;
/**
* A Rule Chain visitor for Scala.
*/
public class ScalaRuleChainVisitor extends AbstractRuleChainVisitor {
@Override
protected void visit(Rule rule, Node node, RuleContext ctx) {
// Rule better either be a JavaParserVisitor, or a XPathRule
if (rule instanceof XPathRule) {
((XPathRule) rule).evaluate(node, ctx);
} else {
((ScalaWrapperNode) node).accept((ScalaParserVisitorAdapter) rule, ctx);
}
}
@Override
protected void indexNodes(List<Node> nodes, RuleContext ctx) {
ScalaParserVisitor visitor = new ScalaParserVisitorAdapter() {
@Override
public Object visit(ScalaNode node, Object data) {
indexNode(node);
return super.visit(node, data);
}
};
for (final Node node : nodes) {
visitor.visit((ASTSourceNode) node, ctx);
}
}
}

View File

@ -0,0 +1,37 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.scala.rule;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.AbstractRuleViolationFactory;
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
/**
* A RuleViolationFactory for Scala.
*/
public class ScalaRuleViolationFactory extends AbstractRuleViolationFactory {
/**
* The shared singleton of this RuleViolationFactory.
*/
public static final RuleViolationFactory INSTANCE = new ScalaRuleViolationFactory();
@Override
protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message) {
return new ParametricRuleViolation<Node>(rule, ruleContext, node, message);
}
@Override
protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message,
int beginLine, int endLine) {
ParametricRuleViolation<Node> rv = new ParametricRuleViolation<>(rule, ruleContext, node, message);
rv.setLines(beginLine, endLine);
return rv;
}
}

View File

@ -1,67 +0,0 @@
/*
* Sonar Scala Plugin
* Copyright (C) 2011 - 2014 All contributors
* 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.plugins.scala.cpd;
import java.util.List;
import org.sonar.plugins.scala.compiler.Lexer;
import org.sonar.plugins.scala.compiler.Token;
import net.sourceforge.pmd.cpd.SourceCode;
import net.sourceforge.pmd.cpd.TokenEntry;
import net.sourceforge.pmd.cpd.Tokenizer;
import net.sourceforge.pmd.cpd.Tokens;
import net.sourceforge.pmd.lang.ast.TokenMgrError;
/**
* Scala tokenizer for PMD CPD.
*
* @since 0.1
*/
public final class ScalaTokenizer implements Tokenizer {
@Override
public void tokenize(SourceCode source, Tokens cpdTokens) {
String filename = source.getFileName();
try {
Lexer lexer = new Lexer();
List<Token> tokens = lexer.getTokensOfFile(filename);
for (Token token : tokens) {
String tokenVal = token.tokenVal() != null ? token.tokenVal() : Integer.toString(token.tokenType());
TokenEntry cpdToken = new TokenEntry(tokenVal, filename, token.line());
cpdTokens.add(cpdToken);
}
cpdTokens.add(TokenEntry.getEOF());
} catch (RuntimeException e) {
e.printStackTrace();
// Wrap exceptions of the Scala tokenizer in a TokenMgrError, so
// they are correctly handled
// when CPD is executed with the '--skipLexicalErrors' command line
// option
throw new TokenMgrError(
"Lexical error in file " + filename + ". The scala tokenizer exited with error: " + e.getMessage(),
TokenMgrError.LEXICAL_ERROR);
}
}
}

View File

@ -1,117 +0,0 @@
/*
* Sonar Scala Plugin
* Copyright (C) 2011 - 2014 All contributors
* 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.plugins.scala.language;
import java.io.IOException;
import java.util.List;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.sonar.plugins.scala.util.StringUtils;
/**
* This class implements a Scala comment and the computation of several base
* metrics for a comment.
*
* @author Felix Müller
* @since 0.1
*/
public class Comment {
private final CommentType type;
private final List<String> lines;
public Comment(String content, CommentType type) throws IOException {
lines = StringUtils.convertStringToListOfLines(content);
this.type = type;
}
public int getNumberOfLines() {
return lines.size() - getNumberOfBlankLines() - getNumberOfCommentedOutLinesOfCode();
}
public int getNumberOfBlankLines() {
int numberOfBlankLines = 0;
for (String comment : lines) {
boolean isBlank = true;
for (int i = 0; isBlank && i < comment.length(); i++) {
char character = comment.charAt(i);
if (!Character.isWhitespace(character) && character != '*' && character != '/') {
isBlank = false;
}
}
if (isBlank) {
numberOfBlankLines++;
}
}
return numberOfBlankLines;
}
public int getNumberOfCommentedOutLinesOfCode() {
if (isDocComment()) {
return 0;
}
int numberOfCommentedOutLinesOfCode = 0;
for (String line : lines) {
String strippedLine = org.apache.commons.lang3.StringUtils.strip(line, " /*");
if (CodeDetector.hasDetectedCode(strippedLine)) {
numberOfCommentedOutLinesOfCode++;
}
}
return numberOfCommentedOutLinesOfCode;
}
public boolean isDocComment() {
return type == CommentType.DOC;
}
public boolean isHeaderComment() {
return type == CommentType.HEADER;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(type).append(lines).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Comment)) {
return false;
}
Comment other = (Comment) obj;
return new EqualsBuilder().append(type, other.type).append(lines, other.lines).isEquals();
}
@Override
public String toString() {
final String firstLine = lines.isEmpty() ? "" : lines.get(0);
final String lastLine = lines.isEmpty() ? "" : lines.get(lines.size() - 1);
return new ToStringBuilder(this).append("type", type).append("firstLine", firstLine)
.append("lastLine", lastLine).append("numberOfLines", getNumberOfLines())
.append("numberOfCommentedOutLinesOfCode", getNumberOfCommentedOutLinesOfCode()).toString();
}
}

View File

@ -1,33 +0,0 @@
/*
* Sonar Scala Plugin
* Copyright (C) 2011 - 2014 All contributors
* 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.plugins.scala.language;
/**
* This enum is a helper to distinguish between the different types of comments
* in Sonar.
*
* @author Felix Müller
* @since 0.1
*/
public enum CommentType {
NORMAL, DOC, HEADER;
}

View File

@ -1,44 +0,0 @@
/*
* Sonar Scala Plugin
* Copyright (C) 2011 - 2014 All contributors
* 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.plugins.scala.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
public final class StringUtils {
private StringUtils() {
// to prevent instantiation
}
public static List<String> convertStringToListOfLines(String string) throws IOException {
final List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new StringReader(string))) {
String line = null;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Best Practices"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules which enforce generally accepted best practices.
</description>
</ruleset>

View File

@ -0,0 +1,18 @@
#
# BSD-style license; for more info see http://pmd.sourceforge.net/license.html
#
rulesets.filenames=
#
# categories without rules
#
# category/scala/bestpractices.xml
# category/scala/codestyle.xml
# category/scala/design.xml
# category/scala/documentation.xml
# category/scala/errorprone.xml
# category/scala/multithreading.xml
# category/scala/performance.xml
# category/scala/security.xml

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Code Style"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules which enforce a specific coding style.
</description>
</ruleset>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Design"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that help you discover design issues.
</description>
</ruleset>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Documentation"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that are related to code documentation.
</description>
</ruleset>

View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<ruleset name="Error Prone"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors.
</description>
</ruleset>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Multithreading"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that flag issues when dealing with multiple threads of execution.
</description>
</ruleset>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Performance"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that flag suboptimal code.
</description>
</ruleset>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Security"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that flag potential security flaws.
</description>
</ruleset>

View File

@ -0,0 +1,18 @@
#
# BSD-style license; for more info see http://pmd.sourceforge.net/license.html
#
rulesets.filenames=
#
# categories without rules
#
# category/scala/bestpractices.xml
# category/scala/codestyle.xml
# category/scala/design.xml
# category/scala/documentation.xml
# category/scala/errorprone.xml
# category/scala/multithreading.xml
# category/scala/performance.xml
# category/scala/security.xml

Some files were not shown because too many files have changed in this diff Show More