Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.sbaudoin.sonar.plugins.yaml.rules.YamlSensor Maven / Gradle / Ivy
/**
* Copyright (c) 2018-2021, Sylvain Baudoin
*
* 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.github.sbaudoin.sonar.plugins.yaml.rules;
import com.github.sbaudoin.sonar.plugins.yaml.settings.YamlSettings;
import com.github.sbaudoin.yamllint.YamlLintConfig;
import com.github.sbaudoin.yamllint.YamlLintConfigException;
import org.sonar.api.batch.fs.FilePredicate;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.TextPointer;
import org.sonar.api.batch.rule.CheckFactory;
import org.sonar.api.batch.rule.Checks;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.measures.FileLinesContextFactory;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import com.github.sbaudoin.sonar.plugins.yaml.linecounter.LineCounter;
import com.github.sbaudoin.sonar.plugins.yaml.checks.*;
import com.github.sbaudoin.sonar.plugins.yaml.highlighting.HighlightingData;
import com.github.sbaudoin.sonar.plugins.yaml.highlighting.YamlHighlighting;
import com.github.sbaudoin.sonar.plugins.yaml.languages.YamlLanguage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import static com.github.sbaudoin.yamllint.Cli.*;
/**
* Main sensor
*/
public class YamlSensor implements Sensor {
private static final Logger LOGGER = Loggers.get(YamlSensor.class);
private final Checks checks;
private final FileSystem fileSystem;
private final FilePredicate mainFilesPredicate;
private final FileLinesContextFactory fileLinesContextFactory;
protected final YamlLintConfig localConfig;
/**
* Name of a local rule configuration file: if this file is present in the work directory, it is used as an extension
* configuration file
*/
public static final String USER_CONF_FILENAME = ".yamllint";
/**
* Constructor
*
* @param fileSystem the file system on which the sensor will find the files to be analyzed
* @param checkFactory check factory used to get the checks to execute against the files
* @param fileLinesContextFactory factory used to report measures
*/
public YamlSensor(FileSystem fileSystem, CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory) {
this.fileLinesContextFactory = fileLinesContextFactory;
this.checks = checkFactory.create(CheckRepository.REPOSITORY_KEY).addAnnotatedChecks((Iterable>) CheckRepository.getCheckClasses());
this.fileSystem = fileSystem;
this.mainFilesPredicate = fileSystem.predicates().and(
fileSystem.predicates().hasType(InputFile.Type.MAIN),
fileSystem.predicates().hasLanguage(YamlLanguage.KEY));
this.localConfig = getLocalConfig();
}
@Override
public void describe(SensorDescriptor descriptor) {
descriptor.onlyOnLanguage(YamlLanguage.KEY);
descriptor.name("YAML Sensor");
}
@Override
public void execute(SensorContext context) {
LOGGER.debug("YAML sensor executed with context: " + context);
Optional parsingErrorKey = getParsingErrorRuleKey();
// Skip analysis if no rules enabled from this plugin
boolean skipChecks = false;
if (context.activeRules().findByRepository(CheckRepository.REPOSITORY_KEY).isEmpty()) {
LOGGER.info("No active rules found for this plugin, skipping.");
skipChecks = true;
}
for (InputFile inputFile : fileSystem.inputFiles(mainFilesPredicate)) {
LOGGER.debug("Analyzing file: " + inputFile.filename());
try {
YamlSourceCode sourceCode = new YamlSourceCode(inputFile, context.config().getBoolean(YamlSettings.FILTER_UTF8_LB_KEY));
computeLinesMeasures(context, sourceCode);
saveSyntaxHighlighting(context, sourceCode);
if (!skipChecks) {
// First check for syntax errors
if (!sourceCode.hasCorrectSyntax()) {
LOGGER.debug("File has syntax errors");
processAnalysisError(context, sourceCode, inputFile, parsingErrorKey);
}
runChecks(context, sourceCode);
}
} catch (IOException e) {
LOGGER.warn("Error reading source file " + inputFile.filename(), e);
}
}
}
/**
* Calculates and feeds line measures (comments, actual number of code lines)
*
* @param context the sensor context
* @param sourceCode the YAML source code to be analyzed
*/
private void computeLinesMeasures(SensorContext context, YamlSourceCode sourceCode) {
LineCounter.analyse(context, fileLinesContextFactory, sourceCode);
}
/**
* Returns the {@link RuleKey} of the check that tags syntax errors
*
* @return a {@link RuleKey}
*/
private Optional getParsingErrorRuleKey() {
for (Object obj : checks.all()) {
YamlCheck check = (YamlCheck) obj;
if (check.getClass().equals(CheckRepository.getParsingErrorCheckClass())) {
LOGGER.debug("Parsing error rule key found: " + check.getRuleKey());
return Optional.of(checks.ruleKey(check));
}
}
LOGGER.debug("No parsing error rule key found");
return Optional.empty();
}
/**
* Runs all checks (except the syntax check) against the passed YAML source code
*
* @param context the sensor context
* @param sourceCode the source code to be checked
*/
private void runChecks(SensorContext context, YamlSourceCode sourceCode) {
for (Object check : checks.all()) {
((YamlCheck) check).setRuleKey(checks.ruleKey(check));
((YamlCheck) check).setYamlSourceCode(sourceCode);
LOGGER.debug("Checking rule: " + ((YamlCheck) check).getRuleKey());
setConfig(((YamlCheck) check));
((YamlCheck) check).validate();
}
saveIssues(context, sourceCode);
}
/**
* Checks if there is a custom, local yamllint configuration file and returns the corresponding {@code YamlLintConfig}
*
* @return the {@code YamlLintConfig} that corresponds to the local yamllint configuration file or {@code null} if the
* file does not exist or is invalid
*/
private YamlLintConfig getLocalConfig() {
Path userGlobalConfig = getUserGlobalConfigPath();
try {
if (fileExists(fileSystem.resolvePath(USER_CONF_FILENAME))) {
return new YamlLintConfig(fileSystem.resolvePath(USER_CONF_FILENAME).toURI().toURL());
} else if (fileExists(fileSystem.resolvePath(USER_CONF_FILENAME + ".yaml"))) {
return new YamlLintConfig(fileSystem.resolvePath(USER_CONF_FILENAME + ".yaml").toURI().toURL());
} else if (fileExists(fileSystem.resolvePath(USER_CONF_FILENAME + ".yml"))) {
return new YamlLintConfig(fileSystem.resolvePath(USER_CONF_FILENAME + ".yml").toURI().toURL());
} else if (fileExists(userGlobalConfig.toString())) {
return new YamlLintConfig(userGlobalConfig.toUri().toURL());
}
} catch (IOException e) {
LOGGER.warn("Cannot read yamllint user configuration file: " + e.getMessage());
LOGGER.warn("Enable debug mode to get the complete stacktrace.");
LOGGER.debug("Complete error trace:", e);
} catch (YamlLintConfigException e) {
LOGGER.warn("Configuration error: " + e.getMessage());
LOGGER.warn("Enable debug mode to get the complete stacktrace.");
LOGGER.debug("Complete error trace:", e);
}
return null;
}
/**
* Checks if there is a configuration for the passed rule in the yamllint configuration file so that the rule will
* use it instead of its SonarQube configuration
*
* @param check the rule to be configured with the local configuration
*/
private void setConfig(YamlCheck check) {
if (localConfig != null) {
check.setConfig(localConfig);
}
}
/**
* Returns the path to the user's yamllint global configuration file, as per the environment setting
*
* @return the path to the user's global configuration file for yamllint
*/
private Path getUserGlobalConfigPath() {
Path userGlobalConfig;
if (System.getenv(YAMLLINT_CONFIG_FILE_ENV_VAR) != null) {
userGlobalConfig = Paths.get(System.getenv(YAMLLINT_CONFIG_FILE_ENV_VAR));
} else if (System.getenv(XDG_CONFIG_HOME_ENV_VAR) != null) {
userGlobalConfig = Paths.get(System.getenv(XDG_CONFIG_HOME_ENV_VAR), APP_NAME, "config");
} else {
userGlobalConfig = Paths.get(System.getProperty("user.home"), ".config", APP_NAME, "config");
}
return userGlobalConfig;
}
/**
* Tells if the passed path is a file that exists
*
* @param path a path
* @return true
if the path exists and is a file, false
otherwise
*/
private boolean fileExists(String path) {
File file = new File(path);
return file.exists() && file.isFile();
}
/**
* Tells if the passed path is a file that exists
*
* @param path a path
* @return true
if the path exists and is a file, false
otherwise
*/
private boolean fileExists(File path) {
return path.exists() && path.isFile();
}
/**
* Saves the found issues in SonarQube
*
* @param context the context
* @param sourceCode the analyzed YAML source
*/
private void saveIssues(SensorContext context, YamlSourceCode sourceCode) {
for (YamlIssue yamlIssue : sourceCode.getYamlIssues()) {
LOGGER.debug("Saving issue: " + yamlIssue.getMessage());
NewIssue newIssue = context.newIssue().forRule(yamlIssue.getRuleKey());
NewIssueLocation location = newIssue.newLocation()
.on(sourceCode.getYamlFile())
.message(yamlIssue.getMessage())
.at(sourceCode.getYamlFile().selectLine(yamlIssue.getLine()==0?1:yamlIssue.getLine()));
newIssue.at(location).save();
}
}
/**
* Saves the syntax highlighting for the analyzed code
*
* @param context the sensor context
* @param sourceCode the YAML source code
*/
private static void saveSyntaxHighlighting(SensorContext context, YamlSourceCode sourceCode) {
List highlightingDataList;
try {
highlightingDataList = new YamlHighlighting(sourceCode).getHighlightingData();
} catch (IOException e) {
throw new IllegalStateException("Could not analyze file " + sourceCode.getYamlFile().filename(), e);
}
NewHighlighting highlighting = context.newHighlighting().onFile(sourceCode.getYamlFile());
for (HighlightingData highlightingData : highlightingDataList) {
highlightingData.highlight(highlighting);
}
highlighting.save();
}
/**
* Reports the passed issue as a syntax/parse error (aka {@link org.sonar.api.batch.sensor.error.AnalysisError} in
* the SonarQube terminology)
*
* @param context the sensor context
* @param sourceCode the analyzed YAML source
* @param inputFile the file that contains the error
* @param parsingErrorKey the {@link RuleKey} of the check that corresponds to a syntax error. If present, an issue
* is reported as well as the analysis error.
*/
private static void processAnalysisError(SensorContext context, YamlSourceCode sourceCode, InputFile inputFile, Optional parsingErrorKey) {
final YamlIssue error = sourceCode.getSyntaxError();
LOGGER.warn("Syntax error in file: {}", inputFile.filename());
LOGGER.warn("Cause: {} at line {}, column {}", error.getMessage(), error.getLine(), error.getColumn());
LOGGER.debug("Creating analysis error");
context.newAnalysisError()
.onFile(inputFile)
.message(sourceCode.getSyntaxError().getMessage())
.at(new TextPointer() {
@Override
public int line() {
return error.getLine();
}
@Override
public int lineOffset() {
return error.getColumn();
}
@Override
public int compareTo(TextPointer textPointer) {
return textPointer.line() - line();
}
})
.save();
if (parsingErrorKey.isPresent()) {
LOGGER.debug("parsingErrorKey present, creating issue");
// the ParsingErrorCheck rule is activated: we create a beautiful issue
NewIssue newIssue = context.newIssue().forRule(parsingErrorKey.get());
NewIssueLocation location = newIssue.newLocation()
.message("Parse error: " + error.getMessage())
.on(inputFile)
.at(sourceCode.getYamlFile().selectLine(error.getLine()));
newIssue.at(location).save();
}
}
}