diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 839fc984..88a9008b 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -39,26 +39,30 @@ jobs: www.bestpractices.dev:443 www.youtube.com:443 youtrack.jetbrains.com:443 - - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Check out code and set up JDK and Maven + uses: s4u/setup-maven-action@4f7fb9d9675e899ca81c6161dadbba0189a4ebb1 # v1.18.0 with: - persist-credentials: false + java-version: 17.0.13 + java-distribution: temurin + maven-version: 3.9.9 - uses: ruby/setup-ruby@1a615958ad9d422dd932dc1d5823942ee002799f # v1.227.0 with: working-directory: ./website bundler-cache: true - name: Configure Github Pages uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 + - name: Compile project and extract data + run: mvn -T1C clean install -DskipTests -Dverification.skip - name: Generate documentation - run: ./generate-docs.sh + run: mvn exec:java@generate-docs -pl documentation-support - name: Build website with Jekyll working-directory: ./website run: bundle exec jekyll build - name: Validate HTML output working-directory: ./website - # XXX: Drop `--disable_external true` once we fully adopted the - # "Refaster rules" terminology on our website and in the code. - run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site + # XXX: Bealdung and StackOverflow return HTTP 403 responses when run on + # a GitHub Action node. + run: bundle exec htmlproofer --no-check-external-hash --swap-url 'https\://error-prone.picnic.tech:' --ignore-urls '/^https:\/\/(www\.baeldung\.com|stackoverflow\.com)\/.*/' ./_site - name: Upload website as artifact uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 with: diff --git a/documentation-support/pom.xml b/documentation-support/pom.xml index 4c29bb57..45ebb996 100644 --- a/documentation-support/pom.xml +++ b/documentation-support/pom.xml @@ -28,10 +28,18 @@ com.fasterxml.jackson.core jackson-annotations + + com.fasterxml.jackson.core + jackson-core + com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + com.fasterxml.jackson.datatype jackson-datatype-guava @@ -76,6 +84,10 @@ com.google.guava guava + + io.github.java-diff-utils + java-diff-utils + org.assertj assertj-core @@ -104,4 +116,29 @@ test + + + + + + org.codehaus.mojo + exec-maven-plugin + + + generate-docs + + java + + + tech.picnic.errorprone.documentation.JekyllCollectionGenerator + + ${maven.multiModuleProjectDirectory} + + + + + + + + diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/JekyllCollectionGenerator.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/JekyllCollectionGenerator.java new file mode 100644 index 00000000..0482b776 --- /dev/null +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/JekyllCollectionGenerator.java @@ -0,0 +1,332 @@ +package tech.picnic.errorprone.documentation; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.ImmutableTable.toImmutableTable; +import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import com.github.difflib.DiffUtils; +import com.github.difflib.UnifiedDiffUtils; +import com.github.difflib.patch.Patch; +import com.google.auto.value.AutoValue; +import com.google.common.base.Function; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Sets; +import com.google.errorprone.BugPattern.SeverityLevel; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import org.jspecify.annotations.Nullable; +import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCase; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.IdentificationTestEntry; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.ReplacementTestEntry; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestEntry; +import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCase; +import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases; + +/** + * A command line utility that produces configuration files for the Jekyll-based Error Prone Support + * website. + */ +// XXX: Expand the class documentation. +// XXX: Rename this class. Then also update the reference in `website/.gitignore`. +// XXX: Now that we have bug checkers in multiple Maven modules, we should +// likely document the source of each check on the website, perhaps even +// grouping them by module. +public final class JekyllCollectionGenerator { + // XXX: Find a bette name. Also, externalize this. + private static final PathMatcher PATH_MATCHER = + FileSystems.getDefault().getPathMatcher("glob:**/target/docs/*.json"); + + // XXX: Review class setup. + private JekyllCollectionGenerator() {} + + /** + * Runs the application. + * + * @param args Arguments to the application; must specify the path to the Error Prone Support + * project root, and nothing else. + * @throws IOException If any file could not be read or written. + */ + public static void main(String[] args) throws IOException { + checkArgument(args.length == 1, "Precisely one project root path must be provided"); + Path projectRoot = Path.of(args[0]).toAbsolutePath(); + + generateIndex(projectRoot); + PageGenerator.apply(projectRoot); + } + + private static void generateIndex(Path projectRoot) throws IOException { + try (BufferedWriter writer = + Files.newBufferedWriter(projectRoot.resolve("website").resolve("index.md"), UTF_8)) { + writer.write("---"); + writer.newLine(); + writer.write("layout: default"); + writer.newLine(); + writer.write("title: Home"); + writer.newLine(); + writer.write("nav_order: 1"); + writer.newLine(); + writer.write("---"); + writer.newLine(); + writer.write( + Files.readString(projectRoot.resolve("README.md")).replace("=\"website/", "=\"")); + } + } + + // XXX: Review this class should be split in two: one for bug patterns and one for Refaster rules. + private static final class PageGenerator extends SimpleFileVisitor { + private static final Splitter LINE_SPLITTER = Splitter.on(System.lineSeparator()); + private static final YAMLMapper YAML_MAPPER = + YAMLMapper.builder() + .visibility(PropertyAccessor.FIELD, Visibility.ANY) + .disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS) + .build(); + + private final List bugPatterns = new ArrayList<>(); + private final List bugPatternTests = new ArrayList<>(); + private final List refasterRuleCollectionTests = new ArrayList<>(); + + static void apply(Path projectRoot) throws IOException { + PageGenerator pageGenerator = new PageGenerator(); + Files.walkFileTree(projectRoot, pageGenerator); + pageGenerator.writePages(projectRoot); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (!PATH_MATCHER.matches(file)) { + return FileVisitResult.CONTINUE; + } + + // XXX: If we use a consistent ID separator, then this can become a switch statement. Now we + // depend on evaluation order. + // XXX: Alternatively, use polymorphism and let Jackson figure it out. + // XXX: If we stick with an ID-based approach, then deduplicate the ID references here and in + // the `Extractor` implementations. + String fileName = file.getFileName().toString(); + if (fileName.startsWith("bugpattern-test")) { + bugPatternTests.add(Json.read(file, BugPatternTestCases.class)); + } else if (fileName.startsWith("bugpattern")) { + bugPatterns.add(Json.read(file, BugPatternDocumentation.class)); + } else if (fileName.startsWith("refaster-rule-collection-test")) { + refasterRuleCollectionTests.add(Json.read(file, RefasterTestCases.class)); + } else { + // XXX: Handle differently? + throw new IllegalStateException("Unexpected file: " + fileName); + } + + return FileVisitResult.CONTINUE; + } + + private void writePages(Path projectRoot) throws IOException { + Path website = projectRoot.resolve("website"); + writePages( + website.resolve("_bugpatterns"), + getJekyllBugPatternDescriptions(projectRoot), + JekyllBugPatternDescription::name); + writePages( + website.resolve("_refasterrules"), + getJekyllRefasterRuleCollectionDescription(), + JekyllRefasterRuleCollectionDescription::name); + } + + private static void writePages( + Path directory, ImmutableList documents, Function nameExtractor) + throws IOException { + for (T document : documents) { + Files.createDirectories(directory); + try (BufferedWriter writer = + Files.newBufferedWriter( + directory.resolve(nameExtractor.apply(document) + ".md"), UTF_8)) { + YAML_MAPPER.writeValue(writer, document); + writer.write("---"); + writer.newLine(); + } + } + } + + private ImmutableList getJekyllBugPatternDescriptions( + Path projectRoot) { + ImmutableListMultimap bugPatternTestCases = + bugPatternTests.stream() + .flatMap(testCases -> testCases.testCases().stream()) + .collect( + flatteningToImmutableListMultimap( + BugPatternTestCase::classUnderTest, t -> t.entries().stream())); + + return bugPatterns.stream() + .map( + b -> + new AutoValue_JekyllCollectionGenerator_JekyllBugPatternDescription( + b.name(), + b.name(), + b.summary(), + b.severityLevel(), + b.tags(), + // XXX: Derive `Path` from filesytem. + projectRoot.relativize(Path.of(b.source())).toString(), + bugPatternTestCases.get(b.fullyQualifiedName()).stream() + .filter(t -> t.type() == TestEntry.TestType.IDENTIFICATION) + .map(t -> ((IdentificationTestEntry) t).code()) + .collect(toImmutableList()), + bugPatternTestCases.get(b.fullyQualifiedName()).stream() + .filter(t -> t.type() == TestEntry.TestType.REPLACEMENT) + .map(t -> generateDiff((ReplacementTestEntry) t)) + .collect(toImmutableList()))) + .collect(toImmutableList()); + } + + private ImmutableList + getJekyllRefasterRuleCollectionDescription() { + ImmutableTable> refasterTests = + refasterRuleCollectionTests.stream() + .collect( + toImmutableTable( + RefasterTestCases::ruleCollection, + RefasterTestCases::isInput, + RefasterTestCases::testCases)); + + return refasterTests.rowMap().entrySet().stream() + .map( + c -> + new AutoValue_JekyllCollectionGenerator_JekyllRefasterRuleCollectionDescription( + c.getKey(), + c.getKey(), + // XXX: Derive severity from input. + SUGGESTION, + // XXX: Derive tags from input (or drop this feature). + ImmutableList.of("Simplification"), + // XXX: Derive source location from input. + String.format( + "error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/%s.java", + c.getKey()), + getRules(c.getValue().get(true), c.getValue().get(false)))) + .collect(toImmutableList()); + } + + private static ImmutableList getRules( + @Nullable List inputTests, @Nullable List outputTests) { + ImmutableMap inputs = indexRefasterTestData(inputTests); + ImmutableMap outputs = indexRefasterTestData(outputTests); + + return Sets.intersection(inputs.keySet(), outputs.keySet()).stream() + .map( + name -> + new AutoValue_JekyllCollectionGenerator_JekyllRefasterRuleCollectionDescription_Rule( + name, + // XXX: Derive severity from input. + SUGGESTION, + // XXX: Derive tags from input (or drop this feature). + ImmutableList.of("Simplification"), + generateDiff( + requireNonNull(inputs.get(name), "Input"), + requireNonNull(outputs.get(name), "Output")))) + .collect(toImmutableList()); + } + + private static ImmutableMap indexRefasterTestData( + @Nullable List data) { + return data == null + ? ImmutableMap.of() + : data.stream() + .collect(toImmutableMap(RefasterTestCase::name, RefasterTestCase::content)); + } + + private static String generateDiff(ReplacementTestEntry testEntry) { + return generateDiff(testEntry.input(), testEntry.output()); + } + + private static String generateDiff(String before, String after) { + // XXX: Extract splitter. + List originalLines = LINE_SPLITTER.splitToList(before); + List replacementLines = LINE_SPLITTER.splitToList(after); + + Patch diff = DiffUtils.diff(originalLines, replacementLines); + + return UnifiedDiffUtils.generateUnifiedDiff( + "", "", originalLines, diff, Integer.MAX_VALUE / 2) + .stream() + .skip(3) + .collect(joining(System.lineSeparator())); + } + } + + @AutoValue + abstract static class JekyllBugPatternDescription { + // XXX: Make this a derived property? + abstract String title(); + + abstract String name(); + + abstract String summary(); + + abstract SeverityLevel severity(); + + abstract ImmutableList tags(); + + // XXX: The documentation could link to the original test code. Perhaps even with the correct + // line numbers. + abstract String source(); + + // XXX: The `identification` and `replacement` fields have odd names. + abstract ImmutableList identification(); + + abstract ImmutableList replacement(); + } + + @AutoValue + abstract static class JekyllRefasterRuleCollectionDescription { + // XXX: Make this a derived property? + abstract String title(); + + abstract String name(); + + abstract SeverityLevel severity(); + + abstract ImmutableList tags(); + + // XXX: The documentation could link to the original test code. Perhaps even with the correct + // line numbers. If we do this, we should do the same for individual rules. + abstract String source(); + + abstract ImmutableList rules(); + + @AutoValue + abstract static class Rule { + abstract String name(); + + abstract SeverityLevel severity(); + + abstract ImmutableList tags(); + + abstract String diff(); + } + } +} diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/models/RefasterTemplateCollectionData.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/models/RefasterTemplateCollectionData.java new file mode 100644 index 00000000..3747c3fd --- /dev/null +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/models/RefasterTemplateCollectionData.java @@ -0,0 +1,27 @@ +package tech.picnic.errorprone.documentation.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; + +/** + * Object containing all data related to a Refaster template collection. This is solely used for + * serialization. + */ +// XXX: This class is not yet used. +@AutoValue +@JsonDeserialize(as = AutoValue_RefasterTemplateCollectionData.class) +abstract class RefasterTemplateCollectionData { + static RefasterTemplateCollectionData create( + String name, String description, String link, ImmutableList templates) { + return new AutoValue_RefasterTemplateCollectionData(name, description, link, templates); + } + + abstract String name(); + + abstract String description(); + + abstract String link(); + + abstract ImmutableList templates(); +} diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/models/RefasterTemplateData.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/models/RefasterTemplateData.java new file mode 100644 index 00000000..f3bce291 --- /dev/null +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/models/RefasterTemplateData.java @@ -0,0 +1,23 @@ +package tech.picnic.errorprone.documentation.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.BugPattern.SeverityLevel; + +// XXX: This class is not yet used. +@AutoValue +@JsonDeserialize(as = AutoValue_RefasterTemplateData.class) +abstract class RefasterTemplateData { + static RefasterTemplateData create( + String name, String description, String link, SeverityLevel severityLevel) { + return new AutoValue_RefasterTemplateData(name, description, link, severityLevel); + } + + abstract String name(); + + abstract String description(); + + abstract String link(); + + abstract SeverityLevel severityLevel(); +} diff --git a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/JekyllCollectionGeneratorTest.java b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/JekyllCollectionGeneratorTest.java new file mode 100644 index 00000000..547078cc --- /dev/null +++ b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/JekyllCollectionGeneratorTest.java @@ -0,0 +1,13 @@ +package tech.picnic.errorprone.documentation; + +import java.io.IOException; +import org.junit.jupiter.api.Test; + +// XXX: Implement tests. +final class JekyllCollectionGeneratorTest { + @Test + void foo() throws IOException { + JekyllCollectionGenerator.main( + new String[] {"/home/sschroevers/workspace/picnic/error-prone-support"}); + } +} diff --git a/generate-docs.sh b/generate-docs.sh deleted file mode 100755 index 1caf0f3d..00000000 --- a/generate-docs.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -set -e -u -o pipefail - -REPOSITORY_ROOT="$(git rev-parse --show-toplevel)" -WEBSITE_ROOT="${REPOSITORY_ROOT}/website" - -generate_homepage() { - local homepage="${WEBSITE_ROOT}/index.md" - - echo "Generating ${homepage}..." - cat - "${REPOSITORY_ROOT}/README.md" > "${homepage}" << EOF ---- -layout: default -title: Home -nav_order: 1 ---- -EOF - - local macos_compat="" - [[ "${OSTYPE}" == "darwin"* ]] && macos_compat="yes" - sed -i ${macos_compat:+".bak"} 's/src="website\//src="/g' "${homepage}" - sed -i ${macos_compat:+".bak"} 's/srcset="website\//srcset="/g' "${homepage}" -} - -# Generate the website. -generate_homepage diff --git a/pom.xml b/pom.xml index b144221c..da557c95 100644 --- a/pom.xml +++ b/pom.xml @@ -360,6 +360,11 @@ nullaway ${version.nullaway} + + io.github.java-diff-utils + java-diff-utils + 4.12 + io.micrometer micrometer-bom @@ -1807,6 +1812,11 @@ org.apache.maven.plugins maven-enforcer-plugin + + org.codehaus.mojo + exec-maven-plugin + 3.4.1 + org.codehaus.mojo license-maven-plugin diff --git a/website/.gitignore b/website/.gitignore index 7caf6d3d..9bd53d5a 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -1,12 +1,13 @@ # Generated by Bundler and Jekyll. .bundle/ -Gemfile.lock .jekyll-cache/ .jekyll-metadata .sass-cache/ _site/ vendor/ -# Generated by `../generate-docs.sh`. -*.bak +# XXX: Update generator name when it's renamed. +# Generated by `JekyllCollectionGenerator`. index.md +_bugpatterns/ +_refasterrules/ diff --git a/website/.ruby-version b/website/.ruby-version index ef538c28..fa7adc7a 100644 --- a/website/.ruby-version +++ b/website/.ruby-version @@ -1 +1 @@ -3.1.2 +3.3.5 diff --git a/website/Gemfile b/website/Gemfile index 734e7a14..2d652b4b 100644 --- a/website/Gemfile +++ b/website/Gemfile @@ -1,8 +1,27 @@ ruby File.read(".ruby-version").strip source "https://rubygems.org" -gem "html-proofer", "4.4.1" -gem "jekyll", "4.2.2" -gem "jekyll-sitemap", "1.4" -gem "just-the-docs", "0.4.0.rc2" -gem "webrick", "1.7" + +# XXX: This dependency group is declared to suppress Ruby depreciation +# warnings. Drop once redundant. +group :standard_library_replacements do + gem "base64", "0.2.0" + gem "csv", "3.3.0" + gem "logger", "1.6.1" +end + +group :jekyll_site_dependencies do + gem "jekyll", "4.3.4" + gem "jekyll-sitemap", "1.4.0" + gem "just-the-docs", "0.10.0" + gem "rake", "13.2.1" + # XXX: Drop this `sass-embedded` version pinning once various parts of the + # ecosystem caught up. See + # https://github.com/just-the-docs/just-the-docs/issues/1541#issuecomment-2401649789. + gem "sass-embedded", "1.78.0" + gem "webrick", "1.9.0" +end + +group :website_validation_dependencies do + gem "html-proofer", "5.0.9" +end diff --git a/website/Gemfile.lock b/website/Gemfile.lock new file mode 100644 index 00000000..0625ee1b --- /dev/null +++ b/website/Gemfile.lock @@ -0,0 +1,211 @@ +GEM + remote: https://rubygems.org/ + specs: + Ascii85 (2.0.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + afm (0.2.2) + async (2.18.0) + console (~> 1.26) + fiber-annotation + io-event (~> 1.6, >= 1.6.5) + base64 (0.2.0) + bigdecimal (3.1.8) + colorator (1.1.0) + concurrent-ruby (1.3.4) + console (1.27.0) + fiber-annotation + fiber-local (~> 1.1) + json + csv (3.3.0) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + ffi (1.17.0-aarch64-linux-gnu) + ffi (1.17.0-aarch64-linux-musl) + ffi (1.17.0-arm-linux-gnu) + ffi (1.17.0-arm-linux-musl) + ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86-linux-gnu) + ffi (1.17.0-x86-linux-musl) + ffi (1.17.0-x86_64-darwin) + ffi (1.17.0-x86_64-linux-gnu) + ffi (1.17.0-x86_64-linux-musl) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.0) + forwardable-extended (2.6.0) + google-protobuf (4.28.3) + bigdecimal + rake (>= 13) + google-protobuf (4.28.3-aarch64-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.28.3-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.28.3-x86-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.28.3-x86_64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.28.3-x86_64-linux) + bigdecimal + rake (>= 13) + hashery (2.1.2) + html-proofer (5.0.9) + addressable (~> 2.3) + async (~> 2.1) + nokogiri (~> 1.13) + pdf-reader (~> 2.11) + rainbow (~> 3.0) + typhoeus (~> 1.3) + yell (~> 2.0) + zeitwerk (~> 2.5) + http_parser.rb (0.8.0) + i18n (1.14.6) + concurrent-ruby (~> 1.0) + io-event (1.7.3) + jekyll (4.3.4) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + json (2.7.5) + just-the-docs (0.10.0) + jekyll (>= 3.8.5) + jekyll-include-cache + jekyll-seo-tag (>= 2.0) + rake (>= 12.3.1) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.1) + mercenary (0.4.0) + nokogiri (1.16.7-aarch64-linux) + racc (~> 1.4) + nokogiri (1.16.7-arm-linux) + racc (~> 1.4) + nokogiri (1.16.7-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86-linux) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-linux) + racc (~> 1.4) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + pdf-reader (2.13.0) + Ascii85 (>= 1.0, < 3.0, != 2.0.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk + public_suffix (6.0.1) + racc (1.8.1) + rainbow (3.1.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.3.9) + rouge (4.4.0) + ruby-rc4 (0.1.5) + safe_yaml (1.0.5) + sass-embedded (1.78.0-aarch64-linux-gnu) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-aarch64-linux-musl) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-arm-linux-gnueabihf) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-arm-linux-musleabihf) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-arm64-darwin) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-x86-linux-gnu) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-x86-linux-musl) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-x86_64-darwin) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-x86_64-linux-gnu) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-x86_64-linux-musl) + google-protobuf (~> 4.27) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + ttfunk (1.8.0) + bigdecimal (~> 3.1) + typhoeus (1.4.1) + ethon (>= 0.9.0) + unicode-display_width (2.6.0) + webrick (1.9.0) + yell (2.2.2) + zeitwerk (2.7.1) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux + arm-linux-gnu + arm-linux-gnueabihf + arm-linux-musl + arm-linux-musleabihf + arm64-darwin + x86-linux + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + base64 (= 0.2.0) + csv (= 3.3.0) + html-proofer (= 5.0.9) + jekyll (= 4.3.4) + jekyll-sitemap (= 1.4.0) + just-the-docs (= 0.10.0) + logger (= 1.6.1) + rake (= 13.2.1) + sass-embedded (= 1.78.0) + webrick (= 1.9.0) + +RUBY VERSION + ruby 3.3.5p100 + +BUNDLED WITH + 2.5.16 diff --git a/website/README.md b/website/README.md index 49111ad1..b22bb7c4 100644 --- a/website/README.md +++ b/website/README.md @@ -7,18 +7,30 @@ statically generated using [Jekyll][jekyll]. # Local development To view the website on `localhost`, first follow the [Jekyll installation -instructions][jekyll-docs-installation]. Once done, in this directory execute: +instructions][jekyll-docs-installation]. Once done, run the following Maven +commands in the root of the repository to extract the (test) data from the bug +patterns and Refaster rule collections and to transform this data into a +Jekyll-digestible format. Unless and relevant Java code has been changed, these +commands needs to be executed once. + +```sh +mvn -T1C clean install -DskipTests -Dverification.skip +mvn exec:java@generate-docs -pl documentation-support +``` + +Then to build the website for local development, execute in this directory: ```sh bundle install -../generate-docs.sh && bundle exec jekyll serve --livereload +bundle exec jekyll serve --livereload ``` The website will now be [available][localhost-port-4000] on port 4000. Source -code modifications (including the result of rerunning `../generate-docs.sh`) -will automatically be reflected. (An exception is `_config.yml`: changes to -this file require a server restart.) Subsequent server restarts do not require -running `bundle install`, unless `Gemfile` has been updated in the interim. +code modifications (including the result of rerunning `mvn +exec:java@generate-docs -pl documentation-support`) will automatically be +reflected. (An exception is `_config.yml`: changes to this file require a +server restart.) Subsequent server restarts do not require running `bundle +install`, unless `Gemfile` has been updated in the interim. If you are not familiar with Jekyll, be sure to check out its [documentation][jekyll-docs]. It is recommended to follow the provided diff --git a/website/_config.yml b/website/_config.yml index ac900637..c0c53f71 100644 --- a/website/_config.yml +++ b/website/_config.yml @@ -8,15 +8,40 @@ description: >- theme: just-the-docs plugins: - - jekyll-sitemap +- jekyll-sitemap # Files and directories not to be deployed through GitHub pages. exclude: - - Gemfile - - Gemfile.lock - - generate-version-compatibility-overview.sh - - README.md - - vendor +- Gemfile +- Gemfile.lock +- generate-version-compatibility-overview.sh +- README.md +- vendor + +collections: + bugpatterns: + output: true + refasterrules: + output: true + +defaults: +- scope: + type: "bugpatterns" + values: + layout: "bugpattern" +- scope: + type: "refasterrules" + values: + layout: "refasterrule" + +just_the_docs: + collections: + bugpatterns: + name: Bug Patterns + nav_fold: true + refasterrules: + name: Refaster Rules + nav_fold: true # See https://jekyllrb.com/docs/permalinks/#built-in-formats. permalink: pretty @@ -25,9 +50,9 @@ permalink: pretty # See # https://just-the-docs.github.io/just-the-docs/docs/navigation-structure/#external-navigation-links. nav_external_links: - - title: Error Prone Support on GitHub - url: https://github.com/PicnicSupermarket/error-prone-support - hide_icon: false +- title: Error Prone Support on GitHub + url: https://github.com/PicnicSupermarket/error-prone-support + hide_icon: false callouts: summary: @@ -40,9 +65,9 @@ callouts: social: name: Picnic links: - - https://github.com/PicnicSupermarket - - https://twitter.com/picnic - - https://www.linkedin.com/company/picnictechnologies + - https://github.com/PicnicSupermarket + - https://twitter.com/picnic + - https://www.linkedin.com/company/picnictechnologies twitter: username: picnic card: summary diff --git a/website/_data/severities.yml b/website/_data/severities.yml new file mode 100644 index 00000000..7c0749ba --- /dev/null +++ b/website/_data/severities.yml @@ -0,0 +1,6 @@ +ERROR: + color: red +WARNING: + color: yellow +SUGGESTION: + color: green diff --git a/website/_layouts/bugpattern.md b/website/_layouts/bugpattern.md new file mode 100644 index 00000000..e11239b5 --- /dev/null +++ b/website/_layouts/bugpattern.md @@ -0,0 +1,94 @@ +--- +# XXX: To be implemented: +# - Support for alt names. +# - Support for explanations. +# - Support for "can disable". +# - Support for custom suppression annotations. +layout: default +--- + +{% capture markdown_layout %} + +# {{ page.name }} + +{{ page.severity }} + {: .label .label-{{ site.data.severities[page.severity].color }} } + +{% for tag in page.tags %} +{{ tag }} + {: .label } +{% endfor %} + + + View source code on GitHub + + + + + +{: .summary-title } +> Summary +> +> {{ page.summary }} + +{% comment %} + # XXX: Here, include a more elaborate explantion, if available. +{% endcomment %} + +{: .note-title } +> Suppression +> +> Suppress false positives by adding the suppression annotation `@SuppressWarnings("{{ page.name }}")` to +> the enclosing element. +> +> Disable this pattern completely by adding `-Xep:{{ page.name }}:OFF` as compiler argument. +> [Learn more][error-prone-flags]. +{% comment %} + # XXX: Create an internal page on documenting the usage of compiler flags. +{% endcomment %} + +{% if page.replacement or page.identification %} + +## Samples + +{% comment %} + # XXX: Either make this "Samples" header useful, or drop it. (In which case + # the wrapping conjunctive guard should also go.) +{% endcomment %} + +{% if page.replacement %} + +### Replacement + +Shows the difference in example code before and after the bug pattern is +applied. + +{% for diff in page.replacement %} +{% highlight diff %} +{{ diff }} +{% endhighlight %} +{% endfor %} + +{% endif %} + +{% if page.identification %} + +### Identification + +Shows code lines which will (not) be flagged by this bug pattern. \ +A `//BUG: Diagnostic contains:` comment is placed above any violating line. + +{% for source in page.identification %} +{% highlight java %} +{{ source }} +{% endhighlight %} +{% endfor %} + +{% endif %} + +{% endif %} + +[error-prone-flags]: https://errorprone.info/docs/flags + +{% endcapture %} +{{ markdown_layout | markdownify }} diff --git a/website/_layouts/refasterrule.md b/website/_layouts/refasterrule.md new file mode 100644 index 00000000..4afc86f5 --- /dev/null +++ b/website/_layouts/refasterrule.md @@ -0,0 +1,78 @@ +--- +layout: default +--- + +{% capture markdown_layout %} + +# {{ page.name }} +{: .no_toc } + +{{ page.severity }} + {: .label .label-{{ site.data.severities[page.severity].color }} } + +{% for tag in page.tags %} +{{ tag }} + {: .label } +{% endfor %} + + + View source code on GitHub + + + + + +{: .note-title } +> Suppression +> +> Disable all rules by adding `-XepOpt:Refaster:NamePattern=^(?!{{page.name}}\$).*` as +> compiler argument. +{% comment %} + # XXX: Create an internal page on documenting the usage of compiler flags. +{% endcomment %} + +
+ + Table of contents + + {: .text-delta } + 1. TOC + {:toc} +
+ +{% for rule in page.rules %} +## {{rule.name}} + +{{ page.severity }} + {: .label .label-{{ site.data.severities[rule.severity].color }} } + +{% for tag in rule.tags %} +{{ tag }} + {: .label } +{% endfor %} + +{: .note-title } +> Suppression +> +> Suppress false positives by adding the suppression annotation `@SuppressWarnings("{{rule.name}}")` to +> the enclosing element. +> +> Disable this rule by adding `-XepOpt:Refaster:NamePattern=^(?!{{page.name}}\${{rule.name}}).*` +> as compiler argument. +{% comment %} + # XXX: Create an internal page on documenting the usage of compiler flags. +{% endcomment %} + +### Samples +{: .no_toc .text-delta } + +Shows the difference in example code before and after the Refaster rule is +applied. + +{% highlight diff %} +{{ rule.diff }} +{% endhighlight %} +{% endfor %} + +{% endcapture %} +{{ markdown_layout | markdownify }} diff --git a/website/_sass/color_schemes/_variables.scss b/website/_sass/color_schemes/_variables.scss index fe5b7683..e8ccfe85 100644 --- a/website/_sass/color_schemes/_variables.scss +++ b/website/_sass/color_schemes/_variables.scss @@ -2,4 +2,4 @@ // https://github.com/just-the-docs/just-the-docs/blob/main/_sass/support/_variables.scss. // Grid system. -$nav-width: 400px; +$nav-width: 25rem; diff --git a/website/bugpatterns.md b/website/bugpatterns.md deleted file mode 100644 index 51ce7bf7..00000000 --- a/website/bugpatterns.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: default -title: Bug Patterns -nav_order: 2 -has_children: true ---- - -# Bug Patterns diff --git a/website/refasterrules.md b/website/refasterrules.md deleted file mode 100644 index bf1d43dc..00000000 --- a/website/refasterrules.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: default -title: Refaster Rules -nav_order: 2 -has_children: true ---- - -# Refaster Rules