diff options
author | 2018-02-12 14:10:17 -0800 | |
---|---|---|
committer | 2018-02-12 14:12:40 -0800 | |
commit | b87a47a090aa04b7df256a229ecdbf2ec319c03c (patch) | |
tree | f03da1df72ce4348679152094d56fdaeafed12d5 /src/main/java/com/google/devtools/build/lib/skylarkinterface | |
parent | dd4ddfd4a78e187f3fd39978d569f9b2ae17968b (diff) |
Create a basic annotation processor for validating SkylarkCallable uses at compile time.
RELNOTES: None.
PiperOrigin-RevId: 185432867
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skylarkinterface')
2 files changed, 120 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/BUILD b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/BUILD new file mode 100644 index 0000000000..c5a14392fa --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/BUILD @@ -0,0 +1,25 @@ +# Description: +# A preprocessor for skylarkinterface annotations. +package(default_visibility = [ + "//src/main/java/com/google/devtools/build/lib:__pkg__", + "//src/test/java/com/google/devtools/build/lib/skylarkinterface/processor:__pkg__", +]) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + srcs = glob( + ["**"], + ), +) + +java_plugin( + name = "annotation_preprocessor", + srcs = glob(["*.java"]), + processor_class = "com.google.devtools.build.lib.skylarkinterface.processor.SkylarkCallableProcessor", + deps = [ + "//src/main/java/com/google/devtools/build/lib:skylarkinterface_internal", + "//third_party:guava", + ], +) diff --git a/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/SkylarkCallableProcessor.java b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/SkylarkCallableProcessor.java new file mode 100644 index 0000000000..cc98597932 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/SkylarkCallableProcessor.java @@ -0,0 +1,95 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.skylarkinterface.processor; + +import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +/** + * Annotation processor for {@link SkylarkCallable}. + * + * <p>Checks the following invariants about {@link SkylarkCallable}-annotated methods: + * <ul> + * <li>The method must be public.</li> + * <li>The number of method parameters much match the number of annotation-declared parameters.</li> + * <li>If structField=true, there must be zero arguments.</li> + * </ul> + * + * <p>These properties can be relied upon at runtime without additional checks. + */ +@SupportedAnnotationTypes({"com.google.devtools.build.lib.skylarkinterface.SkylarkCallable"}) +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public final class SkylarkCallableProcessor extends AbstractProcessor { + + private Messager messager; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + messager = processingEnv.getMessager(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + for (Element element : roundEnv.getElementsAnnotatedWith(SkylarkCallable.class)) { + // Only methods are annotated with SkylarkCallable. This is verified by the + // @Target(ElementType.METHOD) annotation. + ExecutableElement methodElement = (ExecutableElement) element; + SkylarkCallable annotation = methodElement.getAnnotation(SkylarkCallable.class); + + if (!methodElement.getModifiers().contains(Modifier.PUBLIC)) { + error(methodElement, "@SkylarkCallable annotated methods must be public."); + } + if (annotation.parameters().length > 0 || annotation.mandatoryPositionals() >= 0) { + int numDeclaredArgs = annotation.parameters().length + + Math.max(0, annotation.mandatoryPositionals()); + if (methodElement.getParameters().size() != numDeclaredArgs) { + error(methodElement, String.format( + "@SkylarkCallable annotated method has %d parameters, but annotation declared %d.", + methodElement.getParameters().size(), numDeclaredArgs)); + } + } + if (annotation.structField()) { + if (!methodElement.getParameters().isEmpty()) { + error(methodElement, + "@SkylarkCallable annotated methods with structField=true must have zero arguments."); + } + } + } + return true; + } + + /** + * Prints an error message & fails the compilation. + * + * @param e The element which has caused the error. Can be null + * @param msg The error message + */ + public void error(Element e, String msg) { + messager.printMessage(Diagnostic.Kind.ERROR, msg, e); + } +} |