Merge branch 'pr-2280'
[cs] CPD: Replace C# tokenizer by an Antlr-based one
This commit is contained in:
commit
90f286a4a1
8 files changed
+1567
-283
No files matched your search
@@ -24,6 +24,12 @@ For the changes, see [PMD Designer Changelog](https://github.com/pmd/pmd-designe
|
||||
In addition to suppressing violation with the `@SuppressWarnings` annotation, Apex now also supports
|
||||
the suppressions with a `NOPMD` comment. See [Suppressing warnings](pmd_userdocs_suppressing_warnings.html).
|
||||
|
||||
#### Improved CPD support for C#
|
||||
|
||||
The C# tokenizer is now based on an antlr grammar instead of a manual written tokenizer. This
|
||||
should give more accurate results and especially fixes the problems with the using statement syntax
|
||||
(see [#2139](https://github.com/pmd/pmd/issues/2139)).
|
||||
|
||||
#### New Rules
|
||||
|
||||
* The Rule {% rule "apex/design/CognitiveComplexity" %} (`apex-design`) finds methods and classes
|
||||
@@ -38,6 +44,8 @@ the suppressions with a `NOPMD` comment. See [Suppressing warnings](pmd_userdocs
|
||||
* [#2306](https://github.com/pmd/pmd/issues/2306): \[apex] Switch statements are not parsed/supported
|
||||
* apex-design
|
||||
* [#2162](https://github.com/pmd/pmd/issues/2162): \[apex] Cognitive Complexity rule
|
||||
* cs
|
||||
* [#2139](https://github.com/pmd/pmd/issues/2139): \[cs] CPD doesn't understand alternate using statement syntax with C# 8.0
|
||||
* doc
|
||||
* [#2274](https://github.com/pmd/pmd/issues/2274): \[doc] Java API documentation for PMD
|
||||
* java
|
||||
@@ -138,6 +146,7 @@ methods on {% jdoc apex::lang.apex.ast.ApexParserVisitor %} and its implementati
|
||||
* [#2276](https://github.com/pmd/pmd/pull/2276): \[java] AppendCharacterWithCharRule ignore literals in expressions - [Kris Scheibe](https://github.com/kris-scheibe)
|
||||
* [#2278](https://github.com/pmd/pmd/pull/2278): \[java] fix UnusedImports rule for ambiguous static on-demand imports - [Kris Scheibe](https://github.com/kris-scheibe)
|
||||
* [#2279](https://github.com/pmd/pmd/pull/2279): \[apex] Add support for suppressing violations using the // NOPMD comment - [Gwilym Kuiper](https://github.com/gwilymatgearset)
|
||||
* [#2280](https://github.com/pmd/pmd/pull/2280): \[cs] CPD: Replace C# tokenizer by an Antlr-based one - [Maikel Steneker](https://github.com/maikelsteneker)
|
||||
* [#2297](https://github.com/pmd/pmd/pull/2297): \[apex] Cognitive complexity metrics - [Gwilym Kuiper](https://github.com/gwilymatgearset)
|
||||
|
||||
{% endtocmaker %}
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
package net.sourceforge.pmd.cpd.token.internal;
|
||||
|
||||
import static net.sourceforge.pmd.internal.util.IteratorUtil.AbstractIterator;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import net.sourceforge.pmd.cpd.token.TokenFilter;
|
||||
import net.sourceforge.pmd.lang.TokenManager;
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
@@ -15,7 +21,10 @@ import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFilter {
|
||||
|
||||
private final TokenManager tokenManager;
|
||||
private final LinkedList<T> unprocessedTokens; // NOPMD - used both as Queue and List
|
||||
private final Iterable<T> remainingTokens;
|
||||
private boolean discardingSuppressing;
|
||||
private T currentToken;
|
||||
|
||||
/**
|
||||
* Creates a new BaseTokenFilter
|
||||
@@ -23,13 +32,21 @@ public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFi
|
||||
*/
|
||||
public BaseTokenFilter(final TokenManager tokenManager) {
|
||||
this.tokenManager = tokenManager;
|
||||
this.unprocessedTokens = new LinkedList<>();
|
||||
this.remainingTokens = new RemainingTokens();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T getNextToken() {
|
||||
T currentToken = (T) tokenManager.getNextToken();
|
||||
currentToken = null;
|
||||
if (!unprocessedTokens.isEmpty()) {
|
||||
currentToken = unprocessedTokens.poll();
|
||||
return currentToken;
|
||||
}
|
||||
currentToken = (T) tokenManager.getNextToken();
|
||||
while (!shouldStopProcessing(currentToken)) {
|
||||
analyzeToken(currentToken);
|
||||
analyzeTokens(currentToken, remainingTokens);
|
||||
processCPDSuppression(currentToken);
|
||||
|
||||
if (!isDiscarding()) {
|
||||
@@ -73,6 +90,18 @@ public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFi
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to analyze all tokens (before filtering)
|
||||
* and update internal status to decide on custom discard rules.
|
||||
*
|
||||
* @param currentToken The token to be analyzed
|
||||
* @param remainingTokens All upcoming tokens
|
||||
* @see #isLanguageSpecificDiscarding()
|
||||
*/
|
||||
protected void analyzeTokens(final T currentToken, final Iterable<T> remainingTokens) {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to indicate tokens are to be filtered.
|
||||
*
|
||||
@@ -90,4 +119,43 @@ public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFi
|
||||
*/
|
||||
protected abstract boolean shouldStopProcessing(T currentToken);
|
||||
|
||||
private class RemainingTokens implements Iterable<T> {
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new RemainingTokensIterator(currentToken);
|
||||
}
|
||||
|
||||
private class RemainingTokensIterator extends AbstractIterator<T> implements Iterator<T> {
|
||||
|
||||
int index = 0; // index of next element
|
||||
T startToken;
|
||||
|
||||
RemainingTokensIterator(final T startToken) {
|
||||
this.startToken = startToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeNext() {
|
||||
assert index >= 0;
|
||||
if (startToken != currentToken) { // NOPMD - intentional check for reference equality
|
||||
throw new ConcurrentModificationException("Using iterator after next token has been requested.");
|
||||
}
|
||||
if (index < unprocessedTokens.size()) {
|
||||
setNext(unprocessedTokens.get(index++));
|
||||
} else {
|
||||
final T nextToken = (T) tokenManager.getNextToken();
|
||||
if (shouldStopProcessing(nextToken)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
unprocessedTokens.add(nextToken);
|
||||
setNext(nextToken);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
|
||||
/**
|
||||
@@ -88,4 +89,59 @@ public final class IteratorUtil {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public abstract static class AbstractIterator<T> implements Iterator<T> {
|
||||
|
||||
private State state = State.NOT_READY;
|
||||
private T next = null;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
switch (state) {
|
||||
case DONE:
|
||||
return false;
|
||||
case READY:
|
||||
return true;
|
||||
default:
|
||||
state = null;
|
||||
computeNext();
|
||||
if (state == null) {
|
||||
throw new IllegalStateException("Should have called done or setNext");
|
||||
}
|
||||
return state == State.READY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
state = State.NOT_READY;
|
||||
return next;
|
||||
}
|
||||
|
||||
protected final void setNext(T t) {
|
||||
next = t;
|
||||
state = State.READY;
|
||||
}
|
||||
|
||||
protected final void done() {
|
||||
state = State.DONE;
|
||||
}
|
||||
|
||||
protected abstract void computeNext();
|
||||
|
||||
enum State {
|
||||
READY, NOT_READY, DONE
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public final void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.cpd.token.internal;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.TokenManager;
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
|
||||
public class BaseTokenFilterTest {
|
||||
|
||||
class StringToken implements GenericToken {
|
||||
|
||||
private final String text;
|
||||
|
||||
StringToken(final String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericToken getNext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericToken getPreviousComment() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImage() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBeginLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBeginColumn() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndColumn() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class StringTokenManager implements TokenManager {
|
||||
|
||||
Iterator<String> iterator = Collections.unmodifiableList(Arrays.asList("a", "b", "c")).iterator();
|
||||
|
||||
@Override
|
||||
public Object getNextToken() {
|
||||
if (iterator.hasNext()) {
|
||||
return new StringToken(iterator.next());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFileName(final String fileName) {
|
||||
}
|
||||
}
|
||||
|
||||
class DummyTokenFilter<T extends GenericToken> extends BaseTokenFilter<T> {
|
||||
|
||||
Iterable<T> remainingTokens;
|
||||
|
||||
DummyTokenFilter(final TokenManager tokenManager) {
|
||||
super(tokenManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldStopProcessing(final T currentToken) {
|
||||
return currentToken == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void analyzeTokens(final T currentToken, final Iterable<T> remainingTokens) {
|
||||
this.remainingTokens = remainingTokens;
|
||||
}
|
||||
|
||||
public Iterable getRemainingTokens() {
|
||||
return remainingTokens;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemainingTokensFunctionality1() {
|
||||
final TokenManager tokenManager = new StringTokenManager();
|
||||
final DummyTokenFilter tokenFilter = new DummyTokenFilter(tokenManager);
|
||||
final GenericToken firstToken = tokenFilter.getNextToken();
|
||||
assertEquals("a", firstToken.getImage());
|
||||
final Iterable<StringToken> iterable = tokenFilter.getRemainingTokens();
|
||||
final Iterator it1 = iterable.iterator();
|
||||
final Iterator it2 = iterable.iterator();
|
||||
assertTrue(it1.hasNext());
|
||||
assertTrue(it2.hasNext());
|
||||
final StringToken firstValFirstIt = (StringToken) it1.next();
|
||||
final StringToken firstValSecondIt = (StringToken) it2.next();
|
||||
assertTrue(it1.hasNext());
|
||||
assertTrue(it2.hasNext());
|
||||
final StringToken secondValFirstIt = (StringToken) it1.next();
|
||||
assertFalse(it1.hasNext());
|
||||
assertTrue(it2.hasNext());
|
||||
final StringToken secondValSecondIt = (StringToken) it2.next();
|
||||
assertFalse(it2.hasNext());
|
||||
assertEquals("b", firstValFirstIt.getImage());
|
||||
assertEquals("b", firstValSecondIt.getImage());
|
||||
assertEquals("c", secondValFirstIt.getImage());
|
||||
assertEquals("c", secondValSecondIt.getImage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemainingTokensFunctionality2() {
|
||||
final TokenManager tokenManager = new StringTokenManager();
|
||||
final DummyTokenFilter tokenFilter = new DummyTokenFilter(tokenManager);
|
||||
final GenericToken firstToken = tokenFilter.getNextToken();
|
||||
assertEquals("a", firstToken.getImage());
|
||||
final Iterable<StringToken> iterable = tokenFilter.getRemainingTokens();
|
||||
final Iterator it1 = iterable.iterator();
|
||||
final Iterator it2 = iterable.iterator();
|
||||
assertTrue(it1.hasNext());
|
||||
assertTrue(it2.hasNext());
|
||||
final StringToken firstValFirstIt = (StringToken) it1.next();
|
||||
assertTrue(it1.hasNext());
|
||||
final StringToken secondValFirstIt = (StringToken) it1.next();
|
||||
assertFalse(it1.hasNext());
|
||||
assertTrue(it2.hasNext());
|
||||
final StringToken firstValSecondIt = (StringToken) it2.next();
|
||||
assertTrue(it2.hasNext());
|
||||
final StringToken secondValSecondIt = (StringToken) it2.next();
|
||||
assertFalse(it2.hasNext());
|
||||
assertEquals("b", firstValFirstIt.getImage());
|
||||
assertEquals("b", firstValSecondIt.getImage());
|
||||
assertEquals("c", secondValFirstIt.getImage());
|
||||
assertEquals("c", secondValSecondIt.getImage());
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void testRemainingTokensFunctionality3() {
|
||||
final TokenManager tokenManager = new StringTokenManager();
|
||||
final DummyTokenFilter tokenFilter = new DummyTokenFilter(tokenManager);
|
||||
final GenericToken firstToken = tokenFilter.getNextToken();
|
||||
assertEquals("a", firstToken.getImage());
|
||||
final Iterable<StringToken> iterable = tokenFilter.getRemainingTokens();
|
||||
final Iterator it1 = iterable.iterator();
|
||||
final Iterator it2 = iterable.iterator();
|
||||
it1.next();
|
||||
it1.next();
|
||||
it2.next();
|
||||
it2.next();
|
||||
it1.next();
|
||||
}
|
||||
|
||||
@Test(expected = ConcurrentModificationException.class)
|
||||
public void testRemainingTokensFunctionality4() {
|
||||
final TokenManager tokenManager = new StringTokenManager();
|
||||
final DummyTokenFilter tokenFilter = new DummyTokenFilter(tokenManager);
|
||||
final GenericToken firstToken = tokenFilter.getNextToken();
|
||||
assertEquals("a", firstToken.getImage());
|
||||
final Iterable<StringToken> iterable = tokenFilter.getRemainingTokens();
|
||||
final Iterator it1 = iterable.iterator();
|
||||
final GenericToken secondToken = tokenFilter.getNextToken();
|
||||
assertEquals("b", secondToken.getImage());
|
||||
it1.next();
|
||||
}
|
||||
|
||||
}
|
||||
+6
-4
@@ -12,6 +12,11 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<configuration>
|
||||
@@ -23,6 +28,7 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.pmd</groupId>
|
||||
@@ -32,10 +38,6 @@
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -12,6 +12,8 @@ import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.TokenMgrError;
|
||||
|
||||
public class CsTokenizerTest {
|
||||
|
||||
private CsTokenizer tokenizer;
|
||||
@@ -46,7 +48,7 @@ public class CsTokenizerTest {
|
||||
public void testSimpleClassMethodMultipleLines() {
|
||||
tokenizer.tokenize(toSourceCode("class Foo {\n" + " public String foo(int a) {\n" + " int i = a;\n"
|
||||
+ " return \"x\" + a;\n" + " }\n" + "}"), tokens);
|
||||
assertEquals(22, tokens.size());
|
||||
assertEquals(24, tokens.size());
|
||||
List<TokenEntry> tokenList = tokens.getTokens();
|
||||
assertEquals(1, tokenList.get(0).getBeginLine());
|
||||
assertEquals(2, tokenList.get(4).getBeginLine());
|
||||
@@ -56,13 +58,12 @@ public class CsTokenizerTest {
|
||||
@Test
|
||||
public void testStrings() {
|
||||
tokenizer.tokenize(toSourceCode("String s =\"aaa \\\"b\\n\";"), tokens);
|
||||
assertEquals(5, tokens.size());
|
||||
assertEquals(6, tokens.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expected = TokenMgrError.class)
|
||||
public void testOpenString() {
|
||||
tokenizer.tokenize(toSourceCode("String s =\"aaa \\\"b\\"), tokens);
|
||||
assertEquals(5, tokens.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,7 +92,7 @@ public class CsTokenizerTest {
|
||||
+ " a++; \n" + " a /= 3e2; \n" + " float f = -3.1; \n" + " f *= 2; \n"
|
||||
+ " bool b = ! (f == 2.0 || f >= 1.0 && f <= 2.0) \n" + " }\n" + "}"),
|
||||
tokens);
|
||||
assertEquals(50, tokens.size());
|
||||
assertEquals(57, tokens.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -119,8 +120,9 @@ public class CsTokenizerTest {
|
||||
public void testIgnoreUsingDirectives() {
|
||||
tokenizer.setIgnoreUsings(true);
|
||||
tokenizer.tokenize(toSourceCode("using System.Text;\n"), tokens);
|
||||
assertEquals(1, tokens.size());
|
||||
assertNotEquals("using", tokens.getTokens().get(0).toString());
|
||||
assertEquals(2, tokens.size());
|
||||
assertEquals(TokenEntry.EOF, tokens.getTokens().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -132,6 +134,15 @@ public class CsTokenizerTest {
|
||||
assertEquals("using", tokens.getTokens().get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsingVarStatementsAreNotIgnored() {
|
||||
tokenizer.setIgnoreUsings(true);
|
||||
tokenizer.tokenize(toSourceCode(
|
||||
"using var font1 = new Font(\"Arial\", 10.0f);\n" + " byte charset = font1.GdiCharSet;\n"),
|
||||
tokens);
|
||||
assertEquals("using", tokens.getTokens().get(0).toString());
|
||||
}
|
||||
|
||||
private SourceCode toSourceCode(String source) {
|
||||
return new SourceCode(new SourceCode.StringCodeLoader(source));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user