Switch over to Error Prone (now with NullAway!)

There is one major change: we now have a separate artifact for the annotation processor.

As for NullAway, we are currently exempting the clientbound join game/respawn packets. They are ugly and need to be refactored.
This commit is contained in:
Andrew Steinborn
2021-05-13 04:13:15 -04:00
parent c496d912ea
commit 3c41211163
79 changed files with 494 additions and 401 deletions

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.annotationprocessor;
import com.google.gson.Gson;
import com.velocitypowered.api.plugin.Plugin;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor {
private @Nullable String pluginClassFound;
private boolean warnedAboutMultiplePlugins;
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) {
if (element.getKind() != ElementKind.CLASS) {
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with "
+ Plugin.class.getCanonicalName());
return false;
}
Name qualifiedName = ((TypeElement) element).getQualifiedName();
if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
if (!warnedAboutMultiplePlugins) {
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support "
+ "multiple plugins. We are using " + pluginClassFound
+ " for your plugin's main class.");
warnedAboutMultiplePlugins = true;
}
return false;
}
Plugin plugin = element.getAnnotation(Plugin.class);
if (!SerializedPluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin "
+ qualifiedName
+ ". IDs must start alphabetically, have alphanumeric characters, and can "
+ "contain dashes or underscores.");
return false;
}
// All good, generate the velocity-plugin.json.
SerializedPluginDescription description = SerializedPluginDescription
.from(plugin, qualifiedName.toString());
try {
FileObject object = processingEnv.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json");
try (Writer writer = new BufferedWriter(object.openWriter())) {
new Gson().toJson(description, writer);
}
pluginClassFound = qualifiedName.toString();
} catch (IOException e) {
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file");
}
}
return false;
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.annotationprocessor;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.plugin.Plugin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class SerializedPluginDescription {
public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
// @Nullable is used here to make GSON skip these in the serialized file
private final String id;
private final @Nullable String name;
private final @Nullable String version;
private final @Nullable String description;
private final @Nullable String url;
private final @Nullable List<String> authors;
private final @Nullable List<Dependency> dependencies;
private final String main;
private SerializedPluginDescription(String id, String name, String version, String description,
String url,
List<String> authors, List<Dependency> dependencies, String main) {
Preconditions.checkNotNull(id, "id");
Preconditions.checkArgument(ID_PATTERN.matcher(id).matches(), "id is not valid");
this.id = id;
this.name = Strings.emptyToNull(name);
this.version = Strings.emptyToNull(version);
this.description = Strings.emptyToNull(description);
this.url = Strings.emptyToNull(url);
this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors;
this.dependencies =
dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies;
this.main = Preconditions.checkNotNull(main, "main");
}
static SerializedPluginDescription from(Plugin plugin, String qualifiedName) {
List<Dependency> dependencies = new ArrayList<>();
for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) {
dependencies.add(new Dependency(dependency.id(), dependency.optional()));
}
return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(),
plugin.description(), plugin.url(),
Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty())
.collect(Collectors.toList()), dependencies, qualifiedName);
}
public String getId() {
return id;
}
public @Nullable String getName() {
return name;
}
public @Nullable String getVersion() {
return version;
}
public @Nullable String getDescription() {
return description;
}
public @Nullable String getUrl() {
return url;
}
public List<String> getAuthors() {
return authors == null ? ImmutableList.of() : authors;
}
public List<Dependency> getDependencies() {
return dependencies == null ? ImmutableList.of() : dependencies;
}
public String getMain() {
return main;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SerializedPluginDescription that = (SerializedPluginDescription) o;
return Objects.equals(id, that.id)
&& Objects.equals(name, that.name)
&& Objects.equals(version, that.version)
&& Objects.equals(description, that.description)
&& Objects.equals(url, that.url)
&& Objects.equals(authors, that.authors)
&& Objects.equals(dependencies, that.dependencies)
&& Objects.equals(main, that.main);
}
@Override
public int hashCode() {
return Objects.hash(id, name, version, description, url, authors, dependencies);
}
@Override
public String toString() {
return "SerializedPluginDescription{"
+ "id='" + id + '\''
+ ", name='" + name + '\''
+ ", version='" + version + '\''
+ ", description='" + description + '\''
+ ", url='" + url + '\''
+ ", authors=" + authors
+ ", dependencies=" + dependencies
+ ", main='" + main + '\''
+ '}';
}
public static final class Dependency {
private final String id;
private final boolean optional;
public Dependency(String id, boolean optional) {
this.id = id;
this.optional = optional;
}
public String getId() {
return id;
}
public boolean isOptional() {
return optional;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Dependency that = (Dependency) o;
return optional == that.optional
&& Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id, optional);
}
@Override
public String toString() {
return "Dependency{"
+ "id='" + id + '\''
+ ", optional=" + optional
+ '}';
}
}
}

View File

@@ -0,0 +1 @@
com.velocitypowered.annotationprocessor.PluginAnnotationProcessor