Skip to content

Commit 2f619ad

Browse files
committed
Improve checkstyle performance and readability (#54308)
Drop a nasty regex in our checkstyle config that I wrote a long time ago in favor of a checkstyle extension. This is better because: * It is faster. It saves a little more than a minute across the entire build. * It is easier to read. Who knew 100 lines of Java would be easier to read than a regex, but it is. * It has tests.
1 parent 2ca8850 commit 2f619ad

File tree

7 files changed

+211
-7
lines changed

7 files changed

+211
-7
lines changed

buildSrc/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ dependencies {
121121
compile 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
122122
compile 'de.thetaphi:forbiddenapis:2.7'
123123
compile 'com.avast.gradle:gradle-docker-compose-plugin:0.8.12'
124+
compileOnly "com.puppycrawl.tools:checkstyle:${props.getProperty('checkstyle')}"
125+
testCompile "com.puppycrawl.tools:checkstyle:${props.getProperty('checkstyle')}"
124126
testCompile "junit:junit:${props.getProperty('junit')}"
125127
testCompile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${props.getProperty('randomizedrunner')}"
126128
testCompile 'com.github.tomakehurst:wiremock-jre8-standalone:2.23.2'

buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import de.thetaphi.forbiddenapis.gradle.ForbiddenApisPlugin
2424
import org.elasticsearch.gradle.ExportElasticsearchBuildResourcesTask
2525
import org.elasticsearch.gradle.VersionProperties
2626
import org.elasticsearch.gradle.info.BuildParams
27+
import org.elasticsearch.gradle.util.Util
2728
import org.gradle.api.JavaVersion
2829
import org.gradle.api.Project
2930
import org.gradle.api.artifacts.Configuration
@@ -38,8 +39,6 @@ class PrecommitTasks {
3839

3940
/** Adds a precommit task, which depends on non-test verification tasks. */
4041

41-
public static final String CHECKSTYLE_VERSION = '8.20'
42-
4342
public static TaskProvider create(Project project, boolean includeDependencyLicenses) {
4443
project.configurations.create("forbiddenApisCliJar")
4544
project.dependencies {
@@ -232,7 +231,10 @@ class PrecommitTasks {
232231
project.pluginManager.apply('checkstyle')
233232
project.checkstyle {
234233
configDir = checkstyleDir
235-
toolVersion = CHECKSTYLE_VERSION
234+
}
235+
project.dependencies {
236+
checkstyle "com.puppycrawl.tools:checkstyle:${VersionProperties.versions.checkstyle}"
237+
checkstyle project.files(Util.buildSrcCodeSource)
236238
}
237239

238240
project.tasks.withType(Checkstyle).configureEach { task ->
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.gradle.checkstyle;
21+
22+
import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
23+
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
24+
import com.puppycrawl.tools.checkstyle.api.FileText;
25+
26+
import java.io.File;
27+
import java.util.Arrays;
28+
import java.util.Iterator;
29+
import java.util.function.BiConsumer;
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
32+
33+
/**
34+
* Checks the snippets included in the docs aren't too wide to fit on
35+
* the page.
36+
*/
37+
public class SnippetLengthCheck extends AbstractFileSetCheck {
38+
private static final Pattern START = Pattern.compile("^( *)//\\s*tag::(.+?)\\s*$", Pattern.MULTILINE);
39+
private int max;
40+
41+
/**
42+
* The maximum width that a snippet may have.
43+
*/
44+
public void setMax(int max) {
45+
this.max = max;
46+
}
47+
48+
@Override
49+
protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
50+
checkFile((line, message) -> log(line, message), max, fileText.toLinesArray());
51+
}
52+
53+
static void checkFile(BiConsumer<Integer, String> log, int max, String... lineArray) {
54+
LineItr lines = new LineItr(Arrays.asList(lineArray).iterator());
55+
while (lines.hasNext()) {
56+
Matcher m = START.matcher(lines.next());
57+
if (m.matches()) {
58+
checkSnippet(log, max, lines, m.group(1), m.group(2));
59+
}
60+
}
61+
}
62+
63+
private static void checkSnippet(BiConsumer<Integer, String> log, int max, LineItr lines, String leadingSpaces, String name) {
64+
Pattern end = Pattern.compile("^ *//\\s*end::" + name + "\\s*$", Pattern.MULTILINE);
65+
while (lines.hasNext()) {
66+
String line = lines.next();
67+
if (end.matcher(line).matches()) {
68+
return;
69+
}
70+
if (line.isEmpty()) {
71+
continue;
72+
}
73+
if (false == line.startsWith(leadingSpaces)) {
74+
log.accept(lines.lastLineNumber, "snippet line should start with [" + leadingSpaces + "]");
75+
continue;
76+
}
77+
int width = line.length() - leadingSpaces.length();
78+
if (width > max) {
79+
log.accept(lines.lastLineNumber, "snippet line should be no more than [" + max + "] characters but was [" + width + "]");
80+
}
81+
}
82+
}
83+
84+
private static class LineItr implements Iterator<String> {
85+
private final Iterator<String> delegate;
86+
private int lastLineNumber;
87+
88+
LineItr(Iterator<String> delegate) {
89+
this.delegate = delegate;
90+
}
91+
92+
@Override
93+
public boolean hasNext() {
94+
return delegate.hasNext();
95+
}
96+
97+
@Override
98+
public String next() {
99+
lastLineNumber++;
100+
return delegate.next();
101+
}
102+
}
103+
}

buildSrc/src/main/java/org/elasticsearch/gradle/util/Util.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.io.IOException;
2727
import java.io.InputStreamReader;
2828
import java.io.UncheckedIOException;
29+
import java.net.URI;
30+
import java.net.URISyntaxException;
2931
import java.util.Locale;
3032

3133
public class Util {
@@ -65,4 +67,12 @@ public static String getResourceContents(String resourcePath) {
6567
public static String capitalize(String s) {
6668
return s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1);
6769
}
70+
71+
public static URI getBuildSrcCodeSource() {
72+
try {
73+
return Util.class.getProtectionDomain().getCodeSource().getLocation().toURI();
74+
} catch (URISyntaxException e) {
75+
throw new GradleException("Error determining build tools JAR location", e);
76+
}
77+
}
6878
}

buildSrc/src/main/resources/checkstyle.xml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@
2424
characters then it'll need to scroll. This fails the build if it sees
2525
such snippets.
2626
-->
27-
<module name="RegexpMultiline">
27+
<module name="org.elasticsearch.gradle.checkstyle.SnippetLengthCheck">
2828
<property name="id" value="SnippetLength"/>
29-
<property name="format" value="^( *?)//\s*?tag(.+?)\s*?\n(.*?\n)*?\1.{77,}?\n(.*?\n)*?\1//\s*?end\2\s*$"/>
30-
<property name="fileExtensions" value="java"/>
31-
<property name="message" value="Code snippets longer than 76 characters get cut off when rendered in the docs"/>
29+
<property name="max" value="76"/>
3230
</module>
3331

3432
<module name="TreeWalker">
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.gradle.checkstyle;
21+
22+
import org.elasticsearch.gradle.test.GradleUnitTestCase;
23+
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.List;
27+
import java.util.function.BiConsumer;
28+
29+
import static java.util.Collections.singletonList;
30+
import static org.hamcrest.CoreMatchers.equalTo;
31+
32+
public class SnipptLengthCheckTests extends GradleUnitTestCase {
33+
public void testNoSnippets() {
34+
SnippetLengthCheck.checkFile(failOnError(), 10, "There a no snippets");
35+
}
36+
37+
public void testEmptySnippet() {
38+
SnippetLengthCheck.checkFile(failOnError(), 10, "// tag::foo", "// end::foo");
39+
}
40+
41+
public void testSnippetWithSmallText() {
42+
SnippetLengthCheck.checkFile(failOnError(), 10, "// tag::foo", "some words", "// end::foo");
43+
}
44+
45+
public void testSnippetWithLeadingSpaces() {
46+
SnippetLengthCheck.checkFile(failOnError(), 10, " // tag::foo", " some words", " // end::foo");
47+
}
48+
49+
public void testSnippetWithEmptyLine() {
50+
SnippetLengthCheck.checkFile(failOnError(), 10, " // tag::foo", "", " some words", " // end::foo");
51+
}
52+
53+
public void testSnippetBrokenLeadingSpaces() {
54+
List<String> collection = new ArrayList<>();
55+
SnippetLengthCheck.checkFile(collect(collection), 10, " // tag::foo", "some words", " // end::foo");
56+
assertThat(collection, equalTo(singletonList("2: snippet line should start with [ ]")));
57+
}
58+
59+
public void testSnippetTooLong() {
60+
List<String> collection = new ArrayList<>();
61+
SnippetLengthCheck.checkFile(collect(collection), 10, " // tag::foo", " too long words", " // end::foo");
62+
assertThat(collection, equalTo(singletonList("2: snippet line should be no more than [10] characters but was [14]")));
63+
}
64+
65+
public void testLotsOfErrors() {
66+
List<String> collection = new ArrayList<>();
67+
SnippetLengthCheck.checkFile(collect(collection), 10, " // tag::foo", "asdfadf", " too long words", "asdfadf", " // end::foo");
68+
assertThat(
69+
collection,
70+
equalTo(
71+
Arrays.asList(
72+
"2: snippet line should start with [ ]",
73+
"3: snippet line should be no more than [10] characters but was [14]",
74+
"4: snippet line should start with [ ]"
75+
)
76+
)
77+
);
78+
}
79+
80+
private BiConsumer<Integer, String> failOnError() {
81+
return (line, message) -> fail("checkstyle error on line [" + line + "] with message [" + message + "]");
82+
}
83+
84+
private BiConsumer<Integer, String> collect(List<String> collection) {
85+
return (line, message) -> collection.add(line + ": " + message);
86+
}
87+
}

buildSrc/version.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ lucene = 8.5.0
44
bundled_jdk_vendor = adoptopenjdk
55
bundled_jdk = 14+36
66

7+
checkstyle = 8.20
8+
79
# optional dependencies
810
spatial4j = 0.7
911
jts = 1.15.0

0 commit comments

Comments
 (0)