diff --git a/antlr4-wrapper.xml b/antlr4-wrapper.xml
new file mode 100644
index 0000000000..61f9c5fddf
--- /dev/null
+++ b/antlr4-wrapper.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/_config.yml b/docs/_config.yml
index 77b485279f..1bfd57e8fd 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,10 +1,10 @@
repository: pmd/pmd
pmd:
- version: 6.25.0-SNAPSHOT
+ version: 7.0.0-SNAPSHOT
previous_version: 6.24.0
- date: ??-June-2020
- release_type: minor
+ date: ??-2020
+ release_type: major
# release types: major, minor, bugfix
diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml
index 439fa25e96..2bd5c4f803 100644
--- a/docs/_data/sidebars/pmd_sidebar.yml
+++ b/docs/_data/sidebars/pmd_sidebar.yml
@@ -433,6 +433,9 @@ entries:
- title: What does 'PMD' mean?
url: /pmd_projectdocs_trivia_meaning.html
output: web, pdf
+ - title: Logo
+ url: /pmd_projectdocs_logo.html
+ output: web, pdf
- title: FAQ
url: /pmd_projectdocs_faq.html
output: web, pdf
diff --git a/docs/images/logo/PMD.png b/docs/images/logo/PMD.png
new file mode 100644
index 0000000000..48d247aea5
Binary files /dev/null and b/docs/images/logo/PMD.png differ
diff --git a/docs/images/logo/PMD.svg b/docs/images/logo/PMD.svg
new file mode 100644
index 0000000000..fb27428988
--- /dev/null
+++ b/docs/images/logo/PMD.svg
@@ -0,0 +1,105 @@
+
+
diff --git a/docs/images/logo/favicon.ico b/docs/images/logo/favicon.ico
new file mode 100644
index 0000000000..bd9e18f2a0
Binary files /dev/null and b/docs/images/logo/favicon.ico differ
diff --git a/docs/images/logo/pmd-logo-300px.png b/docs/images/logo/pmd-logo-300px.png
new file mode 100644
index 0000000000..e8f2112b3b
Binary files /dev/null and b/docs/images/logo/pmd-logo-300px.png differ
diff --git a/docs/images/logo/pmd-logo-600px.png b/docs/images/logo/pmd-logo-600px.png
new file mode 100644
index 0000000000..387897f800
Binary files /dev/null and b/docs/images/logo/pmd-logo-600px.png differ
diff --git a/docs/images/logo/pmd-logo-white-300px.png b/docs/images/logo/pmd-logo-white-300px.png
new file mode 100644
index 0000000000..878889121f
Binary files /dev/null and b/docs/images/logo/pmd-logo-white-300px.png differ
diff --git a/docs/images/logo/pmd-logo-white-600px.png b/docs/images/logo/pmd-logo-white-600px.png
new file mode 100644
index 0000000000..933b031e68
Binary files /dev/null and b/docs/images/logo/pmd-logo-white-600px.png differ
diff --git a/docs/index.md b/docs/index.md
index 55e9c9e5cd..aa76696556 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -52,6 +52,7 @@ in a variety of ways, which are [documented here](pmd_userdocs_cpd.html).
The latest release of PMD can be downloaded from our [Github releases page](https://github.com/pmd/pmd/releases/latest).
+The Logo is available from the [Logo Project Page](pmd_projectdocs_logo.html).
## Documentation
diff --git a/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md b/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md
index b6d94683e1..4d518453ba 100644
--- a/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md
+++ b/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md
@@ -74,3 +74,62 @@ All you need to do is follow this few steps:
You should take a look to [Kotlin token filter implementation](https://github.com/pmd/pmd/blob/master/pmd-kotlin/src/main/java/net/sourceforge/pmd/cpd/KotlinTokenizer.java)
- For non-Antlr grammars you can use [BaseTokenFilter](https://github.com/pmd/pmd/blob/master/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/internal/BaseTokenFilter.java) directly or take a peek to [Java's token filter](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java)
+
+
+### Testing your implementation
+
+Add a Maven dependency on `pmd-lang-test` (scope `test`) in your `pom.xml`.
+This contains utilities to test your Tokenizer.
+
+For simple tests, create a test class extending from `CpdTextComparisonTest`.
+That class is written in Kotlin, but you can extend it in Java as well.
+
+To add tests, you need to write regular JUnit `@Test`-annotated methods, and
+call the method `doTest` with the name of the test file.
+
+For example, for the Dart language:
+
+```java
+
+public class DartTokenizerTest extends CpdTextComparisonTest {
+
+ /**********************************
+ Implementation of the superclass
+ ***********************************/
+
+
+ public DartTokenizerTest() {
+ super(".dart"); // the file extension for the dart language
+ }
+
+ @Override
+ protected String getResourcePrefix() {
+ // If your class is in src/test/java /some/package
+ // you need to place the test files in src/test/resources/some/package/cpdData
+ return "cpdData";
+ }
+
+ @Override
+ public Tokenizer newTokenizer() {
+ // Override this abstract method to return the correct tokenizer
+ return new DartTokenizer();
+ }
+
+ /**************
+ Test methods
+ ***************/
+
+
+ @Test // don't forget the JUnit annotation
+ public void testLiterals() {
+ // This will look for a file named literals.dart
+ // in the directory identified by getResourcePrefix,
+ // tokenize it, then compare the result against a baseline
+ // literals.txt file in the same directory
+
+ // If the baseline file does not exist, it is created automatically
+ doTest("literals");
+ }
+
+}
+```
\ No newline at end of file
diff --git a/docs/pages/pmd/projectdocs/logo.md b/docs/pages/pmd/projectdocs/logo.md
new file mode 100644
index 0000000000..17a94f01e9
--- /dev/null
+++ b/docs/pages/pmd/projectdocs/logo.md
@@ -0,0 +1,20 @@
+---
+title: Logo
+sidebar: pmd_sidebar
+permalink: pmd_projectdocs_logo.html
+folder: pmd/projectdocs
+---
+
+![PMD Logo](images/logo/pmd-logo-300px.png)
+
+
+The following PMD Logos and Icons are licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/):
+
+* [PMD.svg](images/logo/PMD.svg)
+* [PMD.png](images/logo/PMD.png)
+* [Logo (300px, transparent)](images/logo/pmd-logo-300px.png)
+* [Logo (300px, white)](images/logo/pmd-logo-white-300px.png)
+* [Logo (600px, transparent)](images/logo/pmd-logo-600px.png)
+* [Logo (600px, white)](images/logo/pmd-logo-white-600px.png)
+
+* [Favicon (16x16)](images/logo/favicon.ico)
diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md
index a43c809081..743f5c3a15 100644
--- a/docs/pages/pmd/userdocs/cli_reference.md
+++ b/docs/pages/pmd/userdocs/cli_reference.md
@@ -40,7 +40,9 @@ The tool comes with a rather extensive help text, simply running with `-help`!
{% include custom/cli_option_row.html options="-auxclasspath"
option_arg="cp"
description="Specifies the classpath for libraries used by the source code.
- This is used to resolve types in source files. Alternatively, a `file://` URL
+ This is used to resolve types in source files. The platform specific path delimiter
+ (\":\" on Linux, \";\" on Windows) is used to separate the entries.
+ Alternatively, a single `file:` URL
to a text file containing path elements on consecutive lines can be specified."
languages="Java"
%}
diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md
index 9eac6d5a65..eee331d256 100644
--- a/docs/pages/release_notes.md
+++ b/docs/pages/release_notes.md
@@ -53,30 +53,82 @@ The command line version of PMD continues to use **scala 2.13**.
* The new Java Rule {% rule "java/codestyle/UnnecessaryCast" %} (`java-codestyle`)
finds casts that are unnecessary while accessing collection elements.
+* The new Java Rule {% rule "java/performance/AvoidCalendarDateCreation" %} (`java-performance`)
+ finds usages of `java.util.Calendar` whose purpose is just to get the current date. This
+ can be done in a more lightweight way.
+
+#### Modified rules
+
+* The Java rule {% rule "java/codestyle/UseDiamondOperator" %} (`java-codestyle`) now by default
+ finds unnecessary usages of type parameters, which are nested, involve wildcards and are used
+ within a ternary operator. These usages are usually only unnecessary with Java8 and later, when
+ the type inference in Java has been improved.
+
+ In order to avoid false positives when checking Java7 only code, the rule has the new property
+ `java7Compatibility`, which is disabled by default. Settings this to "true" retains
+ the old rule behaviour.
+
### Fixed Issues
+* apex-bestpractices
+ * [#2554](https://github.com/pmd/pmd/issues/2554): \[apex] Exception applying rule UnusedLocalVariable on trigger
+* core
+ * [#2599](https://github.com/pmd/pmd/pull/2599): \[core] Fix XPath 2.0 Rule Chain Analyzer with Unions
+ * [#2483](https://github.com/pmd/pmd/issues/2483): \[lang-test] Support cpd tests based on text comparison.
+ For details see
+ [Testing your implementation](pmd_devdocs_major_adding_new_cpd_language.html#testing-your-implementation)
+ in the developer documentation.
* c#
* [#2551](https://github.com/pmd/pmd/issues/2551): \[c#] CPD suppression with comments doesn't work
+* cpp
+ * [#1757](https://github.com/pmd/pmd/issues/1757): \[cpp] Support unicode characters
+* java
+ * [#2549](https://github.com/pmd/pmd/issues/2549): \[java] Auxclasspath in PMD CLI does not support relative file path
+* java-codestyle
+ * [#2545](https://github.com/pmd/pmd/issues/2545): \[java] UseDiamondOperator false negatives
+ * [#2573](https://github.com/pmd/pmd/pull/2573): \[java] DefaultPackage: Allow package default JUnit 5 Test methods
* java-design
* [#2563](https://github.com/pmd/pmd/pull/2563): \[java] UselessOverridingMethod false negative with already public methods
+ * [#2570](https://github.com/pmd/pmd/issues/2570): \[java] NPathComplexity should mention the expected NPath complexity
+* java-errorprone
+ * [#2544](https://github.com/pmd/pmd/issues/2544): \[java] UseProperClassLoader can not detect the case with method call on intermediate variable
+* java-performance
+ * [#2591](https://github.com/pmd/pmd/pull/2591): \[java] InefficientStringBuffering/AppendCharacterWithChar: Fix false negatives with concats in appends
+ * [#2600](https://github.com/pmd/pmd/pull/2600): \[java] UseStringBufferForStringAppends: fix false negative with fields
* scala
* [#2547](https://github.com/pmd/pmd/pull/2547): \[scala] Add cross compilation for scala 2.12 and 2.13
+
### API Changes
* The maven module `net.sourceforge.pmd:pmd-scala` is deprecated. Use `net.sourceforge.pmd:pmd-scala_2.13`
or `net.sourceforge.pmd:pmd-scala_2.12` instead.
-
+
#### Deprecated APIs
-* {%jdoc apex::lang.apex.ast.ASTAnnotation#suppresses(core::Rule) %}
+* {% jdoc !!apex::lang.apex.ast.ASTAnnotation#suppresses(core::Rule) %} (Apex)
+* {% jdoc !!core::cpd.TokenEntry#TokenEntry(java.lang.String, java.lang.String, int) %}
+* {% jdoc test::testframework.AbstractTokenizerTest %}. Use CpdTextComparisonTest in module pmd-lang-test instead.
+ For details see
+ [Testing your implementation](pmd_devdocs_major_adding_new_cpd_language.html#testing-your-implementation)
+ in the developer documentation.
+* {% jdoc !!java::lang.java.rule.performance.InefficientStringBufferingRule#isInStringBufferOperation(net.sourceforge.pmd.lang.ast.Node, int, java.lang.String) %}
+
+#### Internal API
+
+* {% jdoc apex::lang.apex.ApexParser %}
+* {% jdoc apex::lang.apex.ApexHandler %}
+
### External Contributions
+* [#1932](https://github.com/pmd/pmd/pull/1932): \[java] Added 4 performance rules originating from PMD-jPinpoint-rules - [Jeroen Borgers](https://github.com/jborgers)
* [#2349](https://github.com/pmd/pmd/pull/2349): \[java] Optimize UnusedPrivateMethodRule - [shilko2013](https://github.com/shilko2013)
* [#2547](https://github.com/pmd/pmd/pull/2547): \[scala] Add cross compilation for scala 2.12 and 2.13 - [João Ferreira](https://github.com/jtjeferreira)
* [#2567](https://github.com/pmd/pmd/pull/2567): \[c#] Fix CPD suppression with comments doesn't work - [Lixon Lookose](https://github.com/LixonLookose)
+* [#2573](https://github.com/pmd/pmd/pull/2573): \[java] DefaultPackage: Allow package default JUnit 5 Test methods - [Craig Andrews](https://github.com/candrews)
+* [#2593](https://github.com/pmd/pmd/pull/2593): \[java] NPathComplexity should mention the expected NPath complexity - [Artem Krosheninnikov](https://github.com/KroArtem)
{% endtocmaker %}
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java b/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java
index 08927b2635..afe8db0b3f 100644
--- a/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java
@@ -55,8 +55,10 @@ public class ApexTokenizer implements Tokenizer {
if (!caseSensitive) {
tokenText = tokenText.toLowerCase(Locale.ROOT);
}
- TokenEntry tokenEntry = new TokenEntry(tokenText, sourceCode.getFileName(), token.getLine(),
- token.getCharPositionInLine(), token.getCharPositionInLine() + tokenText.length());
+ TokenEntry tokenEntry = new TokenEntry(tokenText, sourceCode.getFileName(),
+ token.getLine(),
+ token.getCharPositionInLine() + 1,
+ token.getCharPositionInLine() + tokenText.length() + 1);
tokenEntries.add(tokenEntry);
}
token = lexer.nextToken();
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java
index b82edf8d17..b2d57eb9c9 100644
--- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java
@@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.apex;
import java.util.Arrays;
import java.util.List;
+import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.AbstractPmdLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
@@ -20,6 +21,7 @@ import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
import net.sourceforge.pmd.lang.metrics.internal.AbstractLanguageMetricsProvider;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
+@InternalApi
public class ApexHandler extends AbstractPmdLanguageVersionHandler {
private final ApexMetricsProvider myMetricsProvider = new ApexMetricsProvider();
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java
index d30c027b11..2ab9bd8497 100644
--- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java
@@ -9,6 +9,7 @@ import java.io.Reader;
import org.apache.commons.io.IOUtils;
+import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.AbstractParser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.apex.ApexJorjeLogging;
@@ -19,6 +20,7 @@ import net.sourceforge.pmd.lang.ast.SourceCodePositioner;
import apex.jorje.data.Locations;
import apex.jorje.semantic.ast.compilation.Compilation;
+@InternalApi
public final class ApexParser extends AbstractParser {
public ApexParser(ParserOptions parserOptions) {
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java
index 8e1759caf9..e9f2c7b721 100644
--- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java
@@ -24,6 +24,10 @@ public class UnusedLocalVariableRule extends AbstractApexRule {
String variableName = node.getImage();
ASTBlockStatement variableContext = node.getFirstParentOfType(ASTBlockStatement.class);
+ if (variableContext == null) {
+ // if there is no parent BlockStatement, e.g. in triggers
+ return data;
+ }
List> potentialUsages = new ArrayList<>();
diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/cpd/ApexTokenizerTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/cpd/ApexTokenizerTest.java
index d2604656ed..ec002be37c 100644
--- a/pmd-apex/src/test/java/net/sourceforge/pmd/cpd/ApexTokenizerTest.java
+++ b/pmd-apex/src/test/java/net/sourceforge/pmd/cpd/ApexTokenizerTest.java
@@ -4,89 +4,57 @@
package net.sourceforge.pmd.cpd;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Properties;
-import org.apache.commons.io.IOUtils;
import org.junit.Test;
-import net.sourceforge.pmd.PMD;
-import net.sourceforge.pmd.cpd.SourceCode.StringCodeLoader;
+import net.sourceforge.pmd.cpd.test.CpdTextComparisonTest;
+
+public class ApexTokenizerTest extends CpdTextComparisonTest {
+
+ public ApexTokenizerTest() {
+ super(".cls");
+ }
+
+ @Override
+ protected String getResourcePrefix() {
+ return "../lang/apex/cpd/testdata";
+ }
+
+ @Override
+ public Tokenizer newTokenizer(Properties properties) {
+ ApexTokenizer tokenizer = new ApexTokenizer();
+ tokenizer.setProperties(properties);
+ return tokenizer;
+ }
-public class ApexTokenizerTest {
@Test
- public void testTokenize() throws IOException {
- Tokens tokens = tokenize(load("Simple.cls"));
- if (tokens.size() != 28) {
- printTokens(tokens);
- }
- assertEquals(28, tokens.size());
- assertEquals("someparam", findTokensByLine(8, tokens).get(0).toString());
+ public void testTokenize() {
+ doTest("Simple");
}
@Test
- public void testTokenizeCaseSensitive() throws IOException {
- Tokens tokens = tokenize(load("Simple.cls"), true);
- if (tokens.size() != 28) {
- printTokens(tokens);
- }
- assertEquals(28, tokens.size());
- assertEquals("someParam", findTokensByLine(8, tokens).get(0).toString());
+ public void testTokenizeCaseSensitive() {
+ doTest("Simple", "_caseSensitive", caseSensitive());
}
/**
* Comments are ignored since using ApexLexer.
*/
@Test
- public void testTokenizeWithComments() throws IOException {
- Tokens tokens = tokenize(load("issue427/SFDCEncoder.cls"));
- assertEquals(17, tokens.size());
-
- Tokens tokens2 = tokenize(load("issue427/SFDCEncoderConstants.cls"));
- assertEquals(17, tokens2.size());
+ public void testTokenizeWithComments() {
+ doTest("comments");
}
- private List findTokensByLine(int line, Tokens tokens) {
- List result = new ArrayList<>();
- for (TokenEntry entry : tokens.getTokens()) {
- if (entry.getBeginLine() == line) {
- result.add(entry);
- }
- }
- if (result.isEmpty()) {
- fail("Not tokens found at line " + line);
- }
- return result;
+ private Properties caseSensitive() {
+ return properties(true);
}
- private Tokens tokenize(String code) {
- return tokenize(code, false);
- }
-
- private Tokens tokenize(String code, boolean caseSensitive) {
- ApexTokenizer tokenizer = new ApexTokenizer();
+ private Properties properties(boolean caseSensitive) {
Properties properties = new Properties();
properties.setProperty(ApexTokenizer.CASE_SENSITIVE, Boolean.toString(caseSensitive));
- tokenizer.setProperties(properties);
- Tokens tokens = new Tokens();
- tokenizer.tokenize(new SourceCode(new StringCodeLoader(code)), tokens);
- return tokens;
+ return properties;
}
- private void printTokens(Tokens tokens) {
- for (TokenEntry entry : tokens.getTokens()) {
- System.out.printf("%02d: %s%s", entry.getBeginLine(), entry.toString(), PMD.EOL);
- }
- }
-
- private String load(String name) throws IOException {
- return IOUtils.toString(ApexTokenizerTest.class.getResourceAsStream(name), StandardCharsets.UTF_8);
- }
}
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/cpd/Simple.cls b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple.cls
similarity index 100%
rename from pmd-apex/src/test/resources/net/sourceforge/pmd/cpd/Simple.cls
rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple.cls
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple.txt b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple.txt
new file mode 100644
index 0000000000..73703e57cc
--- /dev/null
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple.txt
@@ -0,0 +1,35 @@
+ [Image] or [Truncated image[ Bcol Ecol
+L4
+ [public] 1 7
+ [with] 8 12
+ [sharing] 13 20
+ [class] 21 26
+ [simple] 27 33
+ [{] 34 35
+L5
+ [public] 5 11
+ [string] 12 18
+ [someparam] 19 28
+ [{] 29 30
+ [get] 31 34
+ [;] 34 35
+ [set] 36 39
+ [;] 39 40
+ [}] 41 42
+L7
+ [public] 5 11
+ [void] 12 16
+ [getinit] 17 24
+ [(] 24 25
+ [)] 25 26
+ [{] 27 28
+L8
+ [someparam] 9 18
+ [=] 19 20
+ [test] 22 26
+ [;] 27 28
+L9
+ [}] 5 6
+L10
+ [}] 1 2
+EOF
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple_caseSensitive.txt b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple_caseSensitive.txt
new file mode 100644
index 0000000000..72e856ca5e
--- /dev/null
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/Simple_caseSensitive.txt
@@ -0,0 +1,35 @@
+ [Image] or [Truncated image[ Bcol Ecol
+L4
+ [public] 1 7
+ [with] 8 12
+ [sharing] 13 20
+ [class] 21 26
+ [Simple] 27 33
+ [{] 34 35
+L5
+ [public] 5 11
+ [String] 12 18
+ [someParam] 19 28
+ [{] 29 30
+ [get] 31 34
+ [;] 34 35
+ [set] 36 39
+ [;] 39 40
+ [}] 41 42
+L7
+ [public] 5 11
+ [void] 12 16
+ [getInit] 17 24
+ [(] 24 25
+ [)] 25 26
+ [{] 27 28
+L8
+ [someParam] 9 18
+ [=] 19 20
+ [test] 22 26
+ [;] 27 28
+L9
+ [}] 5 6
+L10
+ [}] 1 2
+EOF
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/comments.cls b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/comments.cls
new file mode 100644
index 0000000000..1666787997
--- /dev/null
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/comments.cls
@@ -0,0 +1,14 @@
+/**
+ * OWO
+ */
+
+/**
+ * Common character classes used for input validation, output encoding, verifying password strength
+ */
+public with sharing class SFDCEncoderConstants {
+ // a single-line comment
+
+ /************ CLASS CODE HERE *************/
+ public String someParam { get; set; }
+}
+
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/comments.txt b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/comments.txt
new file mode 100644
index 0000000000..466927ec64
--- /dev/null
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/cpd/testdata/comments.txt
@@ -0,0 +1,21 @@
+ [Image] or [Truncated image[ Bcol Ecol
+L8
+ [public] 1 7
+ [with] 8 12
+ [sharing] 13 20
+ [class] 21 26
+ [sfdcencoderconstants] 27 47
+ [{] 48 49
+L12
+ [public] 2 8
+ [string] 9 15
+ [someparam] 16 25
+ [{] 26 27
+ [get] 28 31
+ [;] 31 32
+ [set] 33 36
+ [;] 36 37
+ [}] 38 39
+L13
+ [}] 1 2
+EOF
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml
index 0856903941..1888c60fda 100644
--- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml
@@ -83,6 +83,17 @@ public class Foo {
public String fieldUsage() {
return myfield;
}
+}
+ ]]>
+
+
+
+ NPE with triggers (#2554)
+ 0
+
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java
index 2c9a97ffb6..3877315616 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java
@@ -95,7 +95,11 @@ public class PMDParameters {
private String language = null;
@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.")
+ description = "Specifies the classpath for libraries used by the source code. "
+ + "This is used by the type resolution. The platform specific path delimiter "
+ + "(\":\" on Linux, \";\" on Windows) is used to separate the entries. "
+ + "Alternatively, a single 'file:' URL to a text file containing path elements on consecutive lines "
+ + "can be specified.")
private String auxclasspath;
@Parameter(names = { "-failOnViolation", "--failOnViolation" }, arity = 1,
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java
index a79aee80d4..0de0344457 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java
@@ -34,6 +34,10 @@ public abstract class AbstractTokenizer implements Tokenizer {
private int lineNumber = 0;
private String currentLine;
+ // both zero-based
+ private int tokBeginLine;
+ private int tokBeginCol;
+
protected boolean spanMultipleLinesString = true; // Most languages do, so
// default is true
protected Character spanMultipleLinesLineContinuationCharacter = null;
@@ -49,23 +53,35 @@ public abstract class AbstractTokenizer implements Tokenizer {
int loc = 0;
while (loc < currentLine.length()) {
StringBuilder token = new StringBuilder();
- loc = getTokenFromLine(token, loc);
+ loc = getTokenFromLine(token, loc); // may jump several lines
+
if (token.length() > 0 && !isIgnorableString(token.toString())) {
+ final String image;
if (downcaseString) {
- token = new StringBuilder(token.toString().toLowerCase(Locale.ROOT));
+ image = token.toString().toLowerCase(Locale.ROOT);
+ } else {
+ image = token.toString();
}
- // need to re-think how to link this
- // if ( CPD.debugEnable ) {
- // System.out.println("Token added:" + token.toString());
- // }
- tokenEntries.add(new TokenEntry(token.toString(), tokens.getFileName(), lineNumber + 1, loc - token.length(), loc - 1));
+
+ tokenEntries.add(new TokenEntry(image,
+ tokens.getFileName(),
+ tokBeginLine + 1,
+ tokBeginCol + 1,
+ loc + 1));
}
}
}
tokenEntries.add(TokenEntry.getEOF());
}
+ /**
+ * Returns (0-based) EXclusive offset of the end of the token,
+ * may jump several lines (sets {@link #lineNumber} in this case).
+ */
private int getTokenFromLine(StringBuilder token, int loc) {
+ tokBeginLine = lineNumber;
+ tokBeginCol = loc;
+
for (int j = loc; j < currentLine.length(); j++) {
char tok = currentLine.charAt(j);
if (!Character.isWhitespace(tok) && !ignoreCharacter(tok)) {
@@ -89,6 +105,9 @@ public abstract class AbstractTokenizer implements Tokenizer {
} else {
if (token.length() > 0) {
return j;
+ } else {
+ // ignored char
+ tokBeginCol++;
}
}
loc = j;
@@ -125,14 +144,14 @@ public abstract class AbstractTokenizer implements Tokenizer {
if (spanMultipleLinesLineContinuationCharacter != null
&& token.length() > 0
&& token.charAt(token.length() - 1) == spanMultipleLinesLineContinuationCharacter) {
- token.deleteCharAt(token.length() - 1);
+ token.setLength(token.length() - 1);
}
// parsing new line
currentLine = code.get(++lineNumber);
// Warning : recursive call !
loc = parseString(token, 0, stringDelimiter);
}
- return loc + 1;
+ return loc;
}
private boolean ignoreCharacter(char tok) {
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java
index 2e9745bb8e..d45a4af7b8 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java
@@ -22,14 +22,17 @@ public class AnyTokenizer implements Tokenizer {
StringBuilder sb = sourceCode.getCodeBuffer();
try (BufferedReader reader = new BufferedReader(new CharArrayReader(sb.toString().toCharArray()))) {
int lineNumber = 1;
+ int colNumber = 1;
String line = reader.readLine();
while (line != null) {
StringTokenizer tokenizer = new StringTokenizer(line, TOKENS, true);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
+ int endCol = colNumber + token.length() - 1; // -1 because inclusive
if (!" ".equals(token) && !"\t".equals(token)) {
- tokenEntries.add(new TokenEntry(token, sourceCode.getFileName(), lineNumber));
+ tokenEntries.add(new TokenEntry(token, sourceCode.getFileName(), lineNumber, colNumber, endCol));
}
+ colNumber = endCol + 1;
}
// advance iteration variables
line = reader.readLine();
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Mark.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Mark.java
index a1b88be336..c741a337ad 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Mark.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Mark.java
@@ -60,6 +60,7 @@ public class Mark implements Comparable {
this.endToken = endToken;
}
+ /** Newlines are normalized to \n. */
public String getSourceCodeSlice() {
return this.code.getSlice(getBeginLine(), getEndLine());
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Match.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Match.java
index 19f69b981a..992f551e9c 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Match.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Match.java
@@ -73,6 +73,7 @@ public class Match implements Comparable, Iterable {
return this.tokenCount;
}
+ /** Newlines are normalized to \n. */
public String getSourceCodeSlice() {
return this.getMark(0).getSourceCodeSlice();
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java
index 9ccef7f4f1..9d499def1a 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java
@@ -49,13 +49,13 @@ public class SimpleRenderer implements Renderer, CPDRenderer {
String source = match.getSourceCodeSlice();
if (trimLeadingWhitespace) {
- String[] lines = source.split('[' + PMD.EOL + ']');
+ String[] lines = source.split("\n");
int trimDepth = StringUtil.maxCommonLeadingWhitespaceForAll(lines);
if (trimDepth > 0) {
lines = StringUtil.trimStartOn(lines, trimDepth);
}
- for (int i = 0; i < lines.length; i++) {
- writer.append(lines[i]).append(PMD.EOL);
+ for (String line : lines) {
+ writer.append(line).append(PMD.EOL);
}
return;
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SourceCode.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SourceCode.java
index cb9468d948..99cf8f8829 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SourceCode.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SourceCode.java
@@ -17,8 +17,6 @@ import java.util.List;
import org.apache.commons.io.ByteOrderMark;
import org.apache.commons.io.input.BOMInputStream;
-import net.sourceforge.pmd.PMD;
-
public class SourceCode {
public abstract static class CodeLoader {
@@ -36,6 +34,12 @@ public class SourceCode {
return code.get();
}
+ /**
+ * Loads a range of lines.
+ *
+ * @param startLine Start line (inclusive, 1-based)
+ * @param endLine End line (inclusive, 1-based)
+ */
public List getCodeSlice(int startLine, int endLine) {
List c = null;
if (code != null) {
@@ -65,9 +69,15 @@ public class SourceCode {
}
}
+ /**
+ * Loads a range of lines.
+ *
+ * @param startLine Start line (inclusive, 1-based)
+ * @param endLine End line (inclusive, 1-based)
+ */
protected List load(int startLine, int endLine) {
try (BufferedReader reader = new BufferedReader(getReader())) {
- int linesToRead = endLine - startLine;
+ int linesToRead = 1 + endLine - startLine; // +1 because endLine is inclusive
List lines = new ArrayList<>(linesToRead);
// Skip lines until we reach the start point
@@ -185,22 +195,29 @@ public class SourceCode {
return cl.getCode();
}
+ /** Newlines are normalized to \n. */
public StringBuilder getCodeBuffer() {
StringBuilder sb = new StringBuilder();
List lines = cl.getCode();
for (String line : lines) {
- sb.append(line).append(PMD.EOL);
+ sb.append(line).append('\n');
}
return sb;
}
+ /**
+ * Loads a range of lines. Newlines are normalized to \n
+ *
+ * @param startLine Start line (inclusive, 1-based)
+ * @param endLine End line (inclusive, 1-based)
+ */
public String getSlice(int startLine, int endLine) {
List lines = cl.getCodeSlice(startLine, endLine);
StringBuilder sb = new StringBuilder();
for (String line : lines) {
if (sb.length() != 0) {
- sb.append(PMD.EOL);
+ sb.append('\n');
}
sb.append(line);
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/TokenEntry.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/TokenEntry.java
index 7246dc6acf..85c539965d 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/TokenEntry.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/TokenEntry.java
@@ -48,7 +48,10 @@ public class TokenEntry implements Comparable {
* @param image
* @param tokenSrcID
* @param beginLine the linenumber, 1-based.
+ *
+ * @deprecated Use {@link #TokenEntry(String, String, int, int, int)}, don't be lazy
*/
+ @Deprecated
public TokenEntry(String image, String tokenSrcID, int beginLine) {
this(image, tokenSrcID, beginLine, -1, -1);
}
@@ -62,6 +65,7 @@ public class TokenEntry implements Comparable {
* @param endColumn the column number, 1-based
*/
public TokenEntry(String image, String tokenSrcID, int beginLine, int beginColumn, int endColumn) {
+ assert isOk(beginLine) && isOk(beginColumn) && isOk(endColumn) : "Coordinates are 1-based";
setImage(image);
this.tokenSrcID = tokenSrcID;
this.beginLine = beginLine;
@@ -70,6 +74,10 @@ public class TokenEntry implements Comparable {
this.index = TOKEN_COUNT.get().getAndIncrement();
}
+ private boolean isOk(int coord) {
+ return coord >= 1 || coord == -1;
+ }
+
public static TokenEntry getEOF() {
TOKEN_COUNT.get().getAndIncrement();
return EOF;
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java
index d7e29d93d7..33230152d9 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java
@@ -135,10 +135,12 @@ public final class XMLRenderer implements Renderer, CPDRenderer {
}
private Element addCodeSnippet(Document doc, Element duplication, Match match) {
- String codeSnipet = match.getSourceCodeSlice();
- if (codeSnipet != null) {
+ String codeSnippet = match.getSourceCodeSlice();
+ if (codeSnippet != null) {
+ // the code snippet has normalized line endings
+ String platformSpecific = codeSnippet.replace("\n", System.lineSeparator());
Element codefragment = doc.createElement("codefragment");
- codefragment.appendChild(doc.createCDATASection(codeSnipet));
+ codefragment.appendChild(doc.createCDATASection(platformSpecific));
duplication.appendChild(codefragment);
}
return duplication;
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/internal/AntlrTokenizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/internal/AntlrTokenizer.java
index 7d8462e164..91c4215565 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/internal/AntlrTokenizer.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/internal/AntlrTokenizer.java
@@ -49,7 +49,7 @@ public abstract class AntlrTokenizer implements Tokenizer {
}
private void processToken(final Tokens tokenEntries, final String fileName, final AntlrToken token) {
- final TokenEntry tokenEntry = new TokenEntry(token.getImage(), fileName, token.getBeginLine(), token.getBeginColumn() + 1, token.getEndColumn() + 1);
+ final TokenEntry tokenEntry = new TokenEntry(token.getImage(), fileName, token.getBeginLine(), token.getBeginColumn(), token.getEndColumn());
tokenEntries.add(tokenEntry);
}
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
index c3645cfe52..91ab9de067 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
@@ -244,6 +244,7 @@ public interface Node {
}
}
+
/**
* Returns a data map used to store additional information on this node.
*
@@ -251,6 +252,7 @@ public interface Node {
*/
DataMap> getUserMap();
+
/**
* Returns the parent of this node, or null if this is the {@linkplain RootNode root}
* of the tree.
@@ -259,6 +261,7 @@ public interface Node {
*/
Node getParent();
+
/**
* Returns the child of this node at the given index.
*
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AbstractAntlrVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AbstractAntlrVisitor.java
deleted file mode 100644
index d4acd2a85c..0000000000
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AbstractAntlrVisitor.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.ast.impl.antlr4;
-
-import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor;
-import org.antlr.v4.runtime.tree.ParseTree;
-import org.antlr.v4.runtime.tree.ParseTreeVisitor;
-
-public abstract class AbstractAntlrVisitor extends AbstractParseTreeVisitor implements ParseTreeVisitor {
-
- @Override
- public T visit(ParseTree tree) {
- if (tree instanceof AntlrBaseNode) {
- return visit((AntlrBaseNode) tree);
- }
- return tree.accept(this);
- }
-
- public T visit(final AntlrBaseNode node) {
- return node.accept(this);
- }
-}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseNode.java
deleted file mode 100644
index 5f6e3f52d7..0000000000
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseNode.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.ast.impl.antlr4;
-
-import org.antlr.v4.runtime.ParserRuleContext;
-
-import net.sourceforge.pmd.util.DataMap;
-import net.sourceforge.pmd.util.DataMap.DataKey;
-
-public abstract class AntlrBaseNode extends ParserRuleContext implements AntlrNode {
-
- private final DataMap> userData = DataMap.newDataMap();
-
- /**
- * Constructor required by {@link ParserRuleContext}
- */
- @SuppressWarnings("unused")
- public AntlrBaseNode() {
- // Nothing to be done
- }
-
- /**
- * Constructor required by {@link ParserRuleContext}
- *
- * @param parent The parent
- * @param invokingStateNumber the invokingState defined by {@link org.antlr.v4.runtime.RuleContext} parent
- */
- @SuppressWarnings("unused")
- public AntlrBaseNode(final ParserRuleContext parent, final int invokingStateNumber) {
- super(parent, invokingStateNumber);
- }
-
- /**
- * TODO @NoAttribute (port swift rules)
- */
- @Override
- @SuppressWarnings("PMD.UselessOverridingMethod")
- public String getText() {
- return super.getText();
- }
-
-
- // FIXME these coordinates are not accurate
-
- @Override
- public int getBeginLine() {
- return start.getLine(); // This goes from 1 to n
- }
-
- @Override
- public int getEndLine() {
- return stop.getLine(); // This goes from 1 to n
- }
-
- @Override
- public int getBeginColumn() {
- return start.getCharPositionInLine(); // This goes from 0 to (n - 1)
- }
-
- @Override
- public int getEndColumn() {
- return stop.getCharPositionInLine(); // This goes from 0 to (n - 1)
- }
-
- @Override
- public DataMap> getUserMap() {
- return userData;
- }
-
- @Override
- public AntlrNode getChild(int i) {
- return (AntlrNode) super.getChild(i);
- }
-
- @Override
- public AntlrBaseNode getParent() {
- return (AntlrBaseNode) super.getParent();
- }
-
- @Override
- public int getNumChildren() {
- return getChildCount();
- }
-
- @Override
- public String getXPathNodeName() {
- final String simpleName = getClass().getSimpleName();
- return simpleName.substring(0, simpleName.length() - "Context".length());
- }
-}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java
index 1f92c96862..0e84059e63 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java
@@ -7,6 +7,8 @@ package net.sourceforge.pmd.lang.ast.impl.antlr4;
import java.io.IOException;
import java.io.Reader;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Lexer;
import net.sourceforge.pmd.lang.Parser;
@@ -15,9 +17,16 @@ import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.ast.RootNode;
/**
- * Generic Antlr parser adapter for all Antlr parsers.
+ * Generic Antlr parser adapter for all Antlr parsers. This wraps a parser
+ * generated by antlr, soo {@link AntlrGeneratedParserBase}.
+ *
+ * @param Supertype of all nodes for the language, eg SwiftNode
+ * @param Type of the root node
*/
-public abstract class AntlrBaseParser implements Parser {
+public abstract class AntlrBaseParser<
+ N extends AntlrNode,
+ R extends BaseAntlrInnerNode & RootNode
+ > implements Parser {
protected final ParserOptions parserOptions;
@@ -31,17 +40,17 @@ public abstract class AntlrBaseParser imp
}
@Override
- public RootNode parse(final String fileName, final Reader source) throws ParseException {
+ public R parse(final String fileName, final Reader source) throws ParseException {
+ CharStream cs;
try {
- return getRootNode(getParser(getLexer(source)));
+ cs = CharStreams.fromReader(source, fileName);
} catch (final IOException e) {
throw new ParseException(e);
}
+ return parse(getLexer(cs));
}
- protected abstract RootNode getRootNode(T parser);
+ protected abstract R parse(Lexer parser);
- protected abstract Lexer getLexer(Reader source) throws IOException;
-
- protected abstract T getParser(Lexer lexer);
+ protected abstract Lexer getLexer(CharStream source);
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseRootNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseRootNode.java
deleted file mode 100644
index dd482d9bf8..0000000000
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseRootNode.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.ast.impl.antlr4;
-
-import org.antlr.v4.runtime.ParserRuleContext;
-
-import net.sourceforge.pmd.lang.ast.RootNode;
-
-public abstract class AntlrBaseRootNode extends AntlrBaseNode implements RootNode {
-
- /**
- * Constructor required by {@link ParserRuleContext}
- */
- @SuppressWarnings("unused")
- public AntlrBaseRootNode() {
- super();
- }
-
- /**
- * Constructor required by {@link ParserRuleContext}
- *
- * @param parent The parent
- * @param invokingStateNumber the invokingState defined by {@link org.antlr.v4.runtime.RuleContext} parent
- */
- @SuppressWarnings("unused")
- public AntlrBaseRootNode(final ParserRuleContext parent, final int invokingStateNumber) {
- super(parent, invokingStateNumber);
- }
-}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseRule.java
index 6e822d69ef..db2cd3fa26 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseRule.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseRule.java
@@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.ast.impl.antlr4;
import java.util.List;
import net.sourceforge.pmd.RuleContext;
+import net.sourceforge.pmd.lang.ast.AstVisitor;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.AbstractRule;
@@ -21,13 +22,11 @@ public abstract class AntlrBaseRule extends AbstractRule {
@Override
public void apply(List extends Node> nodes, RuleContext ctx) {
- AbstractAntlrVisitor visitor = buildVisitor(ctx);
+ AstVisitor visitor = buildVisitor();
assert visitor != null : "Rule should provide a non-null visitor";
for (Node node : nodes) {
- assert node instanceof AntlrBaseNode : "Incorrect node type " + node + " passed to " + this;
-
- ((AntlrBaseNode) node).accept(visitor);
+ node.acceptVisitor(visitor, ctx);
}
}
@@ -36,10 +35,8 @@ public abstract class AntlrBaseRule extends AbstractRule {
* This visitor should explore the nodes it's interested in and report
* violations on the given rule context.
*
- * @param ruleCtx Object that accumulates rule violations
- *
* @return A visitor bound to the given rule context
*/
- public abstract AbstractAntlrVisitor buildVisitor(RuleContext ruleCtx);
+ public abstract AstVisitor buildVisitor();
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrGeneratedParserBase.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrGeneratedParserBase.java
new file mode 100644
index 0000000000..5571c21e70
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrGeneratedParserBase.java
@@ -0,0 +1,87 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.ast.impl.antlr4;
+
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.RuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenStream;
+import org.antlr.v4.runtime.tree.ErrorNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import net.sourceforge.pmd.lang.ast.Node;
+
+/**
+ * This is the base class for antlr generated parsers. The implementation
+ * of PMD's {@link net.sourceforge.pmd.lang.Parser} interface is {@link AntlrBaseParser}.
+ *
+ *
This class must implement the two abstract methods to create terminals
+ * and error nodes that implement {@code }. The inner nodes implement PMD
+ * interfaces, and manipulation methods that the {@link Parser} superclass
+ * uses are redirected to the underlying antlr {@link ParserRuleContext} (the
+ * protected overloads here).
+ *
+ *
This is not enough in general to make the generated parser compilable,
+ * so an ant script does some cleanup at the end.
+ *
+ *
Additionally this must have a {@link AntlrNameDictionary} static final field,
+ * which stores the XPath names of the generated nodes (and terminals).
+ *
+ *
Additional members can be added to a parser with {@code @parser::members { ... }}
+ * in the g4 file.
+ */
+public abstract class AntlrGeneratedParserBase> extends Parser {
+
+ public AntlrGeneratedParserBase(TokenStream input) {
+ super(input);
+ }
+
+
+ @Override
+ public TerminalNode createTerminalNode(ParserRuleContext parent, Token t) {
+ return createPmdTerminal(parent, t).asAntlrNode();
+ }
+
+ @Override
+ public ErrorNode createErrorNode(ParserRuleContext parent, Token t) {
+ return createPmdError(parent, t).asAntlrNode();
+ }
+
+ // Those two need to return a node that implements eg SwiftNode
+
+ public abstract BaseAntlrTerminalNode createPmdTerminal(ParserRuleContext parent, Token t);
+
+ public abstract BaseAntlrErrorNode createPmdError(ParserRuleContext parent, Token t);
+
+
+ protected Node asPmdNode(RuleContext ctx) {
+ return ((BaseAntlrNode.AntlrToPmdParseTreeAdapter>) ctx).getPmdNode();
+ }
+
+ // Necessary API to build the trees
+
+ protected void enterRule(BaseAntlrInnerNode ptree, int state, int alt) {
+ enterRule(ptree.asAntlrNode(), state, alt);
+ }
+
+ protected void enterOuterAlt(BaseAntlrInnerNode localctx, int altNum) {
+ enterOuterAlt(localctx.asAntlrNode(), altNum);
+ }
+
+ protected void pushNewRecursionContext(BaseAntlrInnerNode localctx, int state, int ruleIndex) {
+ pushNewRecursionContext(localctx.asAntlrNode(), state, ruleIndex);
+ }
+
+ protected void enterRecursionRule(BaseAntlrInnerNode localctx, int state, int ruleIndex, int precedence) {
+ enterRecursionRule(localctx.asAntlrNode(), state, ruleIndex, precedence);
+
+ }
+
+ protected boolean sempred(BaseAntlrInnerNode localctx, int ruleIndex, int predIndex) {
+ return sempred(localctx.asAntlrNode(), ruleIndex, predIndex);
+ }
+
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNameDictionary.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNameDictionary.java
new file mode 100644
index 0000000000..1ca4c4f953
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNameDictionary.java
@@ -0,0 +1,219 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.ast.impl.antlr4;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.Vocabulary;
+import org.apache.commons.lang3.StringUtils;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Stores the XPath name of antlr terminals. I found no simple way to
+ * give names to punctuation (we could add a lexer rule, but it may
+ * conflict with other tokens). So their names are hardcoded here.
+ *
+ *
Terminal names start with {@code "T-"} in XPath to avoid conflicts
+ * with other stuff.
+ */
+public class AntlrNameDictionary {
+
+ private final String[] terminalXPathNames;
+ private final String[] terminalImages;
+ private final String[] nonTermXpathNames;
+ private final Vocabulary vocabulary;
+
+ public AntlrNameDictionary(Vocabulary vocab, String[] ruleNames) {
+ this.vocabulary = vocab;
+
+ nonTermXpathNames = new String[ruleNames.length];
+ for (int i = 0; i < nonTermXpathNames.length; i++) {
+ nonTermXpathNames[i] = StringUtils.capitalize(ruleNames[i]);
+ }
+ Set seen = new HashSet<>();
+ Collections.addAll(seen, ruleNames);
+
+ // terminal names
+ terminalXPathNames = new String[vocab.getMaxTokenType()];
+ terminalXPathNames[0] = "Invalid"; // See Token.INVALID_TYPE
+
+ terminalImages = new String[vocab.getMaxTokenType()];
+ terminalImages[0] = null;
+
+ for (int i = Token.MIN_USER_TOKEN_TYPE; i < terminalXPathNames.length; i++) {
+ String name = vocab.getSymbolicName(i);
+ String literalName = vocab.getLiteralName(i);
+
+ if (literalName != null) {
+ // cleanup literal name, Antlr surrounds the image with single quotes
+ literalName = literalName.substring(1, literalName.length() - 1);
+ terminalImages[i] = literalName;
+ }
+
+ if (name == null && literalName != null) {
+ name = literalName;
+ if (!name.matches("[a-zA-Z][\\w_-]+")) { // not alphanum
+ name = nonAlphaNumName(name);
+ } // otherwise something like "final"
+ }
+
+
+ assert name != null : "Token of kind " + i + " has no XPath name (literal " + vocab.getLiteralName(i) + ")";
+
+ String finalName = "T-" + name;
+
+ assert finalName.matches("[a-zA-Z][\\w_-]+") : "Not a valid XPath name " + finalName;
+ assert seen.add(finalName) : "Duplicate XPath name " + finalName;
+
+ terminalXPathNames[i] = finalName;
+ }
+
+
+ assert Stream.of(terminalXPathNames).distinct().count() == terminalXPathNames.length
+ : "Duplicate names in " + Arrays.toString(terminalXPathNames);
+ }
+
+ public Vocabulary getVocabulary() {
+ return vocabulary;
+ }
+
+ /**
+ * Override this to customize the XPath name of tokes with no symbolic
+ * name and with an image that is non-alphanumeric. Return null to give
+ * up. The default just gives some name to common punctuation. Remember
+ * that the same token may mean several things in different contexts, so
+ * eg using {@code "not"} as the name of {@code "!"} is too specific.
+ */
+ protected @Nullable String nonAlphaNumName(String name) {
+ switch (name) {
+ case "!": return "bang";
+ case "!!": return "double-bang";
+
+ case "?": return "question";
+ case "??": return "double-question";
+ case "?:": return "elvis";
+ case "?.": return "question-dot";
+
+ case ":": return "colon";
+ case ";": return "semi";
+ case ",": return "comma";
+
+ case "(": return "lparen";
+ case ")": return "rparen";
+ case "[": return "lbracket";
+ case "]": return "rbracket";
+ case "{": return "lbrace";
+ case "}": return "rbrace";
+
+ case "_": return "underscore";
+
+ case ".": return "dot";
+ case "..": return "double-dot";
+ case "...": return "ellipsis";
+
+ case "@": return "at-symbol";
+ case "$": return "dollar";
+
+ case "\\": return "backslash";
+ case "/": return "slash";
+ case "//": return "double-slash";
+ case "`": return "backtick";
+ case "'": return "squote";
+ case "\"": return "dquote";
+ case "\"\"\"": return "triple-quote";
+
+ case ">": return "gt";
+ case ">=": return "ge";
+ case "<": return "lt";
+ case "<=": return "le";
+
+ case ">>": return "double-gt";
+ case "<<": return "double-lt";
+ case ">>>": return "triple-gt";
+ case "<<<": return "triple-lt";
+
+ case "=": return "eq";
+ case "==": return "double-eq";
+ case "===": return "triple-eq";
+ case "!=": return "not-eq";
+
+ case "&": return "amp";
+ case "&&": return "double-amp";
+ case "|": return "pipe";
+ case "||": return "double-pipe";
+
+ case "*": return "star";
+ case "**": return "double-star";
+
+ case "+": return "plus";
+ case "++": return "double-plus";
+ case "-": return "minus";
+ case "--": return "double-minus";
+
+ case "->": return "rarrow";
+ case "<-": return "larrow";
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Gets the xpath name of a terminal node with a given {@link Token#getType()}.
+ *
+ * @throws IllegalArgumentException If the index is invalid
+ */
+ public @NonNull String getXPathNameOfToken(int tokenType) {
+ if (tokenType >= 0 && tokenType < terminalXPathNames.length) {
+ return terminalXPathNames[tokenType];
+ }
+
+ if (tokenType == Token.EOF) {
+ return "EOF";
+ }
+
+ throw new IllegalArgumentException("I don't know token type " + tokenType);
+ }
+
+ /**
+ * Returns the constant image of the given token (a shared string),
+ * or null if the token has none. This is a memory optimization to
+ * avoid creating a new string for tokens with constant images. Antlr
+ * does not do this by itself sadly.
+ */
+ public @Nullable String getConstantImageOfToken(Token token) {
+ int tokenType = token.getType();
+ if (tokenType >= 0 && tokenType < terminalXPathNames.length) {
+ return terminalImages[tokenType];
+ } else if (token.getStartIndex() == token.getStopIndex()) {
+ return "";
+ }
+ return null;
+ }
+
+ /**
+ * Gets the xpath name of an inner node with a given {@link ParserRuleContext#getRuleIndex()}.
+ *
+ * @throws IndexOutOfBoundsException If the index is invalid
+ */
+ public @NonNull String getXPathNameOfRule(int idx) {
+ return nonTermXpathNames[idx];
+ }
+
+ public int getMaxRuleIndex() {
+ return nonTermXpathNames.length;
+ }
+
+ public int getMaxTokenType() {
+ return vocabulary.getMaxTokenType();
+ }
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNode.java
index 840495b068..e3f81f77a9 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNode.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNode.java
@@ -4,37 +4,11 @@
package net.sourceforge.pmd.lang.ast.impl.antlr4;
-import org.antlr.v4.runtime.tree.ParseTree;
-
-import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.NodeStream;
+import net.sourceforge.pmd.lang.ast.impl.GenericNode;
/**
- * Base interface for all Antlr-based implementation of Node interface.
- *
- * Initially all the methods implemented here will be no-op due to scope limitations
+ * Base interface for all Antlr-based implementation of the Node interface.
*/
-public interface AntlrNode extends Node, ParseTree {
-
-
- @Override
- AntlrNode getChild(int index);
-
-
- @Override
- AntlrNode getParent();
-
-
- @Override
- @SuppressWarnings("unchecked")
- default NodeStream extends AntlrNode> children() {
- return (NodeStream extends AntlrNode>) Node.super.children();
- }
-
- @Override
- default int getIndexInParent() {
- // FIXME, relied on by node streams
- throw new UnsupportedOperationException("Out of scope for antlr current implementations");
- }
+public interface AntlrNode> extends GenericNode {
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrRuleChainVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrRuleChainVisitor.java
index 720d1db0db..ed5d7fa710 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrRuleChainVisitor.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrRuleChainVisitor.java
@@ -4,24 +4,19 @@
package net.sourceforge.pmd.lang.ast.impl.antlr4;
+import java.util.Collections;
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;
public class AntlrRuleChainVisitor extends AbstractRuleChainVisitor {
@Override
protected void visit(Rule rule, Node node, RuleContext ctx) {
- if (rule instanceof AntlrBaseRule) {
- AntlrBaseRule rule1 = (AntlrBaseRule) rule;
- ((AntlrBaseNode) node).accept(rule1.buildVisitor(ctx));
- } else {
- ((XPathRule) rule).evaluate(node, ctx);
- }
+ rule.apply(Collections.singletonList(node), ctx);
}
@Override
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrToken.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrToken.java
index e990fa1ada..cb60813252 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrToken.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrToken.java
@@ -4,6 +4,9 @@
package net.sourceforge.pmd.lang.ast.impl.antlr4;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.Token;
@@ -14,14 +17,23 @@ import net.sourceforge.pmd.lang.ast.GenericToken;
*/
public class AntlrToken implements GenericToken {
+ private static final Pattern NEWLINE_PATTERN =
+ // \R on java 8+
+ Pattern.compile("\\u000D\\u000A|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029]");
+
private final Token token;
private final AntlrToken previousComment;
AntlrToken next;
+ private String text;
+
+ private int endline;
+ private int endcolumn;
+
/**
* Constructor
*
- * @param token The antlr token implementation
+ * @param token The antlr token implementation
* @param previousComment The previous comment
*/
public AntlrToken(final Token token, final AntlrToken previousComment) {
@@ -41,7 +53,10 @@ public class AntlrToken implements GenericToken {
@Override
public String getImage() {
- return token.getText();
+ if (text == null) {
+ text = token.getText();
+ }
+ return text;
}
@Override
@@ -55,18 +70,71 @@ public class AntlrToken implements GenericToken {
}
@Override
- public int getEndLine() {
- return token.getLine();
+ public int getBeginColumn() {
+ int charPos = token.getCharPositionInLine() + 1;
+ assert charPos > 0;
+ return charPos;
}
+
@Override
- public int getBeginColumn() {
- return token.getCharPositionInLine();
+ public int getEndLine() {
+ if (endline == 0) {
+ computeEndCoords();
+ assert endline > 0;
+ }
+ return endline;
}
@Override
public int getEndColumn() {
- return token.getCharPositionInLine() + token.getStopIndex() - token.getStartIndex();
+ if (endcolumn == 0) {
+ computeEndCoords();
+ assert endcolumn > 0;
+ }
+ return endcolumn;
+ }
+
+ private void computeEndCoords() {
+ String image = getImage();
+ if (image.length() == 1) {
+ // fast path for single char tokens
+ if (image.charAt(0) != '\n') {
+ this.endline = getBeginLine();
+ this.endcolumn = getBeginColumn() + 1;
+ return;
+ }
+ }
+
+ Matcher matcher = NEWLINE_PATTERN.matcher(image);
+ int numNls = 0;
+ int lastOffset = 0;
+ int lastLineLen = -1;
+ while (matcher.find()) {
+ // continue
+ numNls++;
+ if (lastLineLen < 0) {
+ // first iteration, line may not be completely in the image
+ lastLineLen = token.getCharPositionInLine() + matcher.end();
+ } else {
+ lastLineLen = matcher.end() - lastOffset;
+ }
+ lastOffset = matcher.end();
+ }
+
+ if (numNls == 0) {
+ // single line token
+ this.endline = this.getBeginLine();
+ int length = 1 + token.getStopIndex() - token.getStartIndex();
+ this.endcolumn = token.getCharPositionInLine() + length + 1;
+ } else if (lastOffset < image.length()) {
+ this.endline = this.getBeginLine() + numNls;
+ this.endcolumn = image.length() - lastOffset + 1;
+ } else {
+ // ends with a newline, the newline is considered part of the previous line
+ this.endline = this.getBeginLine() + numNls - 1;
+ this.endcolumn = lastLineLen + 1;
+ }
}
public int getKind() {
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrErrorNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrErrorNode.java
new file mode 100644
index 0000000000..33babb7eac
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrErrorNode.java
@@ -0,0 +1,31 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.ast.impl.antlr4;
+
+import org.antlr.v4.runtime.Token;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public abstract class BaseAntlrErrorNode> extends BaseAntlrTerminalNode {
+
+ protected BaseAntlrErrorNode(Token symbol) {
+ super(symbol, true);
+ }
+
+ @Override
+ protected final AntlrErrorPmdAdapter asAntlrNode() {
+ return (AntlrErrorPmdAdapter) super.asAntlrNode();
+ }
+
+
+ @Override
+ public @NonNull String getText() {
+ return getFirstAntlrToken().getText();
+ }
+
+ @Override
+ public final String getXPathNodeName() {
+ return "Error";
+ }
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrInnerNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrInnerNode.java
new file mode 100644
index 0000000000..4daab3ed6e
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrInnerNode.java
@@ -0,0 +1,154 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.ast.impl.antlr4;
+
+import java.util.List;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.RuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeListener;
+import org.antlr.v4.runtime.tree.ParseTreeVisitor;
+import org.antlr.v4.runtime.tree.RuleNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import net.sourceforge.pmd.lang.ast.impl.antlr4.BaseAntlrInnerNode.PmdAsAntlrInnerNode;
+
+/**
+ * Base class for the inner nodes (corresponds to {@link ParserRuleContext}).
+ * Use the {@code contextSuperClass} option to set this in the antlr g4 file,
+ * eg {@code options { contextSuperClass = SwiftInnerNode; }}.
+ */
+public abstract class BaseAntlrInnerNode> extends BaseAntlrNode, N> {
+
+ public RecognitionException exception;
+
+ private final PmdAsAntlrInnerNode antlrNode;
+
+ protected BaseAntlrInnerNode() {
+ antlrNode = new PmdAsAntlrInnerNode<>(this);
+ }
+
+ protected BaseAntlrInnerNode(ParserRuleContext parent, int invokingStateNumber) {
+ antlrNode = new PmdAsAntlrInnerNode<>(this, (PmdAsAntlrInnerNode) parent, invokingStateNumber);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public N getChild(int index) {
+ if (0 <= index && index < getNumChildren()) {
+ N pmdNode = (N) antlrNode.getChild(index).getPmdNode();
+ assert pmdNode.getIndexInParent() == index;
+ return pmdNode;
+ }
+ throw new IndexOutOfBoundsException("Index " + index + ", numChildren " + getNumChildren());
+ }
+
+ @Override
+ public int getNumChildren() {
+ return antlrNode.getChildCount();
+ }
+
+ @Override
+ protected PmdAsAntlrInnerNode asAntlrNode() {
+ return antlrNode;
+ }
+
+ protected abstract int getRuleIndex();
+
+
+ @Override
+ public Token getFirstAntlrToken() {
+ return asAntlrNode().start;
+ }
+
+ @Override
+ public Token getLastAntlrToken() {
+ return asAntlrNode().stop;
+ }
+
+ protected > T getRuleContext(Class klass, int idx) {
+ return children(klass).get(idx);
+ }
+
+ protected > List getRuleContexts(Class klass) {
+ return children(klass).toList();
+ }
+
+ protected TerminalNode getToken(int kind, int idx) {
+ @SuppressWarnings("rawtypes")
+ BaseAntlrTerminalNode pmdWrapper =
+ children(BaseAntlrTerminalNode.class)
+ .filter(it -> it.getTokenKind() == kind)
+ .get(idx);
+ return pmdWrapper != null ? pmdWrapper.asAntlrNode() : null;
+ }
+
+ protected void copyFrom(BaseAntlrInnerNode other) {
+ asAntlrNode().copyFrom(other.asAntlrNode());
+ }
+
+
+ public void enterRule(ParseTreeListener listener) {
+ // default does nothing
+ }
+
+
+ public void exitRule(ParseTreeListener listener) {
+ // default does nothing
+ }
+
+ protected static class PmdAsAntlrInnerNode> extends ParserRuleContext implements RuleNode, AntlrToPmdParseTreeAdapter {
+
+ private final BaseAntlrInnerNode pmdNode;
+
+ PmdAsAntlrInnerNode(BaseAntlrInnerNode node) {
+ this.pmdNode = node;
+ }
+
+ PmdAsAntlrInnerNode(BaseAntlrInnerNode node, PmdAsAntlrInnerNode parent, int invokingStateNumber) {
+ super(parent, invokingStateNumber);
+ this.pmdNode = node;
+ }
+
+ @Override
+ public BaseAntlrInnerNode getPmdNode() {
+ return pmdNode;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public PmdAsAntlrInnerNode getParent() {
+ return (PmdAsAntlrInnerNode) super.getParent();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public AntlrToPmdParseTreeAdapter getChild(int i) {
+ return (AntlrToPmdParseTreeAdapter) super.getChild(i);
+ }
+
+ @Override
+ public T addAnyChild(T t) {
+ assert t instanceof AntlrToPmdParseTreeAdapter;
+ BaseAntlrNode, ?> pmdNode = ((AntlrToPmdParseTreeAdapter>) t).getPmdNode();
+ pmdNode.setIndexInParent(getChildCount());
+ return super.addAnyChild(t);
+ }
+
+ @Override
+ public void setParent(RuleContext parent) {
+ assert parent instanceof PmdAsAntlrInnerNode;
+ super.setParent(parent);
+ }
+
+ @Override
+ public T accept(ParseTreeVisitor extends T> visitor) {
+ throw new UnsupportedOperationException("Cannot visit the underlying antlr nodes");
+ }
+ }
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrNode.java
new file mode 100644
index 0000000000..be9147822c
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/BaseAntlrNode.java
@@ -0,0 +1,123 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.ast.impl.antlr4;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.ErrorNode;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import net.sourceforge.pmd.lang.ast.impl.GenericNode;
+import net.sourceforge.pmd.lang.ast.impl.antlr4.BaseAntlrNode.AntlrToPmdParseTreeAdapter;
+import net.sourceforge.pmd.util.DataMap;
+import net.sourceforge.pmd.util.DataMap.DataKey;
+
+/**
+ * Base class for an antlr node. This implements the PMD interfaces only,
+ * not the antlr ones. It wraps an antlr node (they are linked both ways).
+ * Antlr primarily distinguishes {@link ParserRuleContext} for inner nodes,
+ * {@link TerminalNode} for nodes that wrap tokens (and can have no children),
+ * and {@link ErrorNode}, a subtype of {@link TerminalNode}. These each have
+ * a base class here, which refines the type of the underlying antlr node:
+ * {@link BaseAntlrInnerNode}, {@link BaseAntlrTerminalNode} and {@link BaseAntlrErrorNode}.
+ * These must be implemented in each language module with a class that also
+ * implements {@code }.
+ *
+ *