fix some tests

This commit is contained in:
Clément Fournier
2022-07-21 03:38:25 +02:00
parent 1d1e1e9c8b
commit 3d5c2d1f70
12 changed files with 191 additions and 163 deletions

View File

@ -8,9 +8,9 @@ import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageProcessor;
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.impl.LanguageModuleWithOneVersion;
import net.sourceforge.pmd.lang.impl.LanguageModuleBase;
public class ApexLanguageModule extends LanguageModuleWithOneVersion {
public class ApexLanguageModule extends LanguageModuleBase {
public static final String NAME = "Apex";
public static final String TERSE_NAME = "apex";

View File

@ -273,7 +273,8 @@ public final class PmdAnalysis implements AutoCloseable {
*/
public LanguagePropertyBundle getLanguageProperties(Language language) {
if (!configuration.languages().getLanguages().contains(language)) {
throw new IllegalArgumentException(language.getId());
throw new IllegalArgumentException(
"Language '" + language.getId() + "' is not registered in " + configuration.languages());
}
return langProperties.computeIfAbsent(language, Language::newPropertyBundle);
}

View File

@ -7,8 +7,6 @@ package net.sourceforge.pmd.lang;
import java.util.List;
import java.util.ServiceLoader;
import net.sourceforge.pmd.processor.BatchLanguageProcessor;
/**
* Represents a language module, and provides access to language-specific
* functionality. You can get a language instance from {@link LanguageRegistry}.
@ -146,14 +144,6 @@ public interface Language extends Comparable<Language> {
return new LanguagePropertyBundle(this);
}
default LanguageProcessor createProcessor(LanguagePropertyBundle bundle) {
return new BatchLanguageProcessor<LanguagePropertyBundle>(bundle) {
@Override
public LanguageVersionHandler services() {
return bundle.getLanguageVersion().getLanguageVersionHandler();
}
};
}
LanguageProcessor createProcessor(LanguagePropertyBundle bundle);
}

View File

@ -5,6 +5,7 @@
package net.sourceforge.pmd.lang;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@ -57,6 +58,10 @@ public final class LanguageRegistry implements Iterable<Language> {
this.languagesByFullName = CollectionUtil.associateBy(languages, Language::getName);
}
public static LanguageRegistry singleton(Language l) {
return new LanguageRegistry(Collections.singleton(l));
}
@Override
public @NonNull Iterator<Language> iterator() {
return languages.iterator();
@ -202,5 +207,8 @@ public final class LanguageRegistry implements Iterable<Language> {
return getLanguages().stream().map(languageToString).sorted().collect(Collectors.joining(", "));
}
@Override
public String toString() {
return "LanguageRegistry(" + commaSeparatedList(Language::getId) + ")";
}
}

View File

@ -5,9 +5,10 @@
package net.sourceforge.pmd.lang.document;
import net.sourceforge.pmd.cpd.SourceCode;
import net.sourceforge.pmd.lang.BaseLanguageModule;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.impl.LanguageModuleBase.LanguageMetadata;
import net.sourceforge.pmd.lang.impl.SimpleLanguageModuleBase;
/**
* Compatibility APIs, to be removed before PMD 7 is out.
@ -22,14 +23,11 @@ public final class CpdCompat {
/** The language version must be non-null. */
@Deprecated
private static final Language DUMMY_LANG = new BaseLanguageModule("dummy", "dummy", "dummy", "dummy") {
{
addDefaultVersion("", () -> task -> {
throw new UnsupportedOperationException();
});
}
};
private static final Language DUMMY_LANG =
new SimpleLanguageModuleBase(LanguageMetadata.withId("dummy").name("dummy").extensions("dummy"),
props -> () -> task -> {
throw new UnsupportedOperationException();
});
@Deprecated
public static LanguageVersion dummyVersion() {

View File

@ -6,13 +6,19 @@ package net.sourceforge.pmd.lang.impl;
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.impl.LanguageModuleBase.LanguageMetadata.LangVersionMetadata;
/**
* The simplest implementation of a language with only a few versions,
@ -24,8 +30,72 @@ public abstract class LanguageModuleBase implements Language {
private final LanguageMetadata meta;
private final List<LanguageVersion> distinctVersions;
private final Map<String, LanguageVersion> byName;
private final LanguageVersion defaultVersion;
protected LanguageModuleBase(LanguageMetadata metadata) {
this.meta = metadata;
metadata.validate();
List<LanguageVersion> versions = new ArrayList<>();
Map<String, LanguageVersion> byName = new HashMap<>();
LanguageVersion defaultVersion = null;
if (metadata.versionMetadata.isEmpty()) {
// Many languages have just one version, which is implicitly
// created here.
metadata.versionMetadata.add(new LangVersionMetadata("", Collections.emptyList(), true));
}
for (LanguageMetadata.LangVersionMetadata versionId : metadata.versionMetadata) {
String versionStr = versionId.name;
LanguageVersion languageVersion = new LanguageVersion(this, versionStr, null);
versions.add(languageVersion);
checkNotPresent(byName, versionStr);
byName.put(versionStr, languageVersion);
for (String alias : versionId.aliases) {
checkNotPresent(byName, alias);
byName.put(alias, languageVersion);
}
if (versionId.isDefault) {
if (defaultVersion != null) {
throw new IllegalStateException(
"Default version already set to " + defaultVersion + ", cannot set it to " + languageVersion);
}
defaultVersion = languageVersion;
}
}
this.byName = Collections.unmodifiableMap(byName);
this.distinctVersions = Collections.unmodifiableList(versions);
this.defaultVersion = Objects.requireNonNull(defaultVersion, "No default version for " + getId());
}
private static void checkNotPresent(Map<String, ?> map, String alias) {
if (map.containsKey(alias)) {
throw new IllegalArgumentException("Version key '" + alias + "' is duplicated");
}
}
@Override
public List<LanguageVersion> getVersions() {
return distinctVersions;
}
@Override
public LanguageVersion getDefaultVersion() {
return defaultVersion;
}
@Override
public LanguageVersion getVersion(String version) {
return byName.get(version);
}
@Override
@ -78,11 +148,12 @@ public abstract class LanguageModuleBase implements Language {
return Objects.equals(getId(), other.getId());
}
protected static final class LanguageMetadata {
public static final class LanguageMetadata {
private String name;
private String shortName;
private final String id;
private List<String> extensions;
private final List<LangVersionMetadata> versionMetadata = new ArrayList<>();
public LanguageMetadata(String id) {
this.id = id;
@ -113,9 +184,31 @@ public abstract class LanguageModuleBase implements Language {
}
public LanguageMetadata extensions(String e1, String... others) {
this.extensions = listOf(e1,others);
this.extensions = listOf(e1, others);
return this;
}
public LanguageMetadata addVersion(String name, String... aliases) {
versionMetadata.add(new LangVersionMetadata(name, Arrays.asList(aliases), false));
return this;
}
public LanguageMetadata addDefaultVersion(String name, String... aliases) {
versionMetadata.add(new LangVersionMetadata(name, Arrays.asList(aliases), true));
return this;
}
static final class LangVersionMetadata {
final String name;
final List<String> aliases;
final boolean isDefault;
private LangVersionMetadata(String name, List<String> aliases, boolean isDefault) {
this.name = name;
this.aliases = aliases;
this.isDefault = isDefault;
}
}
}
}

View File

@ -1,42 +0,0 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.impl;
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
import java.util.List;
import net.sourceforge.pmd.lang.LanguageVersion;
/**
* The simplest implementation of a language with only a few versions,
* and a single handler for all of them.
*
* @author Clément Fournier
*/
public class LanguageModuleWithOneVersion extends LanguageModuleBase {
private final List<LanguageVersion> distinctVersions;
private final LanguageVersion defaultVersion;
public LanguageModuleWithOneVersion(LanguageMetadata metadata) {
super(metadata);
this.defaultVersion = new LanguageVersion(this, "", null);
this.distinctVersions = listOf(defaultVersion);
}
@Override
public List<LanguageVersion> getVersions() {
return distinctVersions;
}
@Override
public LanguageVersion getDefaultVersion() {
return defaultVersion;
}
}

View File

@ -19,7 +19,7 @@ import net.sourceforge.pmd.lang.impl.LanguageModuleWithSeveralVersions.LanguageV
/**
* @author Clément Fournier
*/
public class LanguageModuleWithSeveralVersions<V extends Enum<V> & LanguageVersionId> extends LanguageModuleBase {
public abstract class LanguageModuleWithSeveralVersions<V extends Enum<V> & LanguageVersionId> extends LanguageModuleBase {
private final List<LanguageVersion> distinctVersions;
private final Map<String, LanguageVersion> byName;

View File

@ -12,11 +12,12 @@ import net.sourceforge.pmd.lang.LanguageVersionHandler;
import net.sourceforge.pmd.processor.BatchLanguageProcessor;
/**
* The simplest implementation of a language with only a single version.
* The simplest implementation of a language, where only a {@link LanguageVersionHandler}
* needs to be implemented.
*
* @author Clément Fournier
*/
public class SimpleLanguageModuleBase extends LanguageModuleWithOneVersion {
public class SimpleLanguageModuleBase extends LanguageModuleBase {
private final Function<LanguagePropertyBundle, LanguageVersionHandler> handler;

View File

@ -22,7 +22,6 @@ import net.sourceforge.pmd.lang.document.TextDocument;
import net.sourceforge.pmd.lang.document.TextRegion;
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
import net.sourceforge.pmd.lang.rule.impl.DefaultRuleViolationFactory;
import net.sourceforge.pmd.processor.PmdRunnableTest;
import net.sourceforge.pmd.processor.SimpleBatchLanguageProcessor;
/**
@ -44,7 +43,6 @@ public class DummyLanguageModule extends BaseLanguageModule {
addVersion("1.6", new Handler(), "6");
addDefaultVersion("1.7", new Handler(), "7");
addVersion("1.8", new Handler(), "8");
PmdRunnableTest.registerCustomVersions(this::addVersion);
}
@Override
@ -80,7 +78,7 @@ public class DummyLanguageModule extends BaseLanguageModule {
* children "b" and "c". "x" is ignored. The node "a" is not the root
* node, it has a {@link DummyRootNode} as parent, whose image is "".
*/
private static DummyRootNode readLispNode(ParserTask task) {
public static DummyRootNode readLispNode(ParserTask task) {
TextDocument document = task.getTextDocument();
final DummyRootNode root = new DummyRootNode().withTaskInfo(task);
root.setRegion(document.getEntireRegion());

View File

@ -18,7 +18,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -33,15 +32,16 @@ import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.internal.SystemProps;
import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.internal.util.ContextedAssertionError;
import net.sourceforge.pmd.lang.DummyLanguageModule;
import net.sourceforge.pmd.lang.DummyLanguageModule.Handler;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.Parser;
import net.sourceforge.pmd.lang.ast.RootNode;
import net.sourceforge.pmd.lang.document.TextFile;
import net.sourceforge.pmd.lang.impl.SimpleLanguageModuleBase;
import net.sourceforge.pmd.lang.rule.AbstractRule;
import net.sourceforge.pmd.util.log.MessageReporter;
@ -55,7 +55,6 @@ public class PmdRunnableTest {
private static final String THROWS_ASSERTION_ERROR = "1.9-throws";
private PMDConfiguration configuration;
private PmdRunnable pmdRunnable;
private MessageReporter reporter;
private Rule rule;
@ -64,21 +63,21 @@ public class PmdRunnableTest {
public void prepare() {
// reset data
rule = spy(new RuleThatThrows());
configuration = new PMDConfiguration();
configuration = new PMDConfiguration(LanguageRegistry.singleton(ThrowingLanguageModule.INSTANCE));
reporter = mock(MessageReporter.class);
configuration.setReporter(reporter);
// will be populated by a call to process(LanguageVersion)
pmdRunnable = null;
// exceptions thrown on a worker thread are not thrown by the main thread,
// so this test only makes sense with one thread
configuration.setThreads(1);
}
private Report process(LanguageVersion lv) {
configuration.setForceLanguageVersion(lv);
configuration.setIgnoreIncrementalAnalysis(true);
try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) {
pmd.files().addFile(TextFile.forCharSeq("test", "test.dummy", lv));
pmd.files().addSourceFile("test.dummy", "foo");
pmd.addRuleSet(RuleSet.forSingleRule(rule));
pmd.getLanguageProperties(lv.getLanguage()).setLanguageVersion(lv.getVersion());
return pmd.performAnalysisAndCollectReport();
}
}
@ -99,7 +98,7 @@ public class PmdRunnableTest {
SystemLambda.restoreSystemProperties(() -> {
System.setProperty(SystemProps.PMD_ERROR_RECOVERY, "");
Report report = process(DummyLanguageModule.getInstance().getDefaultVersion());
Report report = process(ThrowingLanguageModule.INSTANCE.getDefaultVersion());
List<ProcessingError> errors = report.getProcessingErrors();
assertThat(errors, hasSize(1));
@ -120,7 +119,7 @@ public class PmdRunnableTest {
public void withoutErrorRecoveryModeProcessingShouldBeAbortedByRule() throws Exception {
SystemLambda.restoreSystemProperties(() -> {
System.clearProperty(SystemProps.PMD_ERROR_RECOVERY);
assertThrows(AssertionError.class, () -> process(DummyLanguageModule.getInstance().getDefaultVersion()));
assertThrows(AssertionError.class, () -> process(ThrowingLanguageModule.INSTANCE.getDefaultVersion()));
});
}
@ -146,29 +145,60 @@ public class PmdRunnableTest {
assertEquals(1, report.getProcessingErrors().size());
}
public static void registerCustomVersions(BiConsumer<String, Handler> addVersion) {
addVersion.accept(THROWS_ASSERTION_ERROR, new HandlerWithParserThatThrows());
addVersion.accept(PARSER_REPORTS_SEMANTIC_ERROR, new HandlerWithParserThatReportsSemanticError());
addVersion.accept(THROWS_SEMANTIC_ERROR, new HandlerWithParserThatThrowsSemanticError());
}
public static LanguageVersion versionWithParserThatThrowsAssertionError() {
return DummyLanguageModule.getInstance().getVersion(THROWS_ASSERTION_ERROR);
return ThrowingLanguageModule.INSTANCE.getVersion(THROWS_ASSERTION_ERROR);
}
public static LanguageVersion getVersionWithParserThatThrowsSemanticError() {
return DummyLanguageModule.getInstance().getVersion(THROWS_SEMANTIC_ERROR);
return ThrowingLanguageModule.INSTANCE.getVersion(THROWS_SEMANTIC_ERROR);
}
public static LanguageVersion versionWithParserThatReportsSemanticError() {
return DummyLanguageModule.getInstance().getVersion(PARSER_REPORTS_SEMANTIC_ERROR);
return ThrowingLanguageModule.INSTANCE.getVersion(PARSER_REPORTS_SEMANTIC_ERROR);
}
static class ThrowingLanguageModule extends SimpleLanguageModuleBase {
static final ThrowingLanguageModule INSTANCE = new ThrowingLanguageModule();
public ThrowingLanguageModule() {
super(LanguageMetadata.withId("foo").name("Foo").extensions("foo")
.addVersion(THROWS_ASSERTION_ERROR)
.addVersion(THROWS_SEMANTIC_ERROR)
.addVersion(PARSER_REPORTS_SEMANTIC_ERROR)
.addDefaultVersion(""),
props -> () -> makeParser(props));
}
private static Parser makeParser(LanguagePropertyBundle bundle) {
switch (bundle.getLanguageVersion().getVersion()) {
case THROWS_ASSERTION_ERROR:
return task -> {
throw new AssertionError("test error while parsing");
};
case PARSER_REPORTS_SEMANTIC_ERROR:
return task -> {
RootNode root = DummyLanguageModule.readLispNode(task);
task.getReporter().error(root, TEST_MESSAGE_SEMANTIC_ERROR);
return root;
};
case THROWS_SEMANTIC_ERROR:
return task -> {
RootNode root = DummyLanguageModule.readLispNode(task);
throw task.getReporter().error(root, TEST_MESSAGE_SEMANTIC_ERROR);
};
case "":
return DummyLanguageModule::readLispNode;
default:
throw AssertionUtil.shouldNotReachHere("");
}
}
}
private static class RuleThatThrows extends AbstractRule {
RuleThatThrows() {
Language dummyLanguage = DummyLanguageModule.getInstance();
setLanguage(dummyLanguage);
setLanguage(ThrowingLanguageModule.INSTANCE);
}
@Override
@ -177,36 +207,4 @@ public class PmdRunnableTest {
}
}
public static class HandlerWithParserThatThrowsSemanticError extends Handler {
@Override
public Parser getParser() {
return task -> {
RootNode root = super.getParser().parse(task);
throw task.getReporter().error(root, TEST_MESSAGE_SEMANTIC_ERROR);
};
}
}
public static class HandlerWithParserThatThrows extends Handler {
@Override
public Parser getParser() {
return task -> {
throw new AssertionError("test error while parsing");
};
}
}
public static class HandlerWithParserThatReportsSemanticError extends Handler {
@Override
public Parser getParser() {
return task -> {
RootNode root = super.getParser().parse(task);
task.getReporter().error(root, TEST_MESSAGE_SEMANTIC_ERROR);
return root;
};
}
}
}

View File

@ -11,8 +11,7 @@ import net.sourceforge.pmd.lang.LanguageProcessor;
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.impl.LanguageModuleWithSeveralVersions;
import net.sourceforge.pmd.lang.scala.ScalaLanguageModule.ScalaVersionIds;
import net.sourceforge.pmd.lang.impl.LanguageModuleBase;
import net.sourceforge.pmd.processor.SimpleBatchLanguageProcessor;
import scala.meta.Dialect;
@ -20,7 +19,7 @@ import scala.meta.Dialect;
/**
* Language Module for Scala.
*/
public class ScalaLanguageModule extends LanguageModuleWithSeveralVersions<ScalaVersionIds> {
public class ScalaLanguageModule extends LanguageModuleBase {
/** The name. */
public static final String NAME = "Scala";
@ -32,41 +31,25 @@ public class ScalaLanguageModule extends LanguageModuleWithSeveralVersions<Scala
* Create a new instance of Scala Language Module.
*/
public ScalaLanguageModule() {
super(LanguageMetadata.withId(TERSE_NAME).name(NAME).extensions("scala"),
ScalaVersionIds.class);
super(LanguageMetadata.withId(TERSE_NAME).name(NAME).extensions("scala")
.addVersion("2.10")
.addVersion("2.11")
.addVersion("2.12")
.addDefaultVersion("2.13"));
}
enum ScalaVersionIds implements LanguageVersionId {
SCALA_210("2.10", scala.meta.dialects.package$.MODULE$.Scala210()),
SCALA_211("2.11", scala.meta.dialects.package$.MODULE$.Scala211()),
SCALA_212("2.12", scala.meta.dialects.package$.MODULE$.Scala212()),
SCALA_213("2.13", scala.meta.dialects.package$.MODULE$.Scala213()),
;
private final String name;
private final Dialect dialect;
ScalaVersionIds(String name, Dialect dialect) {
this.name = name;
this.dialect = dialect;
}
@Override
public String getVersionString() {
return name;
}
@Override
public boolean isDefault() {
return this == SCALA_213;
}
}
@InternalApi
public static @NonNull Dialect dialectOf(LanguageVersion v) {
return getInstance().getIdOf(v).dialect;
switch (v.getVersion()) {
case "2.10": return scala.meta.dialects.package$.MODULE$.Scala210();
case "2.11": return scala.meta.dialects.package$.MODULE$.Scala211();
case "2.12": return scala.meta.dialects.package$.MODULE$.Scala212();
case "2.13": return scala.meta.dialects.package$.MODULE$.Scala213();
default:
throw new IllegalArgumentException(v.getVersion());
}
}
@Override
public LanguageProcessor createProcessor(LanguagePropertyBundle bundle) {
return new SimpleBatchLanguageProcessor(