Skip to content

Commit

Permalink
converting ForbiddenPatternsTask to .java (#36194)
Browse files Browse the repository at this point in the history
* converting ForbiddenPatternsTask to java impl & unit tests
  • Loading branch information
cdschneider authored and alpar-t committed Dec 11, 2018
1 parent 156e193 commit e5940ee
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 131 deletions.
2 changes: 1 addition & 1 deletion buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ if (project != rootProject) {
forbiddenPatterns {
exclude '**/*.wav'
// the file that actually defines nocommit
exclude '**/ForbiddenPatternsTask.groovy'
exclude '**/ForbiddenPatternsTask.java'
}

namingConventions {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.gradle.precommit;

import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileTree;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.util.PatternFilterable;
import org.gradle.api.tasks.util.PatternSet;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
* Checks for patterns in source files for the project which are forbidden.
*/
public class ForbiddenPatternsTask extends DefaultTask {

/*
* A pattern set of which files should be checked.
*/
private final PatternFilterable filesFilter = new PatternSet()
// we always include all source files, and exclude what should not be checked
.include("**")
// exclude known binary extensions
.exclude("**/*.gz")
.exclude("**/*.ico")
.exclude("**/*.jar")
.exclude("**/*.zip")
.exclude("**/*.jks")
.exclude("**/*.crt")
.exclude("**/*.png");

/*
* The rules: a map from the rule name, to a rule regex pattern.
*/
private final Map<String, String> patterns = new HashMap<>();

public ForbiddenPatternsTask() {
setDescription("Checks source files for invalid patterns like nocommits or tabs");
getInputs().property("excludes", filesFilter.getExcludes());
getInputs().property("rules", patterns);

// add mandatory rules
patterns.put("nocommit", "nocommit|NOCOMMIT");
patterns.put("nocommit should be all lowercase or all uppercase", "((?i)nocommit)(?<!(nocommit|NOCOMMIT))");
patterns.put("tab", "\t");
}

@InputFiles
@SkipWhenEmpty
public FileCollection files() {
return getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
.stream()
.map(sourceSet -> sourceSet.getAllSource().matching(filesFilter))
.reduce(FileTree::plus)
.orElse(getProject().files().getAsFileTree());
}

@TaskAction
public void checkInvalidPatterns() throws IOException {
Pattern allPatterns = Pattern.compile("(" + String.join(")|(", getPatterns().values()) + ")");
List<String> failures = new ArrayList<>();
for (File f : files()) {
List<String> lines;
try(Stream<String> stream = Files.lines(f.toPath(), StandardCharsets.UTF_8)) {
lines = stream.collect(Collectors.toList());
} catch (UncheckedIOException e) {
throw new IllegalArgumentException("Failed to read " + f + " as UTF_8", e);
}
List<Integer> invalidLines = IntStream.range(0, lines.size())
.filter(i -> allPatterns.matcher(lines.get(i)).find())
.boxed()
.collect(Collectors.toList());

String path = getProject().getRootProject().getProjectDir().toURI().relativize(f.toURI()).toString();
failures = invalidLines.stream()
.map(l -> new AbstractMap.SimpleEntry<>(l+1, lines.get(l)))
.flatMap(kv -> patterns.entrySet().stream()
.filter(p -> Pattern.compile(p.getValue()).matcher(kv.getValue()).find())
.map(p -> "- " + p.getKey() + " on line " + kv.getKey() + " of " + path)
)
.collect(Collectors.toList());
}
if (failures.isEmpty() == false) {
throw new GradleException("Found invalid patterns:\n" + String.join("\n", failures));
}

File outputMarker = getOutputMarker();
outputMarker.getParentFile().mkdirs();
Files.write(outputMarker.toPath(), "done".getBytes(StandardCharsets.UTF_8));
}

@OutputFile
public File getOutputMarker() {
return new File(getProject().getBuildDir(), "markers/" + getName());
}

@Input
public Map<String, String> getPatterns() {
return Collections.unmodifiableMap(patterns);
}

public void exclude(String... excludes) {
filesFilter.exclude(excludes);
}

public void rule(Map<String,String> props) {
String name = props.remove("name");
if (name == null) {
throw new InvalidUserDataException("Missing [name] for invalid pattern rule");
}
String pattern = props.remove("pattern");
if (pattern == null) {
throw new InvalidUserDataException("Missing [pattern] for invalid pattern rule");
}
if (props.isEmpty() == false) {
throw new InvalidUserDataException("Unknown arguments for ForbiddenPatterns rule mapping: "
+ props.keySet().toString());
}
// TODO: fail if pattern contains a newline, it won't work (currently)
patterns.put(name, pattern);
}
}
Loading

0 comments on commit e5940ee

Please sign in to comment.