Skip to content

Commit

Permalink
Show warnings for items not logged (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
wilsonwatson committed Sep 5, 2023
1 parent dbea544 commit 6f5edf2
Show file tree
Hide file tree
Showing 13 changed files with 493 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ $RECYCLE.BIN/
### Gradle ###
.gradle
/build/
/checker/build/

# Ignore Gradle GUI config
gradle-app.setting
Expand Down
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'

annotationProcessor project(":checker")
}

test {
Expand Down Expand Up @@ -96,6 +98,11 @@ wpi.java.configureTestTasks(test)
// Configure string concat to always inline compile
tasks.withType(JavaCompile) {
options.compilerArgs.add '-XDstringConcat=inline'
// only run in GH actions
if (System.getenv("GITHUB_ACTOR") != null && !System.getenv("GITHUB_ACTOR").equals("")) {
options.compilerArgs << "-Xplugin:rchk"
outputs.upToDateWhen { false } // Force recompile, even if source file hasn't changed
}
}

javadoc {
Expand Down
27 changes: 27 additions & 0 deletions checker/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

plugins {
id 'java-library'
}
tasks.withType(JavaCompile) {
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED'
}

sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

repositories {
mavenCentral()
}

dependencies {

}
20 changes: 20 additions & 0 deletions checker/src/main/java/org/frc5572/robotools/Checks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.frc5572.robotools;

import org.frc5572.robotools.checks.Check;
import org.frc5572.robotools.checks.IOCheck;

/** All checks to run. */
public class Checks {

private static final Check[] CHECKS = new Check[] {new IOCheck()};

/** Run through all checks */
public static boolean process(CompilationData data) {
boolean fatal = false;
for (Check c : CHECKS) {
fatal = fatal || c.check(data);
}
return fatal;
}

}
137 changes: 137 additions & 0 deletions checker/src/main/java/org/frc5572/robotools/CompilationData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package org.frc5572.robotools;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.LineMap;
import com.sun.source.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.Trees;

/**
* Wrapper around a TypeElement. Includes helpers for printing errors, warnings etc.
*/
public class CompilationData {

/** Type Information for entire classpath. */
public Types types;
private Trees trees;
private SourcePositions positions;
private CompilationUnitTree compilationUnitTree;
/** Element being processed */
public TypeElement element;
private Messager messager;

/** Basic constructor. */
public CompilationData(Types types, Trees trees, SourcePositions positions,
CompilationUnitTree compilationUnitTree, TypeElement element, Messager messager) {
this.types = types;
this.trees = trees;
this.positions = positions;
this.compilationUnitTree = compilationUnitTree;
this.element = element;
this.messager = messager;
}

/** Constructor from Javac Plugin context. */
public CompilationData(JavacTask task, TaskEvent event) {
this(task.getTypes(), Trees.instance(task), Trees.instance(task).getSourcePositions(),
event.getCompilationUnit(), event.getTypeElement(), null);
}

/** Constructor from Annotation Processor context. */
public CompilationData(ProcessingEnvironment processingEnv, TypeElement element) {
this(processingEnv.getTypeUtils(), null, null, null, element, processingEnv.getMessager());
}

/** Show error */
public void error(Element element, Object object) {
echo(element, object.toString(), Diagnostic.Kind.ERROR, "error", "Error");
}

/** Show warning */
public void warn(Element element, Object object) {
echo(element, object.toString(), Diagnostic.Kind.MANDATORY_WARNING, "warning", "Warning");
}

/** Show note (this doesn't work in VS Code yet) */
public void note(Element element, Object object) {
echo(element, object.toString(), Diagnostic.Kind.NOTE, "notice", "Note");
}

private void echo(Element element, String errString, Diagnostic.Kind kind, String ghString,
String humanString) {
if (compilationUnitTree == null) {
messager.printMessage(kind, errString, element);
} else {
Tree tree = trees.getTree(element);
LineMap linemap = compilationUnitTree.getLineMap();
long pos = positions.getStartPosition(compilationUnitTree, tree);
long row = linemap.getLineNumber(pos);
String name = compilationUnitTree.getSourceFile().toUri().toString().split("/src/")[1];
System.out
.println("::" + ghString + " file=src/" + name + ",line=" + row + "::" + errString);
}
}

private boolean _implements(String qualifiedName, TypeElement elem) {
if (elem.getQualifiedName().toString().equals(qualifiedName)) {
return true;
}
Element superClass = types.asElement(elem.getSuperclass());
if (superClass instanceof TypeElement) {
if (_implements(qualifiedName, (TypeElement) superClass)) {
return true;
}
}
for (var iface : elem.getInterfaces()) {
Element interface_ = types.asElement(iface);
if (interface_ instanceof TypeElement) {
if (_implements(qualifiedName, (TypeElement) interface_)) {
return true;
}
}
}
return false;
}

private boolean _extends(String qualifiedName, TypeElement elem) {
if (elem.getQualifiedName().toString().equals(qualifiedName)) {
return true;
}
Element superClass = types.asElement(elem.getSuperclass());
if (superClass instanceof TypeElement) {
if (_extends(qualifiedName, (TypeElement) superClass)) {
return true;
}
}
return false;
}

/** Get if this type implements an interface by name. */
public boolean implementsInterface(String qualifiedName) {
return _implements(qualifiedName, this.element);
}

/** Get if the specified type implements an interface by name. */
public boolean implementsInterface(TypeElement e, String qualifiedName) {
return _implements(qualifiedName, e);
}

/** Get if this type extends a class by name. */
public boolean extendsClass(String qualifiedName) {
return _extends(qualifiedName, this.element);
}

/** Get if the specified type extends a class by name. */
public boolean extendsClass(TypeElement e, String qualifiedName) {
return _extends(qualifiedName, e);
}

}
44 changes: 44 additions & 0 deletions checker/src/main/java/org/frc5572/robotools/RobotPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.frc5572.robotools;

import javax.lang.model.util.Types;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.Trees;

/**
* Javac plugin has source info, so it's used for Github Action annotations.
*/
public class RobotPlugin implements Plugin {

/**
* Name used in build.gradle
*/
@Override
public String getName() {
return "rchk";
}

/**
* Function run when loaded
*/
@Override
public void init(JavacTask task, String... arg1) {
Types types = task.getTypes();
Trees trees = Trees.instance(task);
SourcePositions positions = trees.getSourcePositions();
task.addTaskListener(new TaskListener() {
@Override
public void finished(TaskEvent event) {
if (event.getKind() == TaskEvent.Kind.ANALYZE) {
CompilationData data = new CompilationData(types, trees, positions,
event.getCompilationUnit(), event.getTypeElement(), null);
Checks.process(data);
}
}
});
}

}
75 changes: 75 additions & 0 deletions checker/src/main/java/org/frc5572/robotools/RobotProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.frc5572.robotools;

import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;

/**
* Annotation processor for checks. Used by VS Code.
*/
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@SupportedOptions("frc_check.skip")
public class RobotProcessor extends AbstractProcessor {

private boolean processTypeElement(TypeElement typeElement) {
for (Element e3 : typeElement.getEnclosedElements()) {
if (e3 instanceof TypeElement) {
processTypeElement((TypeElement) e3);
}
}
CompilationData data = new CompilationData(processingEnv, typeElement);
return Checks.process(data);
}

private boolean hasProcessed;

/** Initialization function */
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
hasProcessed = false;
}

/** Process all elements. */
@Override
public boolean process(Set<? extends TypeElement> arg0, RoundEnvironment roundEnv) {
if (hasProcessed) {
return false;
}
hasProcessed = true;
boolean fatal = false;
for (ModuleElement mod : processingEnv.getElementUtils().getAllModuleElements()) {
if (!mod.isUnnamed()) {
continue;
}
for (Element element : mod.getEnclosedElements()) {
if (element instanceof PackageElement) {
PackageElement packageElement = (PackageElement) element;
if (packageElement.getQualifiedName().toString().startsWith("frc.")) {
for (Element element2 : packageElement.getEnclosedElements()) {
if (element2 instanceof TypeElement) {
TypeElement typeElement = (TypeElement) element2;
fatal = fatal || processTypeElement(typeElement);
}
}
}
}
}
}
if (fatal) {
throw new RuntimeException("Checks failed!");
}
return true;
}

}
12 changes: 12 additions & 0 deletions checker/src/main/java/org/frc5572/robotools/checks/Check.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.frc5572.robotools.checks;

import org.frc5572.robotools.CompilationData;

/** A code checker */
@FunctionalInterface
public interface Check {

/** Check if code is fine. Returns true if error is fatal. */
public boolean check(CompilationData data);

}
Loading

0 comments on commit 6f5edf2

Please sign in to comment.