Skip to content

Commit e5940ee

Browse files
cdschneideralpar-t
authored andcommitted
converting ForbiddenPatternsTask to .java (#36194)
* converting ForbiddenPatternsTask to java impl & unit tests
1 parent 156e193 commit e5940ee

File tree

7 files changed

+291
-131
lines changed

7 files changed

+291
-131
lines changed

buildSrc/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ if (project != rootProject) {
223223
forbiddenPatterns {
224224
exclude '**/*.wav'
225225
// the file that actually defines nocommit
226-
exclude '**/ForbiddenPatternsTask.groovy'
226+
exclude '**/ForbiddenPatternsTask.java'
227227
}
228228

229229
namingConventions {

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

-130
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
package org.elasticsearch.gradle.precommit;
20+
21+
import org.gradle.api.DefaultTask;
22+
import org.gradle.api.GradleException;
23+
import org.gradle.api.InvalidUserDataException;
24+
import org.gradle.api.file.FileCollection;
25+
import org.gradle.api.file.FileTree;
26+
import org.gradle.api.plugins.JavaPluginConvention;
27+
import org.gradle.api.tasks.Input;
28+
import org.gradle.api.tasks.InputFiles;
29+
import org.gradle.api.tasks.OutputFile;
30+
import org.gradle.api.tasks.SkipWhenEmpty;
31+
import org.gradle.api.tasks.TaskAction;
32+
import org.gradle.api.tasks.util.PatternFilterable;
33+
import org.gradle.api.tasks.util.PatternSet;
34+
35+
import java.io.File;
36+
import java.io.IOException;
37+
import java.io.UncheckedIOException;
38+
import java.nio.charset.StandardCharsets;
39+
import java.nio.file.Files;
40+
import java.util.AbstractMap;
41+
import java.util.ArrayList;
42+
import java.util.Collections;
43+
import java.util.HashMap;
44+
import java.util.List;
45+
import java.util.Map;
46+
import java.util.regex.Pattern;
47+
import java.util.stream.Collectors;
48+
import java.util.stream.IntStream;
49+
import java.util.stream.Stream;
50+
51+
/**
52+
* Checks for patterns in source files for the project which are forbidden.
53+
*/
54+
public class ForbiddenPatternsTask extends DefaultTask {
55+
56+
/*
57+
* A pattern set of which files should be checked.
58+
*/
59+
private final PatternFilterable filesFilter = new PatternSet()
60+
// we always include all source files, and exclude what should not be checked
61+
.include("**")
62+
// exclude known binary extensions
63+
.exclude("**/*.gz")
64+
.exclude("**/*.ico")
65+
.exclude("**/*.jar")
66+
.exclude("**/*.zip")
67+
.exclude("**/*.jks")
68+
.exclude("**/*.crt")
69+
.exclude("**/*.png");
70+
71+
/*
72+
* The rules: a map from the rule name, to a rule regex pattern.
73+
*/
74+
private final Map<String, String> patterns = new HashMap<>();
75+
76+
public ForbiddenPatternsTask() {
77+
setDescription("Checks source files for invalid patterns like nocommits or tabs");
78+
getInputs().property("excludes", filesFilter.getExcludes());
79+
getInputs().property("rules", patterns);
80+
81+
// add mandatory rules
82+
patterns.put("nocommit", "nocommit|NOCOMMIT");
83+
patterns.put("nocommit should be all lowercase or all uppercase", "((?i)nocommit)(?<!(nocommit|NOCOMMIT))");
84+
patterns.put("tab", "\t");
85+
}
86+
87+
@InputFiles
88+
@SkipWhenEmpty
89+
public FileCollection files() {
90+
return getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
91+
.stream()
92+
.map(sourceSet -> sourceSet.getAllSource().matching(filesFilter))
93+
.reduce(FileTree::plus)
94+
.orElse(getProject().files().getAsFileTree());
95+
}
96+
97+
@TaskAction
98+
public void checkInvalidPatterns() throws IOException {
99+
Pattern allPatterns = Pattern.compile("(" + String.join(")|(", getPatterns().values()) + ")");
100+
List<String> failures = new ArrayList<>();
101+
for (File f : files()) {
102+
List<String> lines;
103+
try(Stream<String> stream = Files.lines(f.toPath(), StandardCharsets.UTF_8)) {
104+
lines = stream.collect(Collectors.toList());
105+
} catch (UncheckedIOException e) {
106+
throw new IllegalArgumentException("Failed to read " + f + " as UTF_8", e);
107+
}
108+
List<Integer> invalidLines = IntStream.range(0, lines.size())
109+
.filter(i -> allPatterns.matcher(lines.get(i)).find())
110+
.boxed()
111+
.collect(Collectors.toList());
112+
113+
String path = getProject().getRootProject().getProjectDir().toURI().relativize(f.toURI()).toString();
114+
failures = invalidLines.stream()
115+
.map(l -> new AbstractMap.SimpleEntry<>(l+1, lines.get(l)))
116+
.flatMap(kv -> patterns.entrySet().stream()
117+
.filter(p -> Pattern.compile(p.getValue()).matcher(kv.getValue()).find())
118+
.map(p -> "- " + p.getKey() + " on line " + kv.getKey() + " of " + path)
119+
)
120+
.collect(Collectors.toList());
121+
}
122+
if (failures.isEmpty() == false) {
123+
throw new GradleException("Found invalid patterns:\n" + String.join("\n", failures));
124+
}
125+
126+
File outputMarker = getOutputMarker();
127+
outputMarker.getParentFile().mkdirs();
128+
Files.write(outputMarker.toPath(), "done".getBytes(StandardCharsets.UTF_8));
129+
}
130+
131+
@OutputFile
132+
public File getOutputMarker() {
133+
return new File(getProject().getBuildDir(), "markers/" + getName());
134+
}
135+
136+
@Input
137+
public Map<String, String> getPatterns() {
138+
return Collections.unmodifiableMap(patterns);
139+
}
140+
141+
public void exclude(String... excludes) {
142+
filesFilter.exclude(excludes);
143+
}
144+
145+
public void rule(Map<String,String> props) {
146+
String name = props.remove("name");
147+
if (name == null) {
148+
throw new InvalidUserDataException("Missing [name] for invalid pattern rule");
149+
}
150+
String pattern = props.remove("pattern");
151+
if (pattern == null) {
152+
throw new InvalidUserDataException("Missing [pattern] for invalid pattern rule");
153+
}
154+
if (props.isEmpty() == false) {
155+
throw new InvalidUserDataException("Unknown arguments for ForbiddenPatterns rule mapping: "
156+
+ props.keySet().toString());
157+
}
158+
// TODO: fail if pattern contains a newline, it won't work (currently)
159+
patterns.put(name, pattern);
160+
}
161+
}

0 commit comments

Comments
 (0)