diff --git a/docgen/pom.xml b/docgen/pom.xml new file mode 100644 index 00000000..cd1fb7df --- /dev/null +++ b/docgen/pom.xml @@ -0,0 +1,162 @@ + + + + + 4.0.0 + + + tech.picnic.error-prone-support + error-prone-support + 0.2.1-SNAPSHOT + + + Picnic :: Error Prone Support :: DocGen + error_prone_docgen + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + com.google.auto.value + auto-value + ${version.auto-value} + + + com.google.auto.service + auto-service + ${version.auto-service} + + + + + + + + src/main/java + + **/*.mustache + + + + + + + + ${groupId.error-prone} + error_prone_annotation + + + ${groupId.error-prone} + error_prone_core + + + ${project.groupId} + error-prone-contrib + ${project.version} + + + tech.picnic.error-prone-support + error_prone_docgen_processor + ${project.version} + + + com.google.guava + guava + + + org.yaml + snakeyaml + 1.30 + + + junit + junit + test + + + com.beust + jcommander + 1.82 + + + com.google.auto.value + auto-value-annotations + ${version.auto-value} + provided + + + com.github.spullara.mustache.java + compiler + 0.9.10 + + + com.google.code.gson + gson + 2.8.9 + + + com.google.truth + truth + test + + + + + + run-annotation-processor + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + site + + java + + + com.google.errorprone.DocGenTool + + + -bug_patterns=${basedir}/../error-prone-contrib/target/generated-sources/annotations/bugPatterns.txt + + -docs_repository=${basedir}/target/generated-wiki/ + -explanations=${basedir}/../docs/bugpattern-temp/ + + + + + + + + + + diff --git a/docgen/src/main/java/com/google/errorprone/BugPatternFileGenerator.java b/docgen/src/main/java/com/google/errorprone/BugPatternFileGenerator.java new file mode 100644 index 00000000..322f0d51 --- /dev/null +++ b/docgen/src/main/java/com/google/errorprone/BugPatternFileGenerator.java @@ -0,0 +1,168 @@ +/* + * Copyright 2014 The Error Prone Authors. + * + * 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.errorprone; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.LineProcessor; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.gson.Gson; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; + +/** + * Reads each line of the bugpatterns.txt tab-delimited data file, and generates a GitHub Jekyll + * page for each one. + * + * @author alexeagle@google.com (Alex Eagle) + */ +class BugPatternFileGenerator implements LineProcessor> { + + private final Path outputDir; + private final Path explanationDir; + private final List result; + + private final Function severityRemapper; + + /** The base url for links to bugpatterns. */ + @Nullable private final String baseUrl; + + public BugPatternFileGenerator( + Path bugpatternDir, + Path explanationDir, + String baseUrl, + Function severityRemapper) { + this.outputDir = bugpatternDir; + this.explanationDir = explanationDir; + this.severityRemapper = severityRemapper; + this.baseUrl = baseUrl; + result = new ArrayList<>(); + } + + @Override + public boolean processLine(String line) throws IOException { + BugPatternInstance pattern = new Gson().fromJson(line, BugPatternInstance.class); + pattern.severity = severityRemapper.apply(pattern); + result.add(pattern); + + // replace spaces in filename with underscores + Path checkPath = Paths.get(pattern.name.replace(' ', '_') + ".md"); + + try (Writer writer = Files.newBufferedWriter(outputDir.resolve(checkPath), UTF_8)) { + + // load side-car explanation file, if it exists + Path sidecarExplanation = explanationDir.resolve(checkPath); + if (Files.exists(sidecarExplanation)) { + if (!pattern.explanation.isEmpty()) { + throw new AssertionError( + String.format( + "%s specifies an explanation via @BugPattern and side-car", pattern.name)); + } + pattern.explanation = Files.readString(sidecarExplanation).trim(); + } + + // Construct an appropriate page for this {@code BugPattern}. Include altNames if + // there are any, and explain the correct way to suppress. + + ImmutableMap.Builder templateData = + ImmutableMap.builder() + .put( + "tags", Arrays.stream(pattern.tags).map(Style::styleTag).collect(joining("\n\n"))) + .put("severity", Style.styleSeverity(pattern.severity)) + .put("name", pattern.name) + .put("bugpattern", String.format("%s.java", pattern.className)) + .put("className", pattern.className) + .put("summary", pattern.summary.trim()) + .put("altNames", Joiner.on(", ").join(pattern.altNames)) + .put("explanation", pattern.explanation.trim()); + + if (pattern.sampleInput != null) { + templateData.put("sampleInput", pattern.sampleInput); + } + + if (pattern.sampleOutput != null) { + templateData.put("sampleOutput", pattern.sampleOutput); + } + + if (baseUrl != null) { + templateData.put("baseUrl", baseUrl); + } + + if (pattern.documentSuppression) { + String suppressionString; + if (pattern.suppressionAnnotations.length == 0) { + suppressionString = "This check may not be suppressed."; + } else { + suppressionString = + pattern.suppressionAnnotations.length == 1 + ? "Suppress false positives by adding the suppression annotation %s to the " + + "enclosing element." + : "Suppress false positives by adding one of these suppression annotations to " + + "the enclosing element: %s"; + suppressionString = + String.format( + suppressionString, + Arrays.stream(pattern.suppressionAnnotations) + .map((String anno) -> standardizeAnnotation(anno, pattern.name)) + .collect(joining(", "))); + } + templateData.put("suppression", suppressionString); + } + + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile("com/google/errorprone/resources/bugpattern.mustache"); + mustache.execute(writer, templateData.buildOrThrow()); + } + return true; + } + + private String standardizeAnnotation(String fullAnnotationName, String patternName) { + String annotationName = + fullAnnotationName.endsWith(".class") + ? fullAnnotationName.substring(0, fullAnnotationName.length() - ".class".length()) + : fullAnnotationName; + if (annotationName.equals(SuppressWarnings.class.getName())) { + annotationName = SuppressWarnings.class.getSimpleName() + "(\"" + patternName + "\")"; + } + return "`@" + annotationName + "`"; + } + + @Override + public List getResult() { + return result; + } +} diff --git a/docgen/src/main/java/com/google/errorprone/DocGenTool.java b/docgen/src/main/java/com/google/errorprone/DocGenTool.java new file mode 100644 index 00000000..371ab27f --- /dev/null +++ b/docgen/src/main/java/com/google/errorprone/DocGenTool.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015 The Error Prone Authors. + * + * 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.errorprone; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.io.Files.asCharSource; +import static com.google.errorprone.scanner.BuiltInCheckerSuppliers.ENABLED_ERRORS; +import static com.google.errorprone.scanner.BuiltInCheckerSuppliers.ENABLED_WARNINGS; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.StreamSupport; + +/** + * Utility main which consumes the same tab-delimited text file and generates GitHub pages for the + * BugPatterns. + */ +public final class DocGenTool { + + @Parameters(separators = "=") + static class Options { + @Parameter(names = "-bug_patterns", description = "Path to bugPatterns.txt", required = true) + private String bugPatterns; + + @Parameter( + names = "-explanations", + description = "Path to side-car explanations", + required = true) + private String explanations; + + @Parameter(names = "-docs_repository", description = "Path to docs repository", required = true) + private String docsRepository; + + @Parameter( + names = "-base_url", + description = "The base url for links to bugpatterns", + arity = 1) + private String baseUrl = null; + } + + public static void main(String[] args) throws IOException { + Options options = new Options(); + new JCommander(options).parse(args); + + Path bugPatterns = Paths.get(options.bugPatterns); + if (!Files.exists(bugPatterns)) { + usage("Cannot find bugPatterns file: " + options.bugPatterns); + } + Path explanationDir = Paths.get(options.explanations); + if (!Files.exists(explanationDir)) { + usage("Cannot find explanations dir: " + options.explanations); + } + Path wikiDir = Paths.get(options.docsRepository); + Files.createDirectories(wikiDir); + Path bugpatternDir = wikiDir.resolve("bugpatterns"); + if (!Files.exists(bugpatternDir)) { + Files.createDirectories(bugpatternDir); + } + BugPatternFileGenerator generator = + new BugPatternFileGenerator( + bugpatternDir, + explanationDir, + options.baseUrl, + input -> input.severity); + try (Writer w = + Files.newBufferedWriter(wikiDir.resolve("bugpatterns.md"), StandardCharsets.UTF_8)) { + List patterns = + asCharSource(bugPatterns.toFile(), UTF_8).readLines(generator); + } + } + + private static ImmutableSet enabledCheckNames() { + return StreamSupport.stream( + Iterables.concat(ENABLED_ERRORS, ENABLED_WARNINGS).spliterator(), false) + .map(BugCheckerInfo::canonicalName) + .collect(toImmutableSet()); + } + + private static void usage(String err) { + System.err.println(err); + System.exit(1); + } + + private DocGenTool() {} +} diff --git a/docgen/src/main/java/com/google/errorprone/Style.java b/docgen/src/main/java/com/google/errorprone/Style.java new file mode 100644 index 00000000..fdc79aa9 --- /dev/null +++ b/docgen/src/main/java/com/google/errorprone/Style.java @@ -0,0 +1,27 @@ +package com.google.errorprone; + +import com.google.errorprone.BugPattern.SeverityLevel; + +public final class Style { + + public static String styleSeverity (SeverityLevel severityLevel) { + return String.format("%s\n {: .label .label-%s}", severityLevel.toString(), getSeverityLabelColour(severityLevel)); + } + + private static String getSeverityLabelColour (SeverityLevel severityLevel) { + switch (severityLevel) { + case ERROR: + return "red"; + case WARNING: + return "yellow"; + case SUGGESTION: + return "green"; + default: + return "blue"; + } + } + + public static String styleTag (String tagName) { + return String.format("%s\n {: .label }", tagName); + } +} diff --git a/docgen/src/main/java/com/google/errorprone/resources/bugpattern.mustache b/docgen/src/main/java/com/google/errorprone/resources/bugpattern.mustache new file mode 100644 index 00000000..dab54694 --- /dev/null +++ b/docgen/src/main/java/com/google/errorprone/resources/bugpattern.mustache @@ -0,0 +1,51 @@ +--- +layout: default +title: {{name}} +parent: Bug Patterns +nav_order: 1 +--- + + +# {{name}} + +{{{severity}}} +{{{tags}}} +{{{summary}}} + +{{#suppression}} +## Suppression +{{{suppression}}} +{{/suppression}} + +## Samples +### Input +```java +{{#sampleInput}} +{{{sampleInput}}} +{{/sampleInput}} +{{^sampleInput}} + public static void sample() {} +{{/sampleInput}} +``` +### Output +```java +{{#sampleOutput}} + {{{sampleOutput}}} +{{/sampleOutput}} +{{^sampleOutput}} + public static void sample() {} +{{/sampleOutput}} +``` + + + View source code on GitHub + + + + + + diff --git a/docgen/src/test/java/com/google/errorprone/BugPatternFileGeneratorTest.java b/docgen/src/test/java/com/google/errorprone/BugPatternFileGeneratorTest.java new file mode 100644 index 00000000..e99ecdd2 --- /dev/null +++ b/docgen/src/test/java/com/google/errorprone/BugPatternFileGeneratorTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2014 The Error Prone Authors. + * + * 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.errorprone; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.io.CharStreams; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.gson.Gson; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BugPatternFileGeneratorTest { + + @Rule public TemporaryFolder tmpfolder = new TemporaryFolder(); + + private Path wikiDir; + private Path explanationDirBase; + + @Before + public void setUp() throws Exception { + wikiDir = tmpfolder.newFolder("wiki").toPath(); + explanationDirBase = tmpfolder.newFolder("explanations").toPath(); + } + + private static BugPatternInstance deadExceptionTestInfo() { + BugPatternInstance instance = new BugPatternInstance(); + instance.className = "com.google.errorprone.bugpatterns.DeadException"; + instance.name = "DeadException"; + instance.summary = "Exception created but not thrown"; + instance.explanation = + "The exception is created with new, but is not thrown, and the reference is lost."; + instance.altNames = new String[] {"ThrowableInstanceNeverThrown"}; + instance.tags = new String[] {"LikelyError"}; + instance.severity = SeverityLevel.ERROR; + instance.suppressionAnnotations = new String[] {"java.lang.SuppressWarnings.class"}; + return instance; + } + + private static final String BUGPATTERN_LINE; + + static { + BugPatternInstance instance = deadExceptionTestInfo(); + BUGPATTERN_LINE = new Gson().toJson(instance); + } + + private static final String BUGPATTERN_LINE_SIDECAR; + + static { + BugPatternInstance instance = deadExceptionTestInfo(); + instance.explanation = ""; + BUGPATTERN_LINE_SIDECAR = new Gson().toJson(instance); + } + + // Assert that the generator produces the same output it did before. + // This is brittle, but you can open the golden file + // src/test/resources/com/google/errorprone/DeadException.md + // in the same Jekyll environment you use for prod, and verify it looks good. + @Test + public void regressionTest_frontmatter_pygments() throws Exception { + BugPatternFileGenerator generator = + new BugPatternFileGenerator( + wikiDir, explanationDirBase, null, input -> input.severity); + generator.processLine(BUGPATTERN_LINE); + String expected = + CharStreams.toString( + new InputStreamReader( + getClass().getResourceAsStream("testdata/DeadException_frontmatter_pygments.md"), + UTF_8)); + String actual = + CharStreams.toString(Files.newBufferedReader(wikiDir.resolve("DeadException.md"), UTF_8)); + assertThat(actual.trim()).isEqualTo(expected.trim()); + } + + @Test + public void regressionTest_nofrontmatter_gfm() throws Exception { + BugPatternFileGenerator generator = + new BugPatternFileGenerator( + wikiDir, explanationDirBase, null, input -> input.severity); + generator.processLine(BUGPATTERN_LINE); + String expected = + CharStreams.toString( + new InputStreamReader( + getClass().getResourceAsStream("testdata/DeadException_nofrontmatter_gfm.md"), + UTF_8)); + String actual = new String(Files.readAllBytes(wikiDir.resolve("DeadException.md")), UTF_8); + assertThat(actual.trim()).isEqualTo(expected.trim()); + } + + @Test + public void regressionTest_sidecar() throws Exception { + BugPatternFileGenerator generator = + new BugPatternFileGenerator( + wikiDir, explanationDirBase, null, input -> input.severity); + Files.write( + explanationDirBase.resolve("DeadException.md"), + Arrays.asList( + "The exception is created with new, but is not thrown, and the reference is lost."), + UTF_8); + generator.processLine(BUGPATTERN_LINE_SIDECAR); + String expected = + CharStreams.toString( + new InputStreamReader( + getClass().getResourceAsStream("testdata/DeadException_nofrontmatter_gfm.md"), + UTF_8)); + String actual = new String(Files.readAllBytes(wikiDir.resolve("DeadException.md")), UTF_8); + assertThat(actual.trim()).isEqualTo(expected.trim()); + } + + @Test + public void testEscapeAngleBracketsInSummary() throws Exception { + // Create a BugPattern with angle brackets in the summary + BugPatternInstance instance = new BugPatternInstance(); + instance.className = "com.google.errorprone.bugpatterns.DontDoThis"; + instance.name = "DontDoThis"; + instance.summary = "Don't do this; do List instead"; + instance.explanation = "This is a bad idea, you want `List` instead"; + instance.altNames = new String[0]; + instance.tags = new String[] {"LikelyError"}; + instance.severity = SeverityLevel.ERROR; + instance.suppressionAnnotations = new String[] {"java.lang.SuppressWarnings.class"}; + + // Write markdown file + BugPatternFileGenerator generator = + new BugPatternFileGenerator( + wikiDir, explanationDirBase, null, input -> input.severity); + generator.processLine(new Gson().toJson(instance)); + String expected = + CharStreams.toString( + new InputStreamReader( + getClass().getResourceAsStream("testdata/DontDoThis_nofrontmatter_gfm.md"), UTF_8)); + String actual = new String(Files.readAllBytes(wikiDir.resolve("DontDoThis.md")), UTF_8); + assertThat(actual.trim()).isEqualTo(expected.trim()); + } +} diff --git a/docgen/src/test/java/com/google/errorprone/testdata/DeadException_frontmatter_pygments.md b/docgen/src/test/java/com/google/errorprone/testdata/DeadException_frontmatter_pygments.md new file mode 100644 index 00000000..09bca0e2 --- /dev/null +++ b/docgen/src/test/java/com/google/errorprone/testdata/DeadException_frontmatter_pygments.md @@ -0,0 +1,20 @@ +--- +title: DeadException +summary: Exception created but not thrown +layout: bugpattern +tags: LikelyError +severity: ERROR +--- + + + +_Alternate names: ThrowableInstanceNeverThrown_ + +## The problem +The exception is created with new, but is not thrown, and the reference is lost. + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("DeadException")` to the enclosing element. diff --git a/docgen/src/test/java/com/google/errorprone/testdata/DeadException_nofrontmatter_gfm.md b/docgen/src/test/java/com/google/errorprone/testdata/DeadException_nofrontmatter_gfm.md new file mode 100644 index 00000000..1322b28a --- /dev/null +++ b/docgen/src/test/java/com/google/errorprone/testdata/DeadException_nofrontmatter_gfm.md @@ -0,0 +1,21 @@ + + +# DeadException + +__Exception created but not thrown__ + +
+ + +
SeverityERROR
TagsLikelyError
+ +_Alternate names: ThrowableInstanceNeverThrown_ + +## The problem +The exception is created with new, but is not thrown, and the reference is lost. + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("DeadException")` to the enclosing element. diff --git a/docgen/src/test/java/com/google/errorprone/testdata/DontDoThis_nofrontmatter_gfm.md b/docgen/src/test/java/com/google/errorprone/testdata/DontDoThis_nofrontmatter_gfm.md new file mode 100644 index 00000000..25c4a439 --- /dev/null +++ b/docgen/src/test/java/com/google/errorprone/testdata/DontDoThis_nofrontmatter_gfm.md @@ -0,0 +1,20 @@ + + +# DontDoThis + +__Don't do this; do List<Foo> instead__ + +
+ + +
SeverityERROR
TagsLikelyError
+ + +## The problem +This is a bad idea, you want `List` instead + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("DontDoThis")` to the enclosing element. diff --git a/docgen_processor/pom.xml b/docgen_processor/pom.xml new file mode 100644 index 00000000..30e006f5 --- /dev/null +++ b/docgen_processor/pom.xml @@ -0,0 +1,89 @@ + + + + + 4.0.0 + + + tech.picnic.error-prone-support + error-prone-support + 0.2.1-SNAPSHOT + + + Picnic :: Error Prone Support :: DocGen Processor + error_prone_docgen_processor + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + ${groupId.error-prone} + error_prone_annotation + + + ${groupId.error-prone} + error_prone_core + + + com.google.guava + guava + + + com.google.auto.service + auto-service-annotations + ${version.auto-service} + + + com.google.code.gson + gson + 2.8.9 + + + com.google.googlejavaformat + google-java-format + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + com.google.auto.service + auto-service + ${version.auto-service} + + + + + + + + diff --git a/docgen_processor/src/main/java/com/google/errorprone/BugPatternInstance.java b/docgen_processor/src/main/java/com/google/errorprone/BugPatternInstance.java new file mode 100644 index 00000000..3077d69f --- /dev/null +++ b/docgen_processor/src/main/java/com/google/errorprone/BugPatternInstance.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015 The Error Prone Authors. + * + * 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.errorprone; + +import static java.util.stream.Collectors.joining; + +import com.google.common.base.Preconditions; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.FormatterException; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import org.eclipse.jgit.util.IO; + +/** A serialization-friendly POJO of the information in a {@link BugPattern}. */ +public final class BugPatternInstance { + private static final Formatter FORMATTER = new Formatter(); + + public String className; + public String name; + public String summary; + public String explanation; + public String[] altNames; + public String category; + public String[] tags; + public SeverityLevel severity; + public String[] suppressionAnnotations; + public boolean documentSuppression = true; + + public String testContent; + public String sampleInput; + public String sampleOutput; + + public static BugPatternInstance fromElement(Element element) { + BugPatternInstance instance = new BugPatternInstance(); + instance.className = element.toString(); + + BugPattern annotation = element.getAnnotation(BugPattern.class); + instance.name = + annotation.name().isEmpty() ? element.getSimpleName().toString() : annotation.name(); + instance.altNames = annotation.altNames(); + instance.tags = annotation.tags(); + instance.severity = annotation.severity(); + instance.summary = annotation.summary(); + instance.explanation = annotation.explanation(); + instance.documentSuppression = annotation.documentSuppression(); + + Map keyValues = getAnnotation(element, BugPattern.class.getName()); + Object suppression = keyValues.get("suppressionAnnotations"); + if (suppression == null) { + instance.suppressionAnnotations = new String[] {SuppressWarnings.class.getName()}; + } else { + Preconditions.checkState(suppression instanceof List); + @SuppressWarnings("unchecked") // Always List, see above. + List resultList = (List) suppression; + instance.suppressionAnnotations = + resultList.stream().map(AnnotationValue::toString).toArray(String[]::new); + } + + Path testPath = + Path.of( + "error-prone-contrib/src/test/java/" + + instance.className.replace(".", "/") + + "Test.java"); + System.out.println("test class for " + instance.name + " = " + testPath.toAbsolutePath()); + + try { + Pattern inputPattern = + Pattern.compile("\\.addInputLines\\((\\n.*?\".*?\",)\\n(.*?)\\)\\n", Pattern.DOTALL); + instance.testContent = String.join("\n", Files.readAllLines(testPath)); + Matcher inputMatch = inputPattern.matcher(instance.testContent); + if (inputMatch.find()) { + String inputSrc = + inputMatch + .group(2) + ",\n"; + System.out.println(inputSrc); + inputSrc = inputSrc.replaceAll("\\s*\"(.*?)\"(,\\n)", "$1\n"); + System.out.println(inputSrc); + inputSrc = inputSrc + .replaceAll("\\\\\"(.*?)\\\\\"", "\"$1\""); + System.out.println(inputSrc); + instance.sampleInput = FORMATTER.formatSource(inputSrc); + } + } catch (IOException | IllegalStateException | FormatterException e) { + e.printStackTrace(); + } + + return instance; + } + + private static Map getAnnotation(Element element, String name) { + for (AnnotationMirror mirror : element.getAnnotationMirrors()) { + if (mirror.getAnnotationType().toString().equals(name)) { + return annotationKeyValues(mirror); + } + } + throw new IllegalArgumentException(String.format("%s has no annotation %s", element, name)); + } + + private static Map annotationKeyValues(AnnotationMirror mirror) { + Map result = new LinkedHashMap<>(); + for (ExecutableElement key : mirror.getElementValues().keySet()) { + result.put(key.getSimpleName().toString(), mirror.getElementValues().get(key).getValue()); + } + return result; + } +} diff --git a/docgen_processor/src/main/java/com/google/errorprone/DocGenProcessor.java b/docgen_processor/src/main/java/com/google/errorprone/DocGenProcessor.java new file mode 100644 index 00000000..7591e108 --- /dev/null +++ b/docgen_processor/src/main/java/com/google/errorprone/DocGenProcessor.java @@ -0,0 +1,93 @@ +/* + * Copyright 2011 The Error Prone Authors. + * + * 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.errorprone; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.auto.service.AutoService; +import com.google.googlejavaformat.java.Formatter; +import com.google.gson.Gson; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.Processor; +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.TypeElement; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + +/** + * Annotation processor which visits all classes that have a {@code BugPattern} annotation, and + * writes a tab-delimited text file dumping the data found. + * + * @author eaftan@google.com (Eddie Aftandilian) + * @author alexeagle@google.com (Alex Eagle) + */ +@AutoService(Processor.class) +@SupportedAnnotationTypes("com.google.errorprone.BugPattern") +public class DocGenProcessor extends AbstractProcessor { + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + private final Gson gson = new Gson(); + + private PrintWriter pw; + + /** {@inheritDoc} */ + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + try { + FileObject manifest = + processingEnv + .getFiler() + .createResource(StandardLocation.SOURCE_OUTPUT, "", "bugPatterns.txt"); + pw = new PrintWriter(new OutputStreamWriter(manifest.openOutputStream(), UTF_8), true); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** {@inheritDoc} */ + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (Element element : roundEnv.getElementsAnnotatedWith(BugPattern.class)) { + System.out.println("[DOCGEN] HANDLING: " + element.getSimpleName()); + gson.toJson(BugPatternInstance.fromElement(element), pw); + pw.println(); + } + + if (roundEnv.processingOver()) { + // this was the last round, do cleanup + cleanup(); + } + return false; + } + + /** Perform cleanup after last round of annotation processing. */ + private void cleanup() { + pw.close(); + } +} diff --git a/docgen_processor/src/test/java/tech/picnic/errorprone/docgen/ExampleExtractorTest.java b/docgen_processor/src/test/java/tech/picnic/errorprone/docgen/ExampleExtractorTest.java new file mode 100644 index 00000000..7ef7c472 --- /dev/null +++ b/docgen_processor/src/test/java/tech/picnic/errorprone/docgen/ExampleExtractorTest.java @@ -0,0 +1,90 @@ +package tech.picnic.errorprone.docgen; + +import static java.util.stream.Collectors.joining; + +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.FormatterException; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +class ExampleExtractorTest { + + private static final String INPUT = + String.join("\n", + "@Test", + "void replacementFirstSuggestedFix() {", + " refactoringTestHelper", + " .addInputLines(", + " \"A.java\",", + " \"import static java.util.stream.Collectors.toList;\",", + " \"import static java.util.stream.Collectors.toMap;\",", + " \"import static java.util.stream.Collectors.toSet;\",", + " \"\",", + " \"import java.util.stream.Collectors;\",", + " \"import java.util.stream.Stream;\",", + " \"import reactor.core.publisher.Flux;\",", + " \"\",", + " \"class A {\",", + " \" void m() {\",", + " \" Flux.just(1).collect(Collectors.toList());\",", + " \" Flux.just(2).collect(toList());\",", + " \"\",", + " \" Stream.of(\"foo\").collect(Collectors.toMap(String::getBytes, String::length));\",", + " \" Stream.of(\"bar\").collect(toMap(String::getBytes, String::length));\",", + " \" Flux.just(\"baz\").collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> b));\",", + " \" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> b));\",", + " \"\",", + " \" Stream.of(1).collect(Collectors.toSet());\",", + " \" Stream.of(2).collect(toSet());\",", + " \" }\",", + " \"}\")", + " .addOutputLines(", + " \"A.java\",", + " \"import static com.google.common.collect.ImmutableList.toImmutableList;\",", + " \"import static com.google.common.collect.ImmutableMap.toImmutableMap;\",", + " \"import static com.google.common.collect.ImmutableSet.toImmutableSet;\",", + " \"import static java.util.stream.Collectors.toList;\",", + " \"import static java.util.stream.Collectors.toMap;\",", + " \"import static java.util.stream.Collectors.toSet;\",", + " \"\",", + " \"import java.util.stream.Collectors;\",", + " \"import java.util.stream.Stream;\",", + " \"import reactor.core.publisher.Flux;\",", + " \"\",", + " \"class A {\",", + " \" void m() {\",", + " \" Flux.just(1).collect(toImmutableList());\",", + " \" Flux.just(2).collect(toImmutableList());\",", + " \"\",", + " \" Stream.of(\"foo\").collect(toImmutableMap(String::getBytes, String::length));\",", + " \" Stream.of(\"bar\").collect(toImmutableMap(String::getBytes, String::length));\",", + " \" Flux.just(\"baz\").collect(toImmutableMap(String::getBytes, String::length, (a, b) -> b));\",", + " \" Flux.just(\"qux\").collect(toImmutableMap(String::getBytes, String::length, (a, b) -> b));\",", + " \"\",", + " \" Stream.of(1).collect(toImmutableSet());\",", + " \" Stream.of(2).collect(toImmutableSet());\",", + " \" }\",", + " \"}\")", + " .doTest(TestMode.TEXT_MATCH);", + "}"); + + @Test + void regexTest() throws FormatterException { + final Formatter FORMATTER = new Formatter(); + Pattern pattern = + Pattern.compile("\\.addInputLines\\((\n.*?\".*?\",)\n(.*?)\\)\n", Pattern.DOTALL); + Matcher matcher = pattern.matcher(INPUT); + int count = matcher.groupCount(); + if(!matcher.find()) { + System.out.println("no match!"); + return; + } + + String src = matcher.group(2); + System.out.println("\\\"foo\\\"".replaceAll("\\\\\"(.*?)\\\\\"", "\"$1\"")); + } +} diff --git a/docs/bugpatterns/AmbiguousJsonCreator.md b/docs/bugpatterns/AmbiguousJsonCreator.md new file mode 100644 index 00000000..eec7c72a --- /dev/null +++ b/docs/bugpatterns/AmbiguousJsonCreator.md @@ -0,0 +1,42 @@ +--- +layout: default +title: AmbiguousJsonCreator +parent: Bug Patterns +nav_order: 1 +--- + + +# AmbiguousJsonCreator + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# AmbiguousJsonCreator + +__`JsonCreator.Mode` should be set for single-argument creators__ + +
+ + +
SeverityWARNING
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("AmbiguousJsonCreator")` to the enclosing element. diff --git a/docs/bugpatterns/AssertJIsNull.md b/docs/bugpatterns/AssertJIsNull.md new file mode 100644 index 00000000..3d4edf8a --- /dev/null +++ b/docs/bugpatterns/AssertJIsNull.md @@ -0,0 +1,42 @@ +--- +layout: default +title: AssertJIsNull +parent: Bug Patterns +nav_order: 1 +--- + + +# AssertJIsNull + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# AssertJIsNull + +__Prefer `.isNull()` over `.isEqualTo(null)`__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("AssertJIsNull")` to the enclosing element. diff --git a/docs/bugpatterns/AutowiredConstructor.md b/docs/bugpatterns/AutowiredConstructor.md new file mode 100644 index 00000000..5b05ef88 --- /dev/null +++ b/docs/bugpatterns/AutowiredConstructor.md @@ -0,0 +1,42 @@ +--- +layout: default +title: AutowiredConstructor +parent: Bug Patterns +nav_order: 1 +--- + + +# AutowiredConstructor + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# AutowiredConstructor + +__Omit `@Autowired` on a class' sole constructor, as it is redundant__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("AutowiredConstructor")` to the enclosing element. diff --git a/docs/bugpatterns/CanonicalAnnotationSyntax.md b/docs/bugpatterns/CanonicalAnnotationSyntax.md new file mode 100644 index 00000000..75e2af8d --- /dev/null +++ b/docs/bugpatterns/CanonicalAnnotationSyntax.md @@ -0,0 +1,42 @@ +--- +layout: default +title: CanonicalAnnotationSyntax +parent: Bug Patterns +nav_order: 1 +--- + + +# CanonicalAnnotationSyntax + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# CanonicalAnnotationSyntax + +__Omit redundant syntax from annotation declarations__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("CanonicalAnnotationSyntax")` to the enclosing element. diff --git a/docs/bugpatterns/CollectorMutability.md b/docs/bugpatterns/CollectorMutability.md new file mode 100644 index 00000000..621fb8ce --- /dev/null +++ b/docs/bugpatterns/CollectorMutability.md @@ -0,0 +1,42 @@ +--- +layout: default +title: CollectorMutability +parent: Bug Patterns +nav_order: 1 +--- + + +# CollectorMutability + +FragileCode + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# CollectorMutability + +__Avoid `Collectors.to{List,Map,Set}` in favour of alternatives that emphasize (im)mutability__ + +
+ + +
SeverityWARNING
TagsFragileCode
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("CollectorMutability")` to the enclosing element. diff --git a/docs/bugpatterns/EmptyMethod.md b/docs/bugpatterns/EmptyMethod.md index db69b306..b8b900c0 100644 --- a/docs/bugpatterns/EmptyMethod.md +++ b/docs/bugpatterns/EmptyMethod.md @@ -1 +1,42 @@ -There's not much use to keep empty methods. +--- +layout: default +title: EmptyMethod +parent: Bug Patterns +nav_order: 1 +--- + + +# EmptyMethod + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# EmptyMethod + +__Empty method can likely be deleted__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("EmptyMethod")` to the enclosing element. diff --git a/docs/bugpatterns/ErrorProneTestHelperSourceFormat.md b/docs/bugpatterns/ErrorProneTestHelperSourceFormat.md new file mode 100644 index 00000000..4d6e90f3 --- /dev/null +++ b/docs/bugpatterns/ErrorProneTestHelperSourceFormat.md @@ -0,0 +1,42 @@ +--- +layout: default +title: ErrorProneTestHelperSourceFormat +parent: Bug Patterns +nav_order: 1 +--- + + +# ErrorProneTestHelperSourceFormat + +Style + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# ErrorProneTestHelperSourceFormat + +__Test code should follow the Google Java style__ + +
+ + +
SeveritySUGGESTION
TagsStyle
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("ErrorProneTestHelperSourceFormat")` to the enclosing element. diff --git a/docs/bugpatterns/ExplicitEnumOrdering.md b/docs/bugpatterns/ExplicitEnumOrdering.md new file mode 100644 index 00000000..34847c13 --- /dev/null +++ b/docs/bugpatterns/ExplicitEnumOrdering.md @@ -0,0 +1,42 @@ +--- +layout: default +title: ExplicitEnumOrdering +parent: Bug Patterns +nav_order: 1 +--- + + +# ExplicitEnumOrdering + +FragileCode + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# ExplicitEnumOrdering + +__Make sure `Ordering#explicit` lists all of an enum's values__ + +
+ + +
SeverityWARNING
TagsFragileCode
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("ExplicitEnumOrdering")` to the enclosing element. diff --git a/docs/bugpatterns/FluxFlatMapUsage.md b/docs/bugpatterns/FluxFlatMapUsage.md new file mode 100644 index 00000000..9b3fb7e5 --- /dev/null +++ b/docs/bugpatterns/FluxFlatMapUsage.md @@ -0,0 +1,42 @@ +--- +layout: default +title: FluxFlatMapUsage +parent: Bug Patterns +nav_order: 1 +--- + + +# FluxFlatMapUsage + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# FluxFlatMapUsage + +__`Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; please use `Flux#concatMap` or explicitly specify the desired amount of concurrency__ + +
+ + +
SeverityERROR
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("FluxFlatMapUsage")` to the enclosing element. diff --git a/docs/bugpatterns/FormatStringConcatenation.md b/docs/bugpatterns/FormatStringConcatenation.md new file mode 100644 index 00000000..fd8085bf --- /dev/null +++ b/docs/bugpatterns/FormatStringConcatenation.md @@ -0,0 +1,42 @@ +--- +layout: default +title: FormatStringConcatenation +parent: Bug Patterns +nav_order: 1 +--- + + +# FormatStringConcatenation + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# FormatStringConcatenation + +__Defer string concatenation to the invoked method__ + +
+ + +
SeverityWARNING
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("FormatStringConcatenation")` to the enclosing element. diff --git a/docs/bugpatterns/IdentityConversion.md b/docs/bugpatterns/IdentityConversion.md new file mode 100644 index 00000000..4079ae35 --- /dev/null +++ b/docs/bugpatterns/IdentityConversion.md @@ -0,0 +1,42 @@ +--- +layout: default +title: IdentityConversion +parent: Bug Patterns +nav_order: 1 +--- + + +# IdentityConversion + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# IdentityConversion + +__Avoid or clarify identity conversions__ + +
+ + +
SeverityWARNING
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("IdentityConversion")` to the enclosing element. diff --git a/docs/bugpatterns/ImmutablesSortedSetComparator.md b/docs/bugpatterns/ImmutablesSortedSetComparator.md new file mode 100644 index 00000000..fbea3332 --- /dev/null +++ b/docs/bugpatterns/ImmutablesSortedSetComparator.md @@ -0,0 +1,42 @@ +--- +layout: default +title: ImmutablesSortedSetComparator +parent: Bug Patterns +nav_order: 1 +--- + + +# ImmutablesSortedSetComparator + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# ImmutablesSortedSetComparator + +__`SortedSet` properties of a `@Value.Immutable` or `@Value.Modifiable` type must be annotated with `@Value.NaturalOrder` or `@Value.ReverseOrder`__ + +
+ + +
SeverityERROR
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("ImmutablesSortedSetComparator")` to the enclosing element. diff --git a/docs/bugpatterns/JUnitMethodDeclaration.md b/docs/bugpatterns/JUnitMethodDeclaration.md new file mode 100644 index 00000000..cddbd675 --- /dev/null +++ b/docs/bugpatterns/JUnitMethodDeclaration.md @@ -0,0 +1,42 @@ +--- +layout: default +title: JUnitMethodDeclaration +parent: Bug Patterns +nav_order: 1 +--- + + +# JUnitMethodDeclaration + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# JUnitMethodDeclaration + +__JUnit method declaration can likely be improved__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("JUnitMethodDeclaration")` to the enclosing element. diff --git a/docs/bugpatterns/LexicographicalAnnotationAttributeListing.md b/docs/bugpatterns/LexicographicalAnnotationAttributeListing.md new file mode 100644 index 00000000..23f04925 --- /dev/null +++ b/docs/bugpatterns/LexicographicalAnnotationAttributeListing.md @@ -0,0 +1,42 @@ +--- +layout: default +title: LexicographicalAnnotationAttributeListing +parent: Bug Patterns +nav_order: 1 +--- + + +# LexicographicalAnnotationAttributeListing + +Style + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# LexicographicalAnnotationAttributeListing + +__Where possible, sort annotation array attributes lexicographically__ + +
+ + +
SeveritySUGGESTION
TagsStyle
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("LexicographicalAnnotationAttributeListing")` to the enclosing element. diff --git a/docs/bugpatterns/LexicographicalAnnotationListing.md b/docs/bugpatterns/LexicographicalAnnotationListing.md new file mode 100644 index 00000000..2efe3283 --- /dev/null +++ b/docs/bugpatterns/LexicographicalAnnotationListing.md @@ -0,0 +1,42 @@ +--- +layout: default +title: LexicographicalAnnotationListing +parent: Bug Patterns +nav_order: 1 +--- + + +# LexicographicalAnnotationListing + +Style + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# LexicographicalAnnotationListing + +__Sort annotations lexicographically where possible__ + +
+ + +
SeveritySUGGESTION
TagsStyle
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("LexicographicalAnnotationListing")` to the enclosing element. diff --git a/docs/bugpatterns/MethodReferenceUsage.md b/docs/bugpatterns/MethodReferenceUsage.md new file mode 100644 index 00000000..74b6f689 --- /dev/null +++ b/docs/bugpatterns/MethodReferenceUsage.md @@ -0,0 +1,42 @@ +--- +layout: default +title: MethodReferenceUsage +parent: Bug Patterns +nav_order: 1 +--- + + +# MethodReferenceUsage + +Style + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# MethodReferenceUsage + +__Prefer method references over lambda expressions__ + +
+ + +
SeveritySUGGESTION
TagsStyle
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("MethodReferenceUsage")` to the enclosing element. diff --git a/docs/bugpatterns/MissingRefasterAnnotation.md b/docs/bugpatterns/MissingRefasterAnnotation.md new file mode 100644 index 00000000..0581d519 --- /dev/null +++ b/docs/bugpatterns/MissingRefasterAnnotation.md @@ -0,0 +1,42 @@ +--- +layout: default +title: MissingRefasterAnnotation +parent: Bug Patterns +nav_order: 1 +--- + + +# MissingRefasterAnnotation + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# MissingRefasterAnnotation + +__The Refaster template contains a method without any Refaster annotations__ + +
+ + +
SeverityWARNING
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("MissingRefasterAnnotation")` to the enclosing element. diff --git a/docs/bugpatterns/MockitoStubbing.md b/docs/bugpatterns/MockitoStubbing.md new file mode 100644 index 00000000..fb3d92f9 --- /dev/null +++ b/docs/bugpatterns/MockitoStubbing.md @@ -0,0 +1,42 @@ +--- +layout: default +title: MockitoStubbing +parent: Bug Patterns +nav_order: 1 +--- + + +# MockitoStubbing + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# MockitoStubbing + +__Don't unnecessarily use Mockito's `eq(...)`__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("MockitoStubbing")` to the enclosing element. diff --git a/docs/bugpatterns/NestedOptionals.md b/docs/bugpatterns/NestedOptionals.md new file mode 100644 index 00000000..b219b883 --- /dev/null +++ b/docs/bugpatterns/NestedOptionals.md @@ -0,0 +1,42 @@ +--- +layout: default +title: NestedOptionals +parent: Bug Patterns +nav_order: 1 +--- + + +# NestedOptionals + +FragileCode + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# NestedOptionals + +__Avoid nesting `Optional`s inside `Optional`s; the resultant code is hard to reason about__ + +
+ + +
SeverityWARNING
TagsFragileCode
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("NestedOptionals")` to the enclosing element. diff --git a/docs/bugpatterns/NonEmptyMono.md b/docs/bugpatterns/NonEmptyMono.md new file mode 100644 index 00000000..c99fc2d0 --- /dev/null +++ b/docs/bugpatterns/NonEmptyMono.md @@ -0,0 +1,42 @@ +--- +layout: default +title: NonEmptyMono +parent: Bug Patterns +nav_order: 1 +--- + + +# NonEmptyMono + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# NonEmptyMono + +__Avoid vacuous operations on known non-empty `Mono`s__ + +
+ + +
SeverityWARNING
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("NonEmptyMono")` to the enclosing element. diff --git a/docs/bugpatterns/PrimitiveComparison.md b/docs/bugpatterns/PrimitiveComparison.md new file mode 100644 index 00000000..0e3f7fc5 --- /dev/null +++ b/docs/bugpatterns/PrimitiveComparison.md @@ -0,0 +1,42 @@ +--- +layout: default +title: PrimitiveComparison +parent: Bug Patterns +nav_order: 1 +--- + + +# PrimitiveComparison + +Performance + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# PrimitiveComparison + +__Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type of the provided function__ + +
+ + +
SeverityWARNING
TagsPerformance
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("PrimitiveComparison")` to the enclosing element. diff --git a/docs/bugpatterns/RedundantStringConversion.md b/docs/bugpatterns/RedundantStringConversion.md new file mode 100644 index 00000000..872ca8ce --- /dev/null +++ b/docs/bugpatterns/RedundantStringConversion.md @@ -0,0 +1,42 @@ +--- +layout: default +title: RedundantStringConversion +parent: Bug Patterns +nav_order: 1 +--- + + +# RedundantStringConversion + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# RedundantStringConversion + +__Avoid redundant string conversions when possible__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("RedundantStringConversion")` to the enclosing element. diff --git a/docs/bugpatterns/RefasterAnyOfUsage.md b/docs/bugpatterns/RefasterAnyOfUsage.md new file mode 100644 index 00000000..281447a8 --- /dev/null +++ b/docs/bugpatterns/RefasterAnyOfUsage.md @@ -0,0 +1,42 @@ +--- +layout: default +title: RefasterAnyOfUsage +parent: Bug Patterns +nav_order: 1 +--- + + +# RefasterAnyOfUsage + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# RefasterAnyOfUsage + +__`Refaster#anyOf` should be passed at least two parameters__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("RefasterAnyOfUsage")` to the enclosing element. diff --git a/docs/bugpatterns/RequestMappingAnnotation.md b/docs/bugpatterns/RequestMappingAnnotation.md new file mode 100644 index 00000000..ac7c958c --- /dev/null +++ b/docs/bugpatterns/RequestMappingAnnotation.md @@ -0,0 +1,42 @@ +--- +layout: default +title: RequestMappingAnnotation +parent: Bug Patterns +nav_order: 1 +--- + + +# RequestMappingAnnotation + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# RequestMappingAnnotation + +__Make sure all `@RequestMapping` method parameters are annotated__ + +
+ + +
SeverityWARNING
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("RequestMappingAnnotation")` to the enclosing element. diff --git a/docs/bugpatterns/RequestParamType.md b/docs/bugpatterns/RequestParamType.md new file mode 100644 index 00000000..ea6f5253 --- /dev/null +++ b/docs/bugpatterns/RequestParamType.md @@ -0,0 +1,42 @@ +--- +layout: default +title: RequestParamType +parent: Bug Patterns +nav_order: 1 +--- + + +# RequestParamType + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# RequestParamType + +__`@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes__ + +
+ + +
SeverityERROR
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("RequestParamType")` to the enclosing element. diff --git a/docs/bugpatterns/ScheduledTransactionTrace.md b/docs/bugpatterns/ScheduledTransactionTrace.md new file mode 100644 index 00000000..4c31ad02 --- /dev/null +++ b/docs/bugpatterns/ScheduledTransactionTrace.md @@ -0,0 +1,42 @@ +--- +layout: default +title: ScheduledTransactionTrace +parent: Bug Patterns +nav_order: 1 +--- + + +# ScheduledTransactionTrace + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# ScheduledTransactionTrace + +__Scheduled operation must start a new New Relic transaction__ + +
+ + +
SeverityERROR
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("ScheduledTransactionTrace")` to the enclosing element. diff --git a/docs/bugpatterns/Slf4jLogStatement.md b/docs/bugpatterns/Slf4jLogStatement.md new file mode 100644 index 00000000..794a10e8 --- /dev/null +++ b/docs/bugpatterns/Slf4jLogStatement.md @@ -0,0 +1,42 @@ +--- +layout: default +title: Slf4jLogStatement +parent: Bug Patterns +nav_order: 1 +--- + + +# Slf4jLogStatement + +LikelyError + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# Slf4jLogStatement + +__Make sure SLF4J log statements contain proper placeholders with matching arguments__ + +
+ + +
SeverityWARNING
TagsLikelyError
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("Slf4jLogStatement")` to the enclosing element. diff --git a/docs/bugpatterns/SpringMvcAnnotation.md b/docs/bugpatterns/SpringMvcAnnotation.md new file mode 100644 index 00000000..27195acf --- /dev/null +++ b/docs/bugpatterns/SpringMvcAnnotation.md @@ -0,0 +1,42 @@ +--- +layout: default +title: SpringMvcAnnotation +parent: Bug Patterns +nav_order: 1 +--- + + +# SpringMvcAnnotation + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# SpringMvcAnnotation + +__Prefer the conciseness of `@{Get,Put,Post,Delete,Patch}Mapping` over `@RequestMapping`__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("SpringMvcAnnotation")` to the enclosing element. diff --git a/docs/bugpatterns/StaticImport.md b/docs/bugpatterns/StaticImport.md new file mode 100644 index 00000000..91c3bd69 --- /dev/null +++ b/docs/bugpatterns/StaticImport.md @@ -0,0 +1,42 @@ +--- +layout: default +title: StaticImport +parent: Bug Patterns +nav_order: 1 +--- + + +# StaticImport + +Simplification + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# StaticImport + +__Identifier should be statically imported__ + +
+ + +
SeveritySUGGESTION
TagsSimplification
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("StaticImport")` to the enclosing element. diff --git a/docs/bugpatterns/TimeZoneUsage.md b/docs/bugpatterns/TimeZoneUsage.md new file mode 100644 index 00000000..2c2190f8 --- /dev/null +++ b/docs/bugpatterns/TimeZoneUsage.md @@ -0,0 +1,42 @@ +--- +layout: default +title: TimeZoneUsage +parent: Bug Patterns +nav_order: 1 +--- + + +# TimeZoneUsage + +FragileCode + +${EXTRA_DOCS} + +## Samples + +\`\`\`java +public static void sample() {} +\`\`\` + + + View source code on GitHub + + + + +# TimeZoneUsage + +__Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone__ + +
+ + +
SeverityWARNING
TagsFragileCode
+ + + +## Suppression +Suppress false positives by adding the suppression annotation `@SuppressWarnings("TimeZoneUsage")` to the enclosing element. diff --git a/error-prone-contrib/pom.xml b/error-prone-contrib/pom.xml index b35abc27..d5aa4d83 100644 --- a/error-prone-contrib/pom.xml +++ b/error-prone-contrib/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -231,11 +232,57 @@ - ${project.groupId}:refaster-support + ${project.groupId}:refaster-support + + + + + + run-annotation-processor + + + ${project.groupId} + error_prone_docgen_processor + ${project.version} + + + com.google.guava + guava + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + com.google.auto.value + auto-value + ${version.auto-value} + + + com.google.auto.service + auto-service + ${version.auto-service} + + + ${project.groupId} + error_prone_docgen_processor + ${project.version} + + + + + + + + diff --git a/generate-docs.sh b/generate-docs.sh index 66afe97a..af3d90b4 100755 --- a/generate-docs.sh +++ b/generate-docs.sh @@ -39,44 +39,15 @@ EOF } generate_bugpattern_docs() { - BUGPATTERNS=$(find error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns -type f -iname "*.java" ! -iname "package-info.java") - for BUGPATTERN in $BUGPATTERNS; do - NAME=$(basename "${BUGPATTERN}" ".java") - FILENAME="${BUGPATTERN_FOLDER}/${NAME}.md" + # The "mvn clean" is necessary since the wiki docs are generated by an + # annotation processor that also compiles the code. If Maven thinks the code + # does not need to be recompiled, the wiki docs will not be generated either. + mvn clean - EXTRA_DOCS=$(cat "${BUGPATTERN_DOCS_FOLDER}/${NAME}.md" 2>/dev/null) + # This will create markdown files for each bug pattern in `docgen/target/generated-wiki/bugpatterns` + mvn -P run-annotation-processor compile site -Dverification.skip - echo "Generating ${FILENAME}" - cat > "${FILENAME}" << EOF ---- -layout: default -title: ${NAME} -parent: Bug Patterns -nav_order: 1 ---- - -# ${NAME} - -Simplification -{: .label .label-blue } - -Suggestion -{: .label .label-yellow } - -${EXTRA_DOCS} - -## Samples - -\`\`\`java -public static void sample() {} -\`\`\` - - - View source code on GitHub - - -EOF - done + cp -R docgen/target/generated-wiki/bugpatterns/ website/ } generate_refaster_docs() { diff --git a/pom.xml b/pom.xml index 7b6ff131..d76e730e 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,8 @@ + docgen + docgen_processor error-prone-contrib refaster-compiler refaster-runner diff --git a/website/Gemfile b/website/Gemfile index d5d1e8e2..511f5c89 100644 --- a/website/Gemfile +++ b/website/Gemfile @@ -1,3 +1,4 @@ source "https://rubygems.org" gem "github-pages", "~> 227" gem "rake", "~> 13.0" # Required for "just-the-docs" theme +gem "webrick", "~> 1.7"