Compare commits
92 Commits
main
...
pmd_releas
Author | SHA1 | Date | |
---|---|---|---|
|
46573bb566 | ||
|
da08b0757e | ||
|
1c0badd429 | ||
|
e0a9e49560 | ||
|
dba46f2ebd | ||
|
5fb6daea58 | ||
|
2d655f4684 | ||
|
b77de63950 | ||
|
d347e38622 | ||
|
7494011999 | ||
|
3901ad7ddb | ||
|
2e85f1f935 | ||
|
a5efd66b73 | ||
|
a96ccf9741 | ||
|
f60128b142 | ||
|
89ebc32141 | ||
|
b501de3fba | ||
|
79fb7e359d | ||
|
6aa4897e21 | ||
|
ee05fa73cd | ||
|
0170a40dec | ||
|
4d7b4bb1bc | ||
|
a6c1c1f57c | ||
|
9f8246caac | ||
|
f51e41b05c | ||
|
ad66153a2d | ||
|
c8c6aa16dd | ||
|
b998e64819 | ||
|
62fc70e816 | ||
|
34b868ad14 | ||
|
26a862a51b | ||
|
4569842965 | ||
|
fddccb5030 | ||
|
00e27b4a37 | ||
|
b6c6899741 | ||
|
43ecf1e110 | ||
|
5f45a28621 | ||
|
41c44e5d8d | ||
|
040d465137 | ||
|
4e5ca16990 | ||
|
30af4f6381 | ||
|
cece698b04 | ||
|
13ef401a63 | ||
|
589ec3c6da | ||
|
c00ca9e44c | ||
|
31f6b9af1a | ||
|
8eac048c03 | ||
|
3b14b3f5cb | ||
|
adcbdc204f | ||
|
a1b1e3f31c | ||
|
3e8c42384b | ||
|
a00532595a | ||
|
3feda38cc3 | ||
|
3412d55fc6 | ||
|
436b7dcb50 | ||
|
d25dadc4b4 | ||
|
1c051674a0 | ||
|
3daca86045 | ||
|
d95d960de3 | ||
|
ef7478c350 | ||
|
c080fdda08 | ||
|
e603844a53 | ||
|
52b8be6601 | ||
|
7cbb385dbb | ||
|
61c0b4ea7f | ||
|
26fa85a9fa | ||
|
98b903897e | ||
|
55b5aa7f27 | ||
|
ffac9894c9 | ||
|
d557ed9c01 | ||
|
e0b7f6f17c | ||
|
cb9861dd1f | ||
|
7ee96a6b52 | ||
|
ac8e3314a3 | ||
|
7989ae1863 | ||
|
e3708b5030 | ||
|
85e493d55f | ||
|
6abbea51dd | ||
|
9162917346 | ||
|
637006674f | ||
|
63c1a8fe47 | ||
|
063c4228e3 | ||
|
4b0063dfc1 | ||
|
13e36974a5 | ||
|
33c7af965c | ||
|
a66bbf2193 | ||
|
38af8f2433 | ||
|
a6bcfe2b65 | ||
|
6474fde42d | ||
|
8dcde0edb5 | ||
|
70347aedd5 | ||
|
eaf1385595 |
2
pmd/.gitignore
vendored
2
pmd/.gitignore
vendored
@ -3,5 +3,7 @@ bin/
|
||||
.project
|
||||
.classpath
|
||||
.checkstyle
|
||||
.pmd
|
||||
.ruleset
|
||||
.settings/
|
||||
*.patch
|
||||
|
@ -1,4 +1,76 @@
|
||||
???? ??, 2012 - 5.1.0:
|
||||
May 1, 2013 - 5.0.4:
|
||||
|
||||
Fixed bug 254: False+ : UnusedImport with Javadoc @throws
|
||||
Fixed bug 794: False positive on PreserveStackTrace with anonymous inner
|
||||
Fixed bug 1063: False+: ArrayIsStoredDirectly
|
||||
Fixed bug 1080: net.sourceforge.pmd.cpd.CPDTest test failing
|
||||
Fixed bug 1081: Regression: CPD skipping all files when using relative paths
|
||||
Fixed bug 1082: CPD performance issue on larger projects
|
||||
Fixed bug 1085: NullPointerException by at net.sourceforge.pmd.lang.java.rule.design.GodClassRule.visit(GodClassRule.java:313)
|
||||
Fixed bug 1086: Unsupported Element and Attribute in Ant Task Example
|
||||
Fixed bug 1087: PreserveStackTrace (still) ignores initCause()
|
||||
Fixed bug 1089: When changing priority in a custom ruleset, violations reported twice
|
||||
|
||||
|
||||
April 5, 2013 - 5.0.3:
|
||||
|
||||
Fixed bug 938: False positive on LooseCoupling for overriding methods
|
||||
Fixed bug 940: False positive on UnsynchronizedStaticDateFormatter
|
||||
Fixed bug 942: CheckResultSet False Positive and Negative
|
||||
Fixed bug 943: PreserveStackTrace false positive if a StringBuffer exists
|
||||
Fixed bug 945: PMD generates RuleSets it cannot read.
|
||||
Fixed bug 958: Intermittent NullPointerException while loading XPath node attributes
|
||||
Fixed bug 968: Issues with JUnit4 @Test annotation with expected exception (Thanks to Yiannis Paschalidis)
|
||||
Fixed bug 975: false positive in ClassCastExceptionWithToArray
|
||||
Fixed bug 976: UselessStringValueOf wrong when appending character arrays
|
||||
Fixed bug 977: MisplacedNullCheck makes false positives
|
||||
Fixed bug 984: Cyclomatic complexity should treat constructors like methods
|
||||
Fixed bug 985: Suppressed methods shouldn't affect avg CyclomaticComplexity
|
||||
Fixed bug 992: Class java.beans.Statement triggered in CloseResource rule
|
||||
Fixed bug 997: Rule NonThreadSafeSingleton gives analysis problem
|
||||
Fixed bug 999: Law of Demeter: False positives and negatives
|
||||
Fixed bug 1002: False +: FinalFieldCouldBeStatic on inner class
|
||||
Fixed bug 1005: False + for ConstructorCallsOverridableMethod - overloaded methods
|
||||
Fixed bug 1027: PMD Ant: java.lang.ClassCastException
|
||||
Fixed bug 1032: ImmutableField Rule: Private field in inner class gives false positive
|
||||
Fixed bug 1064: Exception running PrematureDeclaration
|
||||
Fixed bug 1068: CPD fails on broken symbolic links
|
||||
Fixed bug 1073: Hard coded violation messages CommentSize
|
||||
Fixed bug 1074: rule priority doesn't work on group definitions
|
||||
Fixed bug 1076: Report.treeIterator() does not return all violations
|
||||
Fixed bug 1077: Missing JavaDocs for Xref-Test Files
|
||||
Fixed bug 1078: Package statement introduces false positive UnnecessaryFullyQualifiedName violation
|
||||
Merged pull request #14: fix Nullpointer Exception when using -l jsp
|
||||
|
||||
|
||||
|
||||
February 3, 2013 - 5.0.2:
|
||||
|
||||
Fixed bug 878: False positive: UnusedFormalParameter for abstract methods
|
||||
Fixed bug 913: SignatureDeclareThrowsException is raised twice
|
||||
Fixed bug 947: CloseResource rule fails if field is marked with annotation
|
||||
Fixed bug 1004: targetjdk isn't attribute of PMD task
|
||||
Fixed bug 1007: Parse Exception with annotation
|
||||
Fixed bug 1011: CloseResource Rule ignores Constructors
|
||||
Fixed bug 1012: False positive: Useless parentheses.
|
||||
Fixed bug 1020: Parsing Error
|
||||
Fixed bug 1026: PMD doesn't handle 'value =' in SuppressWarnings annotation
|
||||
Fixed bug 1028: False-positive: Compare objects with equals for Enums
|
||||
Fixed bug 1030: CPD Java.lang.IndexOutOfBoundsException: Index:
|
||||
Fixed bug 1037: Facing a showstopper issue in PMD Report Class (report listeners)
|
||||
Fixed bug 1039: pmd-nicerhtml.xsl is packaged in wrong location
|
||||
Fixed bug 1043: node.getEndLine() always returns 0 (ECMAscript)
|
||||
Fixed bug 1044: Unknown option: -excludemarker
|
||||
Fixed bug 1046: ant task CPDTask doesn't accept ecmascript
|
||||
Fixed bug 1047: False Positive in 'for' loops for LocalVariableCouldBeFinal in 5.0.1
|
||||
Fixed bug 1048: CommentContent Rule, String Index out of range Exception
|
||||
Fixed bug 1049: Errors in "How to write a rule"
|
||||
Fixed bug 1055: Please add a colon in the ant output after line,column for Oracle JDeveloper IDE usage
|
||||
Fixed bug 1056: "Error while processing" while running on xml file with DOCTYPE reference
|
||||
Fixed bug 1060: GodClassRule >>> wrong method
|
||||
|
||||
|
||||
November 28, 2012 - 5.0.1:
|
||||
|
||||
Fixed bug 820: False+ AvoidReassigningParameters
|
||||
Fixed bug 1008: pmd-5.0.0: ImmutableField false positive on self-inc/dec
|
||||
@ -471,7 +543,7 @@ The RuleSet XML Schema namespace is now: http://pmd.sourceforge.net/ruleset/2.0.
|
||||
The RuleSet XML Schema is located in the source at: etc/ruleset_2_0_0.xsd
|
||||
The RuleSet DTD is located in the source at: etc/ruleset_2_0_0.dtd
|
||||
Improved include/exclude pattern matching performance for ends-with type patterns.
|
||||
Modify (and hopefully fixed) CPD algorithm thanks to a patch from Juan Jesús García de Soria.
|
||||
Modify (and hopefully fixed) CPD algorithm thanks to a patch from Juan Jesús GarcÃa de Soria.
|
||||
Fixed character reference in xml report - thanks to Seko
|
||||
Enhanced SuspiciousEqualsMethodName rule - thanks to Andy Throgmorton
|
||||
Add a script to launch CPDGUI on Unix system - thanks to Tom Wheeler
|
||||
@ -535,7 +607,7 @@ Fixed bug 2835074 - False -: DoubleCheckedLocking with reversed null check
|
||||
Fixed bug 2826119 - False +: DoubleCheckedLocking warning with volatile field
|
||||
Fixed bug 2904832 - Type resolution not working for ASTType when using an inner class
|
||||
|
||||
Modify (and hopefully fixed) CPD algorithm thanks to a patch from Juan Jesús García de Soria.
|
||||
Modify (and hopefully fixed) CPD algorithm thanks to a patch from Juan Jesús GarcÃa de Soria.
|
||||
Correct -benchmark reporting of Rule visits via the RuleChain
|
||||
Fix issue with Type Resolution incorrectly handling of Classes with same name as a java.lang Class.
|
||||
The JSP/JSF parser can now parse Unicode input.
|
||||
|
@ -1,4 +1,12 @@
|
||||
/**
|
||||
* Fix ForStatement to allow Annotations within the initializer.
|
||||
*
|
||||
* Andreas Dangel 01/2013
|
||||
* ===================================================================
|
||||
* Fix wrong consumption of modifiers (e.g. "final") in a for-each loop.
|
||||
*
|
||||
* Andreas Dangel 12/2012
|
||||
* ===================================================================
|
||||
* Enhance grammar to use LocalVariableDeclaration in a for-each loop.
|
||||
* This enhances the symbol table to recognize variables declared in such
|
||||
* a for-each loop.
|
||||
@ -1914,9 +1922,9 @@ void ForStatement() :
|
||||
{
|
||||
"for" "("
|
||||
(
|
||||
LOOKAHEAD(Modifiers() Type() <IDENTIFIER> ":")
|
||||
LOOKAHEAD(LocalVariableDeclaration() ":")
|
||||
{checkForBadJDK15ForLoopSyntaxArgumentsUsage();}
|
||||
Modifiers() LocalVariableDeclaration() ":" Expression()
|
||||
LocalVariableDeclaration() ":" Expression()
|
||||
|
|
||||
[ ForInit() ] ";"
|
||||
[ Expression() ] ";"
|
||||
@ -1928,7 +1936,7 @@ void ForStatement() :
|
||||
void ForInit() :
|
||||
{}
|
||||
{
|
||||
LOOKAHEAD( [ "final" ] Type() <IDENTIFIER> )
|
||||
LOOKAHEAD( LocalVariableDeclaration() )
|
||||
LocalVariableDeclaration()
|
||||
|
|
||||
StatementExpressionList()
|
||||
|
70
pmd/pom.xml
70
pmd/pom.xml
@ -1,11 +1,10 @@
|
||||
<?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">
|
||||
<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>
|
||||
<groupId>net.sourceforge.pmd</groupId>
|
||||
<artifactId>pmd</artifactId>
|
||||
<name>PMD</name>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.0.4</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
@ -233,6 +232,7 @@
|
||||
<connection>scm:git:git://github.com/pmd/pmd.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/pmd/pmd.git</developerConnection>
|
||||
<url>https://github.com/pmd/pmd</url>
|
||||
<tag>pmd_releases/5.0.4</tag>
|
||||
</scm>
|
||||
<organization>
|
||||
<name>InfoEther</name>
|
||||
@ -246,7 +246,7 @@
|
||||
If the xdocs files stay in src/site/xdoc/, mvn tries to copy over the generated
|
||||
one, and complains... -->
|
||||
<src.xdocs.dir>src/site/xdocs</src.xdocs.dir>
|
||||
<pmd.website.baseurl>http://pmd.sourceforge.net/snapshot</pmd.website.baseurl>
|
||||
<pmd.website.baseurl>http://pmd.sourceforge.net/pmd-${project.version}</pmd.website.baseurl>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@ -333,10 +333,8 @@
|
||||
<configuration>
|
||||
<target>
|
||||
<ant antfile="src/main/ant/alljavacc.xml">
|
||||
<property name="target"
|
||||
value="${project.build.directory}/generated-sources/javacc" />
|
||||
<property name="javacc.jar"
|
||||
value="${settings.localRepository}/net/java/dev/javacc/javacc/${javacc.version}/javacc-${javacc.version}.jar" />
|
||||
<property name="target" value="${project.build.directory}/generated-sources/javacc" />
|
||||
<property name="javacc.jar" value="${settings.localRepository}/net/java/dev/javacc/javacc/${javacc.version}/javacc-${javacc.version}.jar" />
|
||||
</ant>
|
||||
</target>
|
||||
</configuration>
|
||||
@ -350,15 +348,11 @@
|
||||
<configuration>
|
||||
<target>
|
||||
<echo>PMD specific tasks: generating xdocs from rulesets</echo>
|
||||
<mkdir
|
||||
dir="${project.build.directory}/generated-xdocs/" />
|
||||
<copy
|
||||
toDir="${project.build.directory}/generated-xdocs/"
|
||||
overwrite="true" verbose="true">
|
||||
<mkdir dir="${project.build.directory}/generated-xdocs/" />
|
||||
<copy toDir="${project.build.directory}/generated-xdocs/" overwrite="true" verbose="true">
|
||||
<fileset dir="${src.xdocs.dir}" />
|
||||
<filterset>
|
||||
<filter token="VERSION"
|
||||
value="${project.version}" />
|
||||
<filter token="VERSION" value="${project.version}" />
|
||||
</filterset>
|
||||
</copy>
|
||||
</target>
|
||||
@ -374,25 +368,18 @@
|
||||
<target>
|
||||
<echo>PMD site specific tasks</echo>
|
||||
<echo>1. Copying missing images to site directory.</echo>
|
||||
<copy
|
||||
todir="${project.build.directory}/site/images/">
|
||||
<fileset dir="${src.xdocs.dir}/images/"
|
||||
includes="**/*.*" />
|
||||
<copy todir="${project.build.directory}/site/images/">
|
||||
<fileset dir="${src.xdocs.dir}/images/" includes="**/*.*" />
|
||||
</copy>
|
||||
<echo>2. Adding missing text files to site.</echo>
|
||||
<copy todir="${project.build.directory}/site/">
|
||||
<fileset dir="${src.xdocs.dir}/"
|
||||
includes="**/*.txt" />
|
||||
<fileset dir="${src.xdocs.dir}/" includes="**/*.txt" />
|
||||
</copy>
|
||||
<echo>3. Deleting useless generated files.</echo>
|
||||
<delete quiet="true">
|
||||
<fileset dir="${src.xdocs.dir}/rules"
|
||||
includes="**/*.xml" />
|
||||
<fileset dir="${src.xdocs.dir}/"
|
||||
includes="mergedruleset.xml" />
|
||||
<fileset
|
||||
dir="${project.build.directory}"
|
||||
includes="site/mergedruleset.html" />
|
||||
<fileset dir="${src.xdocs.dir}/rules" includes="**/*.xml" />
|
||||
<fileset dir="${src.xdocs.dir}/" includes="mergedruleset.xml" />
|
||||
<fileset dir="${project.build.directory}" includes="site/mergedruleset.html" />
|
||||
</delete>
|
||||
</target>
|
||||
</configuration>
|
||||
@ -407,10 +394,8 @@
|
||||
<target>
|
||||
<echo>PMD specific tasks: cleaning generated xdocs</echo>
|
||||
<delete quiet="true">
|
||||
<fileset dir="${src.xdocs.dir}/rules"
|
||||
includes="**/*.xml" />
|
||||
<fileset dir="${src.xdocs.dir}/"
|
||||
includes="mergedruleset.xml" />
|
||||
<fileset dir="${src.xdocs.dir}/rules" includes="**/*.xml" />
|
||||
<fileset dir="${src.xdocs.dir}/" includes="mergedruleset.xml" />
|
||||
</delete>
|
||||
</target>
|
||||
</configuration>
|
||||
@ -492,7 +477,7 @@
|
||||
<plugin>
|
||||
<groupId>net.sourceforge.pmd</groupId>
|
||||
<artifactId>pmd-build</artifactId>
|
||||
<version>0.7</version>
|
||||
<version>0.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>pre-site</phase>
|
||||
@ -620,6 +605,23 @@
|
||||
<artifactId>javacc</artifactId>
|
||||
<version>${javacc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.9.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<reporting>
|
||||
@ -638,9 +640,7 @@
|
||||
<reportSet>
|
||||
<reports>
|
||||
<report>javadoc</report>
|
||||
<!-- FIXME: The following report fails
|
||||
<report>test-javadoc</report>
|
||||
-->
|
||||
</reports>
|
||||
</reportSet>
|
||||
</reportSets>
|
||||
|
@ -18,9 +18,13 @@
|
||||
<useDefaultExcludes>true</useDefaultExcludes>
|
||||
<excludes>
|
||||
<exclude>${project.build.directory}/**</exclude>
|
||||
<exclude>bin/**</exclude>
|
||||
<exclude>.settings/**</exclude>
|
||||
<exclude>.project</exclude>
|
||||
<exclude>.classpath</exclude>
|
||||
<exclude>.checkstyle</exclude>
|
||||
<exclude>.pmd</exclude>
|
||||
<exclude>.ruleset</exclude>
|
||||
</excludes>
|
||||
<directoryMode>0755</directoryMode>
|
||||
<fileMode>0644</fileMode>
|
||||
|
@ -335,8 +335,6 @@ public class PMD {
|
||||
return languages;
|
||||
}
|
||||
|
||||
private static final int ERROR_STATUS = 1;
|
||||
|
||||
/**
|
||||
* Entry to invoke PMD as command line tool
|
||||
*
|
||||
@ -367,7 +365,7 @@ public class PMD {
|
||||
} catch (Exception e) {
|
||||
PMDCommandLineInterface.buildUsageText();
|
||||
System.out.println(e.getMessage());
|
||||
status = ERROR_STATUS;
|
||||
status = PMDCommandLineInterface.ERROR_STATUS;
|
||||
} finally {
|
||||
logHandlerManager.close();
|
||||
LOG.setLevel(oldLogLevel);
|
||||
|
@ -22,15 +22,18 @@ import net.sourceforge.pmd.util.StringUtil;
|
||||
|
||||
public class Report {
|
||||
|
||||
public static Report createReport(RuleContext ctx, String fileName) {
|
||||
|
||||
Report report = new Report();
|
||||
ctx.setReport(report);
|
||||
ctx.setSourceCodeFilename(fileName);
|
||||
ctx.setSourceCodeFile(new File(fileName));
|
||||
return report;
|
||||
}
|
||||
|
||||
public static Report createReport(RuleContext ctx, String fileName) {
|
||||
Report report = new Report();
|
||||
|
||||
// overtake the listener
|
||||
report.addSynchronizedListeners(ctx.getReport().getSynchronizedListeners());
|
||||
|
||||
ctx.setReport(report);
|
||||
ctx.setSourceCodeFilename(fileName);
|
||||
ctx.setSourceCodeFile(new File(fileName));
|
||||
return report;
|
||||
}
|
||||
|
||||
public static class ReadableDuration {
|
||||
private final long duration;
|
||||
|
||||
@ -112,7 +115,7 @@ public class Report {
|
||||
// Note that this and the above data structure are both being maintained for a bit
|
||||
private final List<RuleViolation> violations = new ArrayList<RuleViolation>();
|
||||
private final Set<Metric> metrics = new HashSet<Metric>();
|
||||
private final List<ReportListener> listeners = new ArrayList<ReportListener>();
|
||||
private final List<SynchronizedReportListener> listeners = new ArrayList<SynchronizedReportListener>();
|
||||
private List<ProcessingError> errors;
|
||||
private List<RuleConfigurationError> configErrors;
|
||||
private Map<Integer, String> linesToSuppress = new HashMap<Integer, String>();
|
||||
@ -164,7 +167,7 @@ public class Report {
|
||||
}
|
||||
|
||||
public void addListener(ReportListener listener) {
|
||||
listeners.add(listener);
|
||||
listeners.add(new SynchronizedReportListener(listener));
|
||||
}
|
||||
|
||||
public List<SuppressedViolation> getSuppressedRuleViolations() {
|
||||
@ -266,13 +269,13 @@ public class Report {
|
||||
}
|
||||
|
||||
public Iterator<ProcessingError> errors() {
|
||||
return errors == null ? EmptyIterator.instance : errors.iterator();
|
||||
return errors == null ? EmptyIterator.<ProcessingError> instance() : errors.iterator();
|
||||
}
|
||||
|
||||
public Iterator<RuleConfigurationError> configErrors() {
|
||||
return configErrors == null ? EmptyIterator.instance : errors.iterator();
|
||||
return configErrors == null ? EmptyIterator.<RuleConfigurationError> instance() : configErrors.iterator();
|
||||
}
|
||||
|
||||
|
||||
public int treeSize() {
|
||||
return violationTree.size();
|
||||
}
|
||||
@ -292,4 +295,12 @@ public class Report {
|
||||
public long getElapsedTimeInMillis() {
|
||||
return end - start;
|
||||
}
|
||||
|
||||
public List<SynchronizedReportListener> getSynchronizedListeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public void addSynchronizedListeners(List<SynchronizedReportListener> synchronizedListeners) {
|
||||
listeners.addAll(synchronizedListeners);
|
||||
}
|
||||
}
|
||||
|
@ -42,10 +42,11 @@ public class RuleContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor which shares attributes with the given RuleContext.
|
||||
* Constructor which shares attributes and report listeners with the given RuleContext.
|
||||
*/
|
||||
public RuleContext(RuleContext ruleContext) {
|
||||
this.attributes = ruleContext.attributes;
|
||||
this.report.addSynchronizedListeners(ruleContext.getReport().getSynchronizedListeners());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,9 @@ package net.sourceforge.pmd;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
@ -71,7 +73,7 @@ public class RuleSet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new rule to this ruleset
|
||||
* Add a new rule to this ruleset. Note that this method does not check for duplicates.
|
||||
*
|
||||
* @param rule the rule to be added
|
||||
*/
|
||||
@ -82,6 +84,53 @@ public class RuleSet {
|
||||
rules.add(rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rule. If a rule with the same name and language already existed before in the ruleset,
|
||||
* then the new rule will replace it. This makes sure that the rule configured is overridden.
|
||||
* @param rule
|
||||
* @return <code>true</code> if the new rule replaced an existing one, otherwise <code>false</code>.
|
||||
*/
|
||||
public boolean addRuleReplaceIfExists(Rule rule) {
|
||||
if (rule == null) {
|
||||
throw new IllegalArgumentException("Missing rule");
|
||||
}
|
||||
|
||||
boolean replaced = false;
|
||||
for (Iterator<Rule> it = rules.iterator(); it.hasNext(); ) {
|
||||
Rule r = it.next();
|
||||
if (r.getName().equals(rule.getName()) && r.getLanguage() == rule.getLanguage()) {
|
||||
it.remove();
|
||||
replaced = true;
|
||||
}
|
||||
}
|
||||
addRule(rule);
|
||||
return replaced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only adds a rule to the ruleset if no rule with the same name for the same language was added
|
||||
* before, so that the existent rule configuration won't be overridden.
|
||||
* @param rule
|
||||
* @return <code>true</code> if the rule was added, <code>false</code> otherwise
|
||||
*/
|
||||
public boolean addRuleIfNotExists(Rule rule) {
|
||||
if (rule == null) {
|
||||
throw new IllegalArgumentException("Missing rule");
|
||||
}
|
||||
|
||||
boolean exists = false;
|
||||
for (Rule r : rules) {
|
||||
if (r.getName().equals(rule.getName()) && r.getLanguage() == rule.getLanguage()) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
addRule(rule);
|
||||
}
|
||||
return !exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new rule by reference to this ruleset.
|
||||
*
|
||||
@ -144,8 +193,7 @@ public class RuleSet {
|
||||
*/
|
||||
public Rule getRuleByName(String ruleName) {
|
||||
|
||||
for (Iterator<Rule> i = rules.iterator(); i.hasNext();) {
|
||||
Rule r = i.next();
|
||||
for (Rule r : rules) {
|
||||
if (r.getName().equals(ruleName)) {
|
||||
return r;
|
||||
}
|
||||
@ -162,6 +210,18 @@ public class RuleSet {
|
||||
rules.addAll(rules.size(), ruleSet.getRules());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all rules by reference from one RuleSet to this RuleSet. The rules
|
||||
* can be added as individual references, or collectively as an all rule
|
||||
* reference.
|
||||
*
|
||||
* @param ruleSet the RuleSet to add
|
||||
* @param allRules
|
||||
*/
|
||||
public void addRuleSetByReference(RuleSet ruleSet, boolean allRules) {
|
||||
addRuleSetByReference(ruleSet, allRules, (String[])null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all rules by reference from one RuleSet to this RuleSet. The rules
|
||||
* can be added as individual references, or collectively as an all rule
|
||||
@ -169,13 +229,17 @@ public class RuleSet {
|
||||
*
|
||||
* @param ruleSet the RuleSet to add
|
||||
* @param allRules
|
||||
* @param excludes names of the rules that should be excluded.
|
||||
*/
|
||||
public void addRuleSetByReference(RuleSet ruleSet, boolean allRules) {
|
||||
public void addRuleSetByReference(RuleSet ruleSet, boolean allRules, String ... excludes) {
|
||||
if (StringUtil.isEmpty(ruleSet.getFileName())) {
|
||||
throw new RuntimeException("Adding a rule by reference is not allowed with an empty rule set file name.");
|
||||
}
|
||||
RuleSetReference ruleSetReference = new RuleSetReference(ruleSet.getFileName());
|
||||
ruleSetReference.setAllRules(allRules);
|
||||
if (excludes != null) {
|
||||
ruleSetReference.setExcludes(new HashSet<String>(Arrays.asList(excludes)));
|
||||
}
|
||||
for (Rule rule : ruleSet.getRules()) {
|
||||
RuleReference ruleReference = new RuleReference(rule, ruleSetReference);
|
||||
rules.add(ruleReference);
|
||||
|
@ -280,11 +280,15 @@ public class RuleSetFactory {
|
||||
RuleSetReference ruleSetReference = new RuleSetReference();
|
||||
ruleSetReference.setAllRules(true);
|
||||
ruleSetReference.setRuleSetFileName(ref);
|
||||
NodeList excludeNodes = ruleElement.getChildNodes();
|
||||
for (int i = 0; i < excludeNodes.getLength(); i++) {
|
||||
if (isElementNode(excludeNodes.item(i),"exclude")) {
|
||||
Element excludeElement = (Element) excludeNodes.item(i);
|
||||
String priority = null;
|
||||
NodeList childNodes = ruleElement.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
Node child = childNodes.item(i);
|
||||
if (isElementNode(child,"exclude")) {
|
||||
Element excludeElement = (Element) child;
|
||||
ruleSetReference.addExclude(excludeElement.getAttribute("name"));
|
||||
} else if (isElementNode(child, "priority")) {
|
||||
priority = parseTextNode(child).trim();
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,7 +301,12 @@ public class RuleSetFactory {
|
||||
RuleReference ruleReference = new RuleReference();
|
||||
ruleReference.setRuleSetReference(ruleSetReference);
|
||||
ruleReference.setRule(rule);
|
||||
ruleSet.addRule(ruleReference);
|
||||
ruleSet.addRuleIfNotExists(ruleReference);
|
||||
|
||||
// override the priority
|
||||
if (priority != null) {
|
||||
ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(priority)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -508,7 +517,7 @@ public class RuleSetFactory {
|
||||
|
||||
if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
|
||||
|| referencedRule.getPriority().compareTo(minimumPriority) <= 0) {
|
||||
ruleSet.addRule(ruleReference);
|
||||
ruleSet.addRuleReplaceIfExists(ruleReference);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,9 @@ public class RuleSetWriter {
|
||||
}
|
||||
|
||||
private Element createExcludeElement(String exclude) {
|
||||
return createTextElement("exclude", exclude);
|
||||
Element element = document.createElementNS(RULESET_NS_URI, "exclude");
|
||||
element.setAttribute("name", exclude);
|
||||
return element;
|
||||
}
|
||||
|
||||
private Element createExampleElement(String example) {
|
||||
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd;
|
||||
|
||||
import net.sourceforge.pmd.stat.Metric;
|
||||
|
||||
/**
|
||||
* Wraps a report listener in order to synchronize calls to it.
|
||||
*/
|
||||
public final class SynchronizedReportListener implements ReportListener {
|
||||
|
||||
private final ReportListener wrapped;
|
||||
|
||||
public SynchronizedReportListener(ReportListener listener) {
|
||||
this.wrapped = listener;
|
||||
}
|
||||
|
||||
public synchronized void ruleViolationAdded(RuleViolation ruleViolation) {
|
||||
wrapped.ruleViolationAdded(ruleViolation);
|
||||
}
|
||||
|
||||
public synchronized void metricAdded(Metric metric) {
|
||||
wrapped.metricAdded(metric);
|
||||
}
|
||||
|
||||
}
|
@ -26,7 +26,6 @@ import net.sourceforge.pmd.RuleSet;
|
||||
import net.sourceforge.pmd.RuleSetFactory;
|
||||
import net.sourceforge.pmd.RuleSetNotFoundException;
|
||||
import net.sourceforge.pmd.RuleSets;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.renderers.AbstractRenderer;
|
||||
import net.sourceforge.pmd.renderers.Renderer;
|
||||
@ -110,24 +109,10 @@ public class PMDTask extends Task {
|
||||
formatters.add(f);
|
||||
}
|
||||
|
||||
public void addConfiguredVersion(Version version) {
|
||||
LanguageVersion languageVersion = LanguageVersion.findByTerseName(version.getTerseName());
|
||||
public void addConfiguredSourceLanguage(SourceLanguage version) {
|
||||
LanguageVersion languageVersion = LanguageVersion.findVersionsForLanguageTerseName(version.getName(), version.getVersion());
|
||||
if (languageVersion == null) {
|
||||
StringBuilder buf = new StringBuilder("The <version> element, if used, must be one of ");
|
||||
boolean first = true;
|
||||
for (Language language : Language.values()) {
|
||||
if (language.getVersions().size() > 2) {
|
||||
for (LanguageVersion v : language.getVersions()) {
|
||||
if (!first) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append('\'').append(v.getTerseName()).append('\'');
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.append('.');
|
||||
throw new BuildException(buf.toString());
|
||||
throw new BuildException("The following language is not supported:" + version + ".");
|
||||
}
|
||||
configuration.setDefaultLanguageVersion(languageVersion);
|
||||
}
|
||||
@ -304,7 +289,12 @@ public class PMDTask extends Task {
|
||||
log("Using the normal ClassLoader", Project.MSG_VERBOSE);
|
||||
} else {
|
||||
log("Using the AntClassLoader", Project.MSG_VERBOSE);
|
||||
configuration.setClassLoader(new AntClassLoader(getProject(), classpath));
|
||||
// must be true, otherwise you'll get ClassCastExceptions as classes are loaded twice
|
||||
// and exist in multiple class loaders
|
||||
boolean parentFirst = true;
|
||||
configuration.setClassLoader(
|
||||
new AntClassLoader(Thread.currentThread().getContextClassLoader(), getProject(),
|
||||
classpath, parentFirst));
|
||||
}
|
||||
try {
|
||||
/*
|
||||
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd.ant;
|
||||
|
||||
/**
|
||||
* Stores LanguageVersion terse name value.
|
||||
*/
|
||||
public class SourceLanguage {
|
||||
|
||||
private String name;
|
||||
private String version;
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "<language name=\"" + this.name + "\" version=\"" + this.version + "\" />";
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd.ant;
|
||||
|
||||
/**
|
||||
* Stores LanguageVersion terse name value.
|
||||
*/
|
||||
public class Version {
|
||||
private String terseName;
|
||||
|
||||
public void addText(String text) {
|
||||
this.terseName = text;
|
||||
}
|
||||
|
||||
public String getTerseName() {
|
||||
return terseName;
|
||||
}
|
||||
}
|
@ -27,17 +27,24 @@ public class PMDCommandLineInterface {
|
||||
public static final String NO_EXIT_AFTER_RUN = "net.sourceforge.pmd.cli.noExit";
|
||||
public static final String STATUS_CODE_PROPERTY = "net.sourceforge.pmd.cli.status";
|
||||
|
||||
public static final int ERROR_STATUS = 1;
|
||||
|
||||
public static PMDParameters extractParameters(PMDParameters arguments, String[] args, String progName) {
|
||||
jcommander = new JCommander(arguments);
|
||||
jcommander.setProgramName(progName);
|
||||
|
||||
try {
|
||||
jcommander = new JCommander(arguments, args);
|
||||
jcommander.setProgramName(progName);
|
||||
jcommander.parse(args);
|
||||
if (arguments.isHelp()) {
|
||||
jcommander.usage();
|
||||
System.exit(0);
|
||||
System.out.println(buildUsageText());
|
||||
setStatusCodeOrExit(0);
|
||||
}
|
||||
} catch (ParameterException e) {
|
||||
jcommander.usage();
|
||||
System.out.println(buildUsageText());
|
||||
System.out.println(e.getMessage());
|
||||
setStatusCodeOrExit(ERROR_STATUS);
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
@ -61,7 +68,7 @@ public class PMDCommandLineInterface {
|
||||
+ "3) A ruleset filename or a comma-delimited string of ruleset filenames" + PMD.EOL
|
||||
+ PMD.EOL
|
||||
+ "For example: " + PMD.EOL
|
||||
+ "c:\\> " + launchCmd + "-d c:\\my\\source\\code -f html -R java-unusedcode" + PMD.EOL
|
||||
+ "c:\\> " + launchCmd + " -d c:\\my\\source\\code -f html -R java-unusedcode" + PMD.EOL
|
||||
+ PMD.EOL;
|
||||
|
||||
fullText += supportedVersions() + PMD.EOL;
|
||||
@ -91,16 +98,16 @@ public class PMDCommandLineInterface {
|
||||
+ WINDOWS_PROMPT + launchCmd + " -dir" + WINDOWS_PATH_TO_CODE + "-format text -R java-unusedcode,java-imports -version 1.5 -language java -debug" + PMD.EOL
|
||||
+ WINDOWS_PROMPT + launchCmd + " -dir" + WINDOWS_PATH_TO_CODE + "-f xml -rulesets java-basic,java-design -encoding UTF-8" + PMD.EOL
|
||||
+ WINDOWS_PROMPT + launchCmd + " -d" + WINDOWS_PATH_TO_CODE + "-rulesets java-typeresolution -auxclasspath commons-collections.jar;derby.jar" + PMD.EOL
|
||||
+ WINDOWS_PROMPT + launchCmd + " -d" + WINDOWS_PATH_TO_CODE + "-f html -R java-typeresolution -auxclasspath -d file:///C:/my/classpathfile" + PMD.EOL
|
||||
+ WINDOWS_PROMPT + launchCmd + " -d" + WINDOWS_PATH_TO_CODE + "-f html -R java-typeresolution -auxclasspath file:///C:/my/classpathfile" + PMD.EOL
|
||||
+ PMD.EOL;
|
||||
}
|
||||
|
||||
private static String getUnixExample(String launchCmd) {
|
||||
final String UNIX_PROMPT = "$ ";
|
||||
return "For example on *nix: " + PMD.EOL
|
||||
+ UNIX_PROMPT + launchCmd + " -dir /home/workspace/src/main/java/code -f nicehtml -rulesets java-basic,java-design" + PMD.EOL
|
||||
+ UNIX_PROMPT + launchCmd + " -d ./src/main/java/code -f nicehtml -r java-basic,java-design -xslt my-own.xsl" + PMD.EOL
|
||||
+ UNIX_PROMPT + launchCmd + " -d ./src/main/java/code -f nicehtml -r java-typeresolution -auxclasspath commons-collections.jar:derby.jar"
|
||||
+ UNIX_PROMPT + launchCmd + " -dir /home/workspace/src/main/java/code -f html -rulesets java-basic,java-design" + PMD.EOL
|
||||
+ UNIX_PROMPT + launchCmd + " -d ./src/main/java/code -f xslt -R java-basic,java-design -property xsltFilename=my-own.xsl" + PMD.EOL
|
||||
+ UNIX_PROMPT + launchCmd + " -d ./src/main/java/code -f html -R java-typeresolution -auxclasspath commons-collections.jar:derby.jar"
|
||||
+ PMD.EOL;
|
||||
}
|
||||
|
||||
@ -148,12 +155,16 @@ public class PMDCommandLineInterface {
|
||||
}
|
||||
|
||||
public static void run(String[] args) {
|
||||
if ( isExitAfterRunSet() )
|
||||
System.exit(PMD.run(args));
|
||||
else
|
||||
setStatusCode(PMD.run(args));
|
||||
setStatusCodeOrExit(PMD.run(args));
|
||||
}
|
||||
|
||||
public static void setStatusCodeOrExit(int status) {
|
||||
if ( isExitAfterRunSet() )
|
||||
System.exit(status);
|
||||
else
|
||||
setStatusCode(status);
|
||||
}
|
||||
|
||||
private static boolean isExitAfterRunSet() {
|
||||
return (System.getenv(NO_EXIT_AFTER_RUN) == null ? false : true);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public class PMDParameters {
|
||||
@Parameter(names = {"-debug", "-verbose", "-D", "-V"}, description = "Debug mode")
|
||||
private boolean debug = false;
|
||||
|
||||
@Parameter(names = {"-help","-h","-H"}, description = "Display help on usage")
|
||||
@Parameter(names = {"-help","-h","-H"}, description = "Display help on usage", help = true)
|
||||
private boolean help = false;
|
||||
|
||||
@Parameter(names= {"-encoding", "-e"} , description = "specifies the character set encoding of the source code files PMD is reading (i.e., UTF-8)")
|
||||
@ -67,7 +67,7 @@ public class PMDParameters {
|
||||
@Parameter(names = {"-language", "-l"}, description = "specify version of a language PMD should use")
|
||||
private String language = Language.getDefaultLanguage().getTerseName();
|
||||
|
||||
@Parameter(names = "-auxclasspath", description = "specifies the classpath for libraries used by the source code (used by type resolution)\n(alternatively, a 'file://' URL to a text file containing path elements on consecutive lines")
|
||||
@Parameter(names = "-auxclasspath", description = "specifies the classpath for libraries used by the source code. This is used by the type resolution. Alternatively, a 'file://' URL to a text file containing path elements on consecutive lines can be specified.")
|
||||
private String auxclasspath;
|
||||
|
||||
class PropertyConverter implements IStringConverter<Properties> {
|
||||
@ -118,7 +118,11 @@ public class PMDParameters {
|
||||
configuration.setSuppressMarker(params.getSuppressmarker());
|
||||
configuration.setThreads(params.getThreads());
|
||||
for ( LanguageVersion language : LanguageVersion.findVersionsForLanguageTerseName( params.getLanguage() ) ) {
|
||||
configuration.getLanguageVersionDiscoverer().setDefaultLanguageVersion(language.getLanguage().getVersion(params.getVersion()));
|
||||
LanguageVersion languageVersion = language.getLanguage().getVersion(params.getVersion());
|
||||
if (languageVersion == null) {
|
||||
languageVersion = language.getLanguage().getDefaultVersion();
|
||||
}
|
||||
configuration.getLanguageVersionDiscoverer().setDefaultLanguageVersion(languageVersion);
|
||||
}
|
||||
try {
|
||||
configuration.prependClasspath(params.getAuxclasspath());
|
||||
|
@ -16,6 +16,8 @@ import java.util.TreeMap;
|
||||
|
||||
import net.sourceforge.pmd.util.FileFinder;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
public class CPD {
|
||||
|
||||
private static final int MISSING_FILES = 1;
|
||||
@ -31,6 +33,8 @@ public class CPD {
|
||||
|
||||
public CPD(CPDConfiguration theConfiguration) {
|
||||
configuration = theConfiguration;
|
||||
// before we start any tokenizing (add(File...)), we need to reset the static TokenEntry status
|
||||
TokenEntry.clearImages();
|
||||
}
|
||||
|
||||
public void setCpdListener(CPDListener cpdListener) {
|
||||
@ -38,7 +42,6 @@ public class CPD {
|
||||
}
|
||||
|
||||
public void go() {
|
||||
TokenEntry.clearImages();
|
||||
matchAlgorithm = new MatchAlgorithm(
|
||||
source, tokens,
|
||||
configuration.minimumTileSize(),
|
||||
@ -92,11 +95,16 @@ public class CPD {
|
||||
current.add(signature);
|
||||
}
|
||||
|
||||
if (!file.getCanonicalPath().equals(new File(file.getAbsolutePath()).getCanonicalPath())) {
|
||||
if (!FilenameUtils.equalsNormalizedOnSystem(file.getAbsoluteFile().getCanonicalPath(), file.getAbsolutePath())) {
|
||||
System.err.println("Skipping " + file + " since it appears to be a symlink");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.exists()) {
|
||||
System.err.println("Skipping " + file + " since it doesn't exist (broken symlink?)");
|
||||
return;
|
||||
}
|
||||
|
||||
listener.addedFile(fileCount, file);
|
||||
SourceCode sourceCode = configuration.sourceCodeFor(file);
|
||||
configuration.tokenizer().tokenize(sourceCode, tokens);
|
||||
|
@ -188,16 +188,9 @@ public class CPDTask extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME Can't we do something cleaner and
|
||||
* more dynamic ? Maybe externalise to a properties files that will
|
||||
* be generated when building pmd ? This will not have to add manually
|
||||
* new language here ?
|
||||
*/
|
||||
public static class LanguageAttribute extends EnumeratedAttribute {
|
||||
private static final String[] LANGUAGES = new String[]{"java","jsp","cpp", "c","php", "ruby", "fortran", "cs"};
|
||||
public String[] getValues() {
|
||||
return LANGUAGES;
|
||||
return LanguageFactory.supportedLanguages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,19 +106,22 @@ public class GUI implements CPDListener {
|
||||
{"Fortran", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("fortran"); }
|
||||
public String[] extensions() { return new String[] {".rb" }; }; } },
|
||||
{"by extension...", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.BY_EXTENSION, p); }
|
||||
public String[] extensions() { return new String[] {"" }; }; } },
|
||||
{"PHP", new LanguageConfig() {
|
||||
{"PHP", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("php"); }
|
||||
public String[] extensions() { return new String[] {".php" }; }; } },
|
||||
{"C#", new LanguageConfig() {
|
||||
{"C#", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("cs"); }
|
||||
public String[] extensions() { return new String[] {".cs" }; }; } },
|
||||
{"Ecmascript", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("js"); }
|
||||
public String[] extensions() { return new String[] {".js" }; }; } },
|
||||
{"by extension...", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.BY_EXTENSION, p); }
|
||||
public String[] extensions() { return new String[] {"" }; }; } },
|
||||
};
|
||||
|
||||
private static final int DEFAULT_CPD_MINIMUM_LENGTH = 75;
|
||||
private static final Map LANGUAGE_CONFIGS_BY_LABEL = new HashMap(LANGUAGE_SETS.length);
|
||||
private static final Map<String, LanguageConfig> LANGUAGE_CONFIGS_BY_LABEL = new HashMap<String, LanguageConfig>(LANGUAGE_SETS.length);
|
||||
private static final KeyStroke COPY_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_C,ActionEvent.CTRL_MASK,false);
|
||||
private static final KeyStroke DELETE_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
|
||||
|
||||
@ -148,12 +151,12 @@ public class GUI implements CPDListener {
|
||||
|
||||
static {
|
||||
for (int i=0; i<LANGUAGE_SETS.length; i++) {
|
||||
LANGUAGE_CONFIGS_BY_LABEL.put(LANGUAGE_SETS[i][0], LANGUAGE_SETS[i][1]);
|
||||
LANGUAGE_CONFIGS_BY_LABEL.put((String)LANGUAGE_SETS[i][0], (LanguageConfig)LANGUAGE_SETS[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
private static LanguageConfig languageConfigFor(String label) {
|
||||
return (LanguageConfig)LANGUAGE_CONFIGS_BY_LABEL.get(label);
|
||||
return LANGUAGE_CONFIGS_BY_LABEL.get(label);
|
||||
}
|
||||
|
||||
private static class CancelListener implements ActionListener {
|
||||
|
@ -4,7 +4,7 @@
|
||||
package net.sourceforge.pmd.cpd;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.*;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.LanguageVersionHandler;
|
||||
@ -23,8 +23,6 @@ public class JavaTokenizer implements Tokenizer {
|
||||
private boolean ignoreAnnotations;
|
||||
private boolean ignoreLiterals;
|
||||
private boolean ignoreIdentifiers;
|
||||
List<Discarder> discarders = new ArrayList<Discarder>();
|
||||
|
||||
|
||||
public void setProperties(Properties properties) {
|
||||
ignoreAnnotations = Boolean.parseBoolean(properties.getProperty(IGNORE_ANNOTATIONS, "false"));
|
||||
@ -42,22 +40,17 @@ public class JavaTokenizer implements Tokenizer {
|
||||
fileName, new StringReader(stringBuilder.toString()));
|
||||
Token currentToken = (Token) tokenMgr.getNextToken();
|
||||
|
||||
initDiscarders();
|
||||
TokenDiscarder discarder = new TokenDiscarder(ignoreAnnotations);
|
||||
|
||||
while (currentToken.image.length() > 0) {
|
||||
for (Discarder discarder : discarders) {
|
||||
discarder.add(currentToken);
|
||||
}
|
||||
discarder.updateState(currentToken);
|
||||
|
||||
if (inDiscardingState()) {
|
||||
if (discarder.isDiscarding()) {
|
||||
currentToken = (Token) tokenMgr.getNextToken();
|
||||
continue;
|
||||
}
|
||||
|
||||
//skip semicolons
|
||||
if (currentToken.kind != JavaParserConstants.SEMICOLON) {
|
||||
processToken(tokenEntries, fileName, currentToken);
|
||||
}
|
||||
processToken(tokenEntries, fileName, currentToken);
|
||||
currentToken = (Token) tokenMgr.getNextToken();
|
||||
}
|
||||
tokenEntries.add(TokenEntry.getEOF());
|
||||
@ -68,7 +61,8 @@ public class JavaTokenizer implements Tokenizer {
|
||||
if (ignoreLiterals
|
||||
&& (currentToken.kind == JavaParserConstants.STRING_LITERAL
|
||||
|| currentToken.kind == JavaParserConstants.CHARACTER_LITERAL
|
||||
|| currentToken.kind == JavaParserConstants.DECIMAL_LITERAL || currentToken.kind == JavaParserConstants.FLOATING_POINT_LITERAL)) {
|
||||
|| currentToken.kind == JavaParserConstants.DECIMAL_LITERAL
|
||||
|| currentToken.kind == JavaParserConstants.FLOATING_POINT_LITERAL)) {
|
||||
image = String.valueOf(currentToken.kind);
|
||||
}
|
||||
if (ignoreIdentifiers && currentToken.kind == JavaParserConstants.IDENTIFIER) {
|
||||
@ -77,23 +71,6 @@ public class JavaTokenizer implements Tokenizer {
|
||||
tokenEntries.add(new TokenEntry(image, fileName, currentToken.beginLine));
|
||||
}
|
||||
|
||||
private void initDiscarders() {
|
||||
if (ignoreAnnotations)
|
||||
discarders.add(new AnnotationStateDiscarder());
|
||||
discarders.add(new SuppressCPDDiscarder());
|
||||
discarders.add(new KeyWordToSemiColonStateDiscarder(JavaParserConstants.IMPORT));
|
||||
discarders.add(new KeyWordToSemiColonStateDiscarder(JavaParserConstants.PACKAGE));
|
||||
}
|
||||
|
||||
private boolean inDiscardingState() {
|
||||
boolean discarding = false;
|
||||
for (Discarder discarder : discarders) {
|
||||
if (discarder.isDiscarding())
|
||||
discarding = true;
|
||||
}
|
||||
return discarding;
|
||||
}
|
||||
|
||||
public void setIgnoreLiterals(boolean ignore) {
|
||||
this.ignoreLiterals = ignore;
|
||||
}
|
||||
@ -106,83 +83,101 @@ public class JavaTokenizer implements Tokenizer {
|
||||
this.ignoreAnnotations = ignoreAnnotations;
|
||||
}
|
||||
|
||||
static public interface Discarder {
|
||||
public void add(Token token);
|
||||
/**
|
||||
* The {@link TokenDiscarder} consumes token by token and maintains state.
|
||||
* It can detect, whether the current token belongs to an annotation and whether
|
||||
* the current token should be discarded by CPD.
|
||||
* <p>
|
||||
* By default, it discards semicolons, package and import statements, and enables CPD suppression.
|
||||
* Optionally, all annotations can be ignored, too.
|
||||
* </p>
|
||||
*/
|
||||
private static class TokenDiscarder {
|
||||
private boolean isAnnotation = false;
|
||||
private boolean nextTokenEndsAnnotation = false;
|
||||
private int annotationStack = 0;
|
||||
|
||||
public boolean isDiscarding();
|
||||
}
|
||||
private boolean discardingSemicolon = false;
|
||||
private boolean discardingKeywords = false;
|
||||
private boolean discardingSuppressing = false;
|
||||
private boolean discardingAnnotations = false;
|
||||
private boolean ignoreAnnotations = false;
|
||||
|
||||
static public class AnnotationStateDiscarder implements Discarder {
|
||||
public TokenDiscarder(boolean ignoreAnnotations) {
|
||||
this.ignoreAnnotations = ignoreAnnotations;
|
||||
}
|
||||
|
||||
Stack<Token> tokenStack = new Stack<Token>();
|
||||
public void updateState(Token currentToken) {
|
||||
detectAnnotations(currentToken);
|
||||
|
||||
public void add(Token token) {
|
||||
if (isDiscarding() && tokenStack.size() == 2 && token.kind != JavaParserConstants.LPAREN) {
|
||||
tokenStack.clear();
|
||||
}
|
||||
|
||||
if (token.kind == JavaParserConstants.AT && !isDiscarding()) {
|
||||
tokenStack.push(token);
|
||||
return;
|
||||
}
|
||||
if (token.kind == JavaParserConstants.RPAREN && isDiscarding()) {
|
||||
Token popped = null;
|
||||
while ((popped = tokenStack.pop()).kind != JavaParserConstants.LPAREN) ;
|
||||
return;
|
||||
|
||||
} else {
|
||||
if (isDiscarding())
|
||||
tokenStack.push(token);
|
||||
skipSemicolon(currentToken);
|
||||
skipPackageAndImport(currentToken);
|
||||
skipCPDSuppression(currentToken);
|
||||
if (ignoreAnnotations) {
|
||||
skipAnnotations();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDiscarding() {
|
||||
return !tokenStack.isEmpty();
|
||||
public void skipPackageAndImport(Token currentToken) {
|
||||
if (currentToken.kind == JavaParserConstants.PACKAGE || currentToken.kind == JavaParserConstants.IMPORT) {
|
||||
discardingKeywords = true;
|
||||
} else if (discardingKeywords && currentToken.kind == JavaParserConstants.SEMICOLON) {
|
||||
discardingKeywords = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static public class KeyWordToSemiColonStateDiscarder implements Discarder {
|
||||
|
||||
private final int keyword;
|
||||
Stack<Token> tokenStack = new Stack<Token>();
|
||||
|
||||
public KeyWordToSemiColonStateDiscarder(int keyword) {
|
||||
this.keyword = keyword;
|
||||
public void skipSemicolon(Token currentToken) {
|
||||
if (currentToken.kind == JavaParserConstants.SEMICOLON) {
|
||||
discardingSemicolon = true;
|
||||
} else if (discardingSemicolon && currentToken.kind != JavaParserConstants.SEMICOLON) {
|
||||
discardingSemicolon = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Token token) {
|
||||
if (token.kind == keyword)
|
||||
tokenStack.add(token);
|
||||
if (token.kind == JavaParserConstants.SEMICOLON && isDiscarding())
|
||||
tokenStack.clear();
|
||||
}
|
||||
|
||||
public boolean isDiscarding() {
|
||||
return !tokenStack.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static public class SuppressCPDDiscarder implements Discarder {
|
||||
AnnotationStateDiscarder asm = new AnnotationStateDiscarder();
|
||||
Boolean discarding = false;
|
||||
|
||||
public void add(Token token) {
|
||||
asm.add(token);
|
||||
public void skipCPDSuppression(Token currentToken) {
|
||||
//if processing an annotation, look for a CPD-START or CPD-END
|
||||
if (asm.isDiscarding()) {
|
||||
if (CPD_START.equals(token.image))
|
||||
discarding = true;
|
||||
if (CPD_END.equals(token.image) && discarding)
|
||||
discarding = false;
|
||||
if (isAnnotation) {
|
||||
if (!discardingSuppressing && currentToken.kind == JavaParserConstants.STRING_LITERAL && CPD_START.equals(currentToken.image)) {
|
||||
discardingSuppressing = true;
|
||||
} else if (discardingSuppressing && currentToken.kind == JavaParserConstants.STRING_LITERAL && CPD_END.equals(currentToken.image)) {
|
||||
discardingSuppressing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void skipAnnotations() {
|
||||
if (!discardingAnnotations && isAnnotation) {
|
||||
discardingAnnotations = true;
|
||||
} else if (discardingAnnotations && !isAnnotation) {
|
||||
discardingAnnotations = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDiscarding() {
|
||||
return discarding;
|
||||
boolean result = discardingSemicolon || discardingKeywords || discardingAnnotations || discardingSuppressing;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void detectAnnotations(Token currentToken) {
|
||||
if (isAnnotation && nextTokenEndsAnnotation) {
|
||||
isAnnotation = false;
|
||||
nextTokenEndsAnnotation = false;
|
||||
}
|
||||
if (isAnnotation) {
|
||||
if (currentToken.kind == JavaParserConstants.LPAREN) {
|
||||
annotationStack++;
|
||||
} else if (currentToken.kind == JavaParserConstants.RPAREN) {
|
||||
annotationStack--;
|
||||
if (annotationStack == 0) {
|
||||
nextTokenEndsAnnotation = true;
|
||||
}
|
||||
} else if (annotationStack == 0 && currentToken.kind != JavaParserConstants.IDENTIFIER && currentToken.kind != JavaParserConstants.LPAREN) {
|
||||
isAnnotation = false;
|
||||
}
|
||||
}
|
||||
if (currentToken.kind == JavaParserConstants.AT) {
|
||||
isAnnotation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,9 +7,16 @@ import java.util.Properties;
|
||||
|
||||
public class LanguageFactory {
|
||||
|
||||
// TODO derive and provide this at runtime instead, used by outside IDEs
|
||||
public static String[] supportedLanguages = new String[]{"java", "jsp", "cpp", "c", "php", "ruby","fortran", "ecmascript","cs" };
|
||||
|
||||
/*
|
||||
* TODO derive and provide this at runtime instead, used by outside IDEs
|
||||
* FIXME Can't we do something cleaner and
|
||||
* more dynamic ? Maybe externalise to a properties files that will
|
||||
* be generated when building pmd ? This will not have to add manually
|
||||
* new language here ?
|
||||
*/
|
||||
public static String[] supportedLanguages =
|
||||
new String[]{"java", "jsp", "cpp", "c", "php", "ruby", "fortran", "ecmascript", "cs"};
|
||||
|
||||
private static final String SUFFIX = "Language";
|
||||
public static final String EXTENSION = "extension";
|
||||
public static final String BY_EXTENSION = "by_extension";
|
||||
|
@ -200,6 +200,23 @@ public enum LanguageVersion {
|
||||
return versionsAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility method to retrieve the appropriate enum, given the provided parameters
|
||||
*
|
||||
* @param languageTerseName The LanguageVersion terse name.
|
||||
* @param languageVersion The version of the language requested.
|
||||
* @return A list of versions associated with the terse name.
|
||||
*/
|
||||
public static LanguageVersion findVersionsForLanguageTerseName(String languageTerseName, String languageVersion) {
|
||||
List<LanguageVersion> versionsAvailable = findVersionsForLanguageTerseName(languageTerseName);
|
||||
for ( LanguageVersion version : versionsAvailable ) {
|
||||
if ( version.getVersion().equalsIgnoreCase(languageVersion) )
|
||||
return version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a comma-separated list of LanguageVersion terse names.
|
||||
* @param languageVersions The language versions.
|
||||
|
@ -119,7 +119,7 @@ public abstract class AbstractNode implements Node {
|
||||
if (children != null && children.length > 0) {
|
||||
return children[0].getBeginColumn();
|
||||
} else {
|
||||
throw new RuntimeException("Unable to determine begining line of Node.");
|
||||
throw new RuntimeException("Unable to determine beginning line of Node.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.ast.xpath;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -15,31 +16,32 @@ import net.sourceforge.pmd.lang.ast.Node;
|
||||
public class AttributeAxisIterator implements Iterator<Attribute> {
|
||||
|
||||
private static class MethodWrapper {
|
||||
public Method method;
|
||||
public String name;
|
||||
public Method method;
|
||||
public String name;
|
||||
|
||||
public MethodWrapper(Method m) {
|
||||
this.method = m;
|
||||
this.name = truncateMethodName(m.getName());
|
||||
}
|
||||
public MethodWrapper(Method m) {
|
||||
this.method = m;
|
||||
this.name = truncateMethodName(m.getName());
|
||||
}
|
||||
|
||||
private String truncateMethodName(String n) {
|
||||
// about 70% of the methods start with 'get', so this case goes first
|
||||
if (n.startsWith("get")) {
|
||||
return n.substring("get".length());
|
||||
}
|
||||
if (n.startsWith("is")) {
|
||||
return n.substring("is".length());
|
||||
}
|
||||
if (n.startsWith("has")) {
|
||||
return n.substring("has".length());
|
||||
}
|
||||
if (n.startsWith("uses")) {
|
||||
return n.substring("uses".length());
|
||||
}
|
||||
private String truncateMethodName(String n) {
|
||||
// about 70% of the methods start with 'get', so this case goes
|
||||
// first
|
||||
if (n.startsWith("get")) {
|
||||
return n.substring("get".length());
|
||||
}
|
||||
if (n.startsWith("is")) {
|
||||
return n.substring("is".length());
|
||||
}
|
||||
if (n.startsWith("has")) {
|
||||
return n.substring("has".length());
|
||||
}
|
||||
if (n.startsWith("uses")) {
|
||||
return n.substring("uses".length());
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
private Attribute currObj;
|
||||
@ -47,64 +49,65 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
|
||||
private int position;
|
||||
private Node node;
|
||||
|
||||
private static Map<Class<?>, MethodWrapper[]> methodCache = new HashMap<Class<?>, MethodWrapper[]>();
|
||||
private static Map<Class<?>, MethodWrapper[]> methodCache =
|
||||
Collections.synchronizedMap(new HashMap<Class<?>, MethodWrapper[]>());
|
||||
|
||||
public AttributeAxisIterator(Node contextNode) {
|
||||
this.node = contextNode;
|
||||
if (!methodCache.containsKey(contextNode.getClass())) {
|
||||
Method[] preFilter = contextNode.getClass().getMethods();
|
||||
List<MethodWrapper> postFilter = new ArrayList<MethodWrapper>();
|
||||
for (Method element : preFilter) {
|
||||
if (isAttributeAccessor(element)) {
|
||||
postFilter.add(new MethodWrapper(element));
|
||||
}
|
||||
}
|
||||
methodCache.put(contextNode.getClass(), postFilter.toArray(new MethodWrapper[postFilter.size()]));
|
||||
}
|
||||
this.methodWrappers = methodCache.get(contextNode.getClass());
|
||||
this.node = contextNode;
|
||||
if (!methodCache.containsKey(contextNode.getClass())) {
|
||||
Method[] preFilter = contextNode.getClass().getMethods();
|
||||
List<MethodWrapper> postFilter = new ArrayList<MethodWrapper>();
|
||||
for (Method element : preFilter) {
|
||||
if (isAttributeAccessor(element)) {
|
||||
postFilter.add(new MethodWrapper(element));
|
||||
}
|
||||
}
|
||||
methodCache.put(contextNode.getClass(), postFilter.toArray(new MethodWrapper[postFilter.size()]));
|
||||
}
|
||||
this.methodWrappers = methodCache.get(contextNode.getClass());
|
||||
|
||||
this.position = 0;
|
||||
this.currObj = getNextAttribute();
|
||||
this.position = 0;
|
||||
this.currObj = getNextAttribute();
|
||||
}
|
||||
|
||||
public Attribute next() {
|
||||
if (currObj == null) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
Attribute ret = currObj;
|
||||
currObj = getNextAttribute();
|
||||
return ret;
|
||||
if (currObj == null) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
Attribute ret = currObj;
|
||||
currObj = getNextAttribute();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return currObj != null;
|
||||
return currObj != null;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private Attribute getNextAttribute() {
|
||||
if (position == methodWrappers.length) {
|
||||
return null;
|
||||
}
|
||||
MethodWrapper m = methodWrappers[position++];
|
||||
return new Attribute(node, m.name, m.method);
|
||||
if (methodWrappers == null || position == methodWrappers.length) {
|
||||
return null;
|
||||
}
|
||||
MethodWrapper m = methodWrappers[position++];
|
||||
return new Attribute(node, m.name, m.method);
|
||||
}
|
||||
|
||||
protected boolean isAttributeAccessor(Method method) {
|
||||
|
||||
String methodName = method.getName();
|
||||
String methodName = method.getName();
|
||||
|
||||
return (Integer.TYPE == method.getReturnType() || Boolean.TYPE == method.getReturnType()
|
||||
|| Double.TYPE == method.getReturnType() || String.class == method.getReturnType())
|
||||
&& method.getParameterTypes().length == 0
|
||||
&& Void.TYPE != method.getReturnType()
|
||||
&& !methodName.startsWith("jjt")
|
||||
&& !methodName.equals("toString")
|
||||
&& !methodName.equals("getScope")
|
||||
&& !methodName.equals("getClass")
|
||||
&& !methodName.equals("getTypeNameNode")
|
||||
&& !methodName.equals("getImportedNameNode") && !methodName.equals("hashCode");
|
||||
return (Integer.TYPE == method.getReturnType() || Boolean.TYPE == method.getReturnType()
|
||||
|| Double.TYPE == method.getReturnType() || String.class == method.getReturnType())
|
||||
&& method.getParameterTypes().length == 0
|
||||
&& Void.TYPE != method.getReturnType()
|
||||
&& !methodName.startsWith("jjt")
|
||||
&& !methodName.equals("toString")
|
||||
&& !methodName.equals("getScope")
|
||||
&& !methodName.equals("getClass")
|
||||
&& !methodName.equals("getTypeNameNode")
|
||||
&& !methodName.equals("getImportedNameNode") && !methodName.equals("hashCode");
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd.lang.dfa.report;
|
||||
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
@ -23,6 +26,9 @@ public class ViolationNode extends AbstractReportNode {
|
||||
|
||||
return rv.getFilename().equals(getRuleViolation().getFilename()) &&
|
||||
rv.getBeginLine() == getRuleViolation().getBeginLine() &&
|
||||
rv.getBeginColumn() == getRuleViolation().getBeginColumn() &&
|
||||
rv.getEndLine() == getRuleViolation().getEndLine() &&
|
||||
rv.getEndColumn()== getRuleViolation().getEndColumn() &&
|
||||
rv.getVariableName().equals(getRuleViolation().getVariableName());
|
||||
}
|
||||
|
||||
|
@ -7,18 +7,27 @@ import net.sourceforge.pmd.lang.ast.AbstractNode;
|
||||
|
||||
import org.mozilla.javascript.ast.AstNode;
|
||||
|
||||
public abstract class AbstractEcmascriptNode<T extends AstNode> extends AbstractNode implements EcmascriptNode {
|
||||
public abstract class AbstractEcmascriptNode<T extends AstNode> extends AbstractNode implements EcmascriptNode<T> {
|
||||
|
||||
protected final T node;
|
||||
|
||||
public AbstractEcmascriptNode(T node) {
|
||||
super(node.getType());
|
||||
this.node = node;
|
||||
this.beginLine = node.getLineno() + 1;
|
||||
this.beginLine = node.getLineno() + 1;
|
||||
// TODO Implement positions, or figure out how to do begin/end lines/column
|
||||
//this.beginPosition = node.getAbsolutePosition();
|
||||
//this.endPosition = this.beginPosition + node.getLength();
|
||||
}
|
||||
|
||||
/* package private */
|
||||
void calculateLineNumbers(SourceCodePositioner positioner) {
|
||||
int startOffset = node.getAbsolutePosition();
|
||||
int endOffset = startOffset + node.getLength();
|
||||
|
||||
this.beginLine = positioner.lineNumberFromOffset(startOffset);
|
||||
this.beginColumn = positioner.columnFromOffset(startOffset);
|
||||
this.endLine = positioner.lineNumberFromOffset(endOffset);
|
||||
this.endColumn = positioner.columnFromOffset(endOffset) - 1; // end column is inclusive
|
||||
if (this.endColumn < 0) {
|
||||
this.endColumn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,7 +43,9 @@ public abstract class AbstractEcmascriptNode<T extends AstNode> extends Abstract
|
||||
public Object childrenAccept(EcmascriptParserVisitor visitor, Object data) {
|
||||
if (children != null) {
|
||||
for (int i = 0; i < children.length; ++i) {
|
||||
((EcmascriptNode) children[i]).jjtAccept(visitor, data);
|
||||
@SuppressWarnings("unchecked") // we know that the children here are all EcmascriptNodes
|
||||
EcmascriptNode<T> ecmascriptNode = (EcmascriptNode<T>) children[i];
|
||||
ecmascriptNode.jjtAccept(visitor, data);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
@ -52,11 +63,6 @@ public abstract class AbstractEcmascriptNode<T extends AstNode> extends Abstract
|
||||
return node.hasSideEffects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBeginColumn() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return node.shortName();
|
||||
|
@ -11,6 +11,7 @@ import java.util.List;
|
||||
import net.sourceforge.pmd.lang.ast.ParseException;
|
||||
import net.sourceforge.pmd.lang.ecmascript.EcmascriptParserOptions;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.mozilla.javascript.CompilerEnvirons;
|
||||
import org.mozilla.javascript.Parser;
|
||||
import org.mozilla.javascript.ast.AstRoot;
|
||||
@ -24,7 +25,7 @@ public class EcmascriptParser {
|
||||
this.parserOptions = parserOptions;
|
||||
}
|
||||
|
||||
protected AstRoot parseEcmascript(final Reader reader, final List<ParseProblem> parseProblems) throws ParseException {
|
||||
protected AstRoot parseEcmascript(final String sourceCode, final List<ParseProblem> parseProblems) throws ParseException {
|
||||
final CompilerEnvirons compilerEnvirons = new CompilerEnvirons();
|
||||
compilerEnvirons.setRecordingComments(parserOptions.isRecordingComments());
|
||||
compilerEnvirons.setRecordingLocalJsDocComments(parserOptions.isRecordingLocalJsDocComments());
|
||||
@ -35,23 +36,23 @@ public class EcmascriptParser {
|
||||
// TODO We should do something with Rhino errors...
|
||||
final ErrorCollector errorCollector = new ErrorCollector();
|
||||
final Parser parser = new Parser(compilerEnvirons, errorCollector);
|
||||
// TODO Fix hardcode
|
||||
final String sourceURI = "unknown";
|
||||
final int beginLineno = 1;
|
||||
AstRoot astRoot = parser.parse(sourceCode, sourceURI, beginLineno);
|
||||
parseProblems.addAll(errorCollector.getErrors());
|
||||
return astRoot;
|
||||
}
|
||||
|
||||
public EcmascriptNode<AstRoot> parse(final Reader reader) {
|
||||
try {
|
||||
// TODO Fix hardcode
|
||||
final String sourceURI = "unknown";
|
||||
// TODO Fix hardcode
|
||||
final int lineno = 0;
|
||||
AstRoot astRoot = parser.parse(reader, sourceURI, lineno);
|
||||
parseProblems.addAll(errorCollector.getErrors());
|
||||
return astRoot;
|
||||
} catch (final IOException e) {
|
||||
final List<ParseProblem> parseProblems = new ArrayList<ParseProblem>();
|
||||
final String sourceCode = IOUtils.toString(reader);
|
||||
final AstRoot astRoot = parseEcmascript(sourceCode, parseProblems);
|
||||
final EcmascriptTreeBuilder treeBuilder = new EcmascriptTreeBuilder(sourceCode, parseProblems);
|
||||
return treeBuilder.build(astRoot);
|
||||
} catch (IOException e) {
|
||||
throw new ParseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public EcmascriptNode parse(final Reader reader) {
|
||||
final List<ParseProblem> parseProblems = new ArrayList<ParseProblem>();
|
||||
final AstRoot astRoot = parseEcmascript(reader, parseProblems);
|
||||
final EcmascriptTreeBuilder treeBuilder = new EcmascriptTreeBuilder(parseProblems);
|
||||
return treeBuilder.build(astRoot);
|
||||
}
|
||||
}
|
||||
|
@ -65,9 +65,9 @@ import org.mozilla.javascript.ast.XmlExpression;
|
||||
import org.mozilla.javascript.ast.XmlMemberGet;
|
||||
import org.mozilla.javascript.ast.XmlString;
|
||||
|
||||
public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
public final class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
|
||||
protected static final Map<Class<? extends AstNode>, Constructor<? extends EcmascriptNode>> NODE_TYPE_TO_NODE_ADAPTER_TYPE = new HashMap<Class<? extends AstNode>, Constructor<? extends EcmascriptNode>>();
|
||||
private static final Map<Class<? extends AstNode>, Constructor<? extends EcmascriptNode<?>>> NODE_TYPE_TO_NODE_ADAPTER_TYPE = new HashMap<Class<? extends AstNode>, Constructor<? extends EcmascriptNode<?>>>();
|
||||
static {
|
||||
register(ArrayComprehension.class, ASTArrayComprehension.class);
|
||||
register(ArrayComprehensionLoop.class, ASTArrayComprehensionLoop.class);
|
||||
@ -120,7 +120,7 @@ public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
register(XmlString.class, ASTXmlString.class);
|
||||
}
|
||||
|
||||
protected static void register(Class<? extends AstNode> nodeType, Class<? extends EcmascriptNode> nodeAdapterType) {
|
||||
private static <T extends AstNode> void register(Class<T> nodeType, Class<? extends EcmascriptNode<T>> nodeAdapterType) {
|
||||
try {
|
||||
NODE_TYPE_TO_NODE_ADAPTER_TYPE.put(nodeType, nodeAdapterType.getConstructor(nodeType));
|
||||
} catch (SecurityException e) {
|
||||
@ -139,13 +139,18 @@ public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
// The Rhino nodes with children to build.
|
||||
protected Stack<AstNode> parents = new Stack<AstNode>();
|
||||
|
||||
public EcmascriptTreeBuilder(List<ParseProblem> parseProblems) {
|
||||
private final SourceCodePositioner sourceCodePositioner;
|
||||
|
||||
public EcmascriptTreeBuilder(String sourceCode, List<ParseProblem> parseProblems) {
|
||||
this.sourceCodePositioner = new SourceCodePositioner(sourceCode);
|
||||
this.parseProblems = parseProblems;
|
||||
}
|
||||
|
||||
protected EcmascriptNode createNodeAdapter(AstNode node) {
|
||||
private <T extends AstNode> EcmascriptNode<T> createNodeAdapter(T node) {
|
||||
try {
|
||||
Constructor<? extends EcmascriptNode> constructor = NODE_TYPE_TO_NODE_ADAPTER_TYPE.get(node.getClass());
|
||||
@SuppressWarnings("unchecked") // the register function makes sure only EcmascriptNode<T> can be added,
|
||||
// where T is "T extends AstNode".
|
||||
Constructor<? extends EcmascriptNode<T>> constructor = (Constructor<? extends EcmascriptNode<T>>) NODE_TYPE_TO_NODE_ADAPTER_TYPE.get(node.getClass());
|
||||
if (constructor == null) {
|
||||
throw new IllegalArgumentException("There is no Node adapter class registered for the Node class: "
|
||||
+ node.getClass());
|
||||
@ -160,8 +165,10 @@ public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
public EcmascriptNode build(AstNode astNode) {
|
||||
EcmascriptNode node = buildInternal(astNode);
|
||||
public <T extends AstNode> EcmascriptNode<T> build(T astNode) {
|
||||
EcmascriptNode<T> node = buildInternal(astNode);
|
||||
|
||||
calculateLineNumbers(node);
|
||||
|
||||
// Set all the trailing comma nodes
|
||||
for (TrailingCommaNode trailingCommaNode : parseProblemToNode.values()) {
|
||||
@ -171,9 +178,9 @@ public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
return node;
|
||||
}
|
||||
|
||||
protected EcmascriptNode buildInternal(AstNode astNode) {
|
||||
private <T extends AstNode> EcmascriptNode<T> buildInternal(T astNode) {
|
||||
// Create a Node
|
||||
EcmascriptNode node = createNodeAdapter(astNode);
|
||||
EcmascriptNode<T> node = createNodeAdapter(astNode);
|
||||
|
||||
// Append to parent
|
||||
Node parent = nodes.isEmpty() ? null : nodes.peek();
|
||||
@ -203,7 +210,7 @@ public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleParseProblems(EcmascriptNode node) {
|
||||
private void handleParseProblems(EcmascriptNode<? extends AstNode> node) {
|
||||
if (node instanceof TrailingCommaNode) {
|
||||
TrailingCommaNode trailingCommaNode = (TrailingCommaNode) node;
|
||||
int nodeStart = node.getNode().getAbsolutePosition();
|
||||
@ -216,7 +223,7 @@ public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
if ("Trailing comma is not legal in an ECMA-262 object initializer".equals(parseProblem.getMessage())) {
|
||||
// Report on the shortest code block containing the
|
||||
// problem (i.e. inner most code in nested structures).
|
||||
EcmascriptNode currentNode = (EcmascriptNode) parseProblemToNode.get(parseProblem);
|
||||
EcmascriptNode<? extends AstNode> currentNode = (EcmascriptNode<? extends AstNode>) parseProblemToNode.get(parseProblem);
|
||||
if (currentNode == null || node.getNode().getLength() < currentNode.getNode().getLength()) {
|
||||
parseProblemToNode.put(parseProblem, trailingCommaNode);
|
||||
}
|
||||
@ -225,4 +232,15 @@ public class EcmascriptTreeBuilder implements NodeVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateLineNumbers(EcmascriptNode<?> node) {
|
||||
EcmascriptParserVisitorAdapter visitor = new EcmascriptParserVisitorAdapter() {
|
||||
@Override
|
||||
public Object visit(EcmascriptNode node, Object data) {
|
||||
((AbstractEcmascriptNode<?>)node).calculateLineNumbers(sourceCodePositioner);
|
||||
return super.visit(node, data); // also visit the children
|
||||
}
|
||||
};
|
||||
node.jjtAccept(visitor, null);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd.lang.ecmascript.ast;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Calculates from an absolute offset in the source file the line/column coordinate.
|
||||
* This is needed as Rhino only offers absolute positions for each node.
|
||||
*
|
||||
* Idea from: http://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/javascript/jscomp/SourceFile.java
|
||||
*/
|
||||
public class SourceCodePositioner {
|
||||
|
||||
private int[] lineOffsets;
|
||||
|
||||
public SourceCodePositioner(String sourceCode) {
|
||||
analyzeLineOffsets(sourceCode);
|
||||
}
|
||||
|
||||
private void analyzeLineOffsets(String sourceCode) {
|
||||
String[] lines = sourceCode.split("\n");
|
||||
|
||||
int startOffset = 0;
|
||||
int lineNumber = 0;
|
||||
|
||||
lineOffsets = new int[lines.length];
|
||||
|
||||
for (String line : lines) {
|
||||
lineOffsets[lineNumber] = startOffset;
|
||||
lineNumber++;
|
||||
startOffset += line.length() + 1; // +1 for the "\n" character
|
||||
}
|
||||
}
|
||||
|
||||
public int lineNumberFromOffset(int offset) {
|
||||
int search = Arrays.binarySearch(lineOffsets, offset);
|
||||
int lineNumber;
|
||||
if (search >= 0) {
|
||||
lineNumber = search;
|
||||
} else {
|
||||
int insertionPoint = search;
|
||||
insertionPoint += 1;
|
||||
insertionPoint *= -1;
|
||||
lineNumber = insertionPoint - 1; // take the insertion point one before
|
||||
}
|
||||
return lineNumber + 1; // 1-based line numbers
|
||||
}
|
||||
|
||||
public int columnFromOffset(int offset) {
|
||||
int lineNumber = lineNumberFromOffset(offset);
|
||||
int columnOffset = offset - lineOffsets[lineNumber - 1];
|
||||
return columnOffset + 1; // 1-based column offsets
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user