blob: 6b84c037cc88fb42db8803634c3d8fd5cc3b236d [file] [log] [blame]
// Copyright 2017-2020 The Verible Authors.
//
// 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.
#include "verilog/analysis/verilog_linter_configuration.h"
#include <algorithm>
#include <functional>
#include <initializer_list>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "common/analysis/line_lint_rule.h"
#include "common/analysis/syntax_tree_lint_rule.h"
#include "common/analysis/text_structure_lint_rule.h"
#include "common/analysis/token_stream_lint_rule.h"
#include "common/util/container_util.h"
#include "common/util/enum_flags.h"
#include "common/util/file_util.h"
#include "common/util/logging.h"
#include "verilog/analysis/default_rules.h"
#include "verilog/analysis/lint_rule_registry.h"
namespace verilog {
using verible::LineLintRule;
using verible::SyntaxTreeLintRule;
using verible::TextStructureLintRule;
using verible::TokenStreamLintRule;
using verible::container::FindOrNull;
template <typename List>
static const char* MatchesAnyItem(absl::string_view filename,
const List& items) {
for (const auto item : items) {
if (absl::StrContains(filename, item)) {
return item;
}
}
return nullptr;
}
const char* ProjectPolicy::MatchesAnyPath(absl::string_view filename) const {
return MatchesAnyItem(filename, path_substrings);
}
const char* ProjectPolicy::MatchesAnyExclusions(
absl::string_view filename) const {
return MatchesAnyItem(filename, path_exclusions);
}
bool ProjectPolicy::IsValid() const {
for (const auto rule : disabled_rules) {
if (!analysis::IsRegisteredLintRule(rule)) return false;
}
for (const auto rule : enabled_rules) {
if (!analysis::IsRegisteredLintRule(rule)) return false;
}
return true;
}
std::string ProjectPolicy::ListPathGlobs() const {
return absl::StrJoin(path_substrings.begin(), path_substrings.end(), " | ",
[](std::string* out, absl::string_view pattern) {
absl::StrAppend(out, "*", pattern, "*");
});
}
bool RuleBundle::ParseConfiguration(absl::string_view text, char separator,
std::string* error) {
// Clear the vector to overwrite any existing value.
rules.clear();
for (absl::string_view part :
absl::StrSplit(text, separator, absl::SkipEmpty())) {
if (separator == '\n') {
// In configuration files, we can ignore #-comments
// TODO(hzeller): this will fall short if in the configuration string
// we expect rules that accept a #-character for some reason. In that
// case we need to expand this to parse out 'within # quotes' parts.
// ... then this will finally become a more complex lexer.
const auto comment_pos = part.find('#');
if (comment_pos != absl::string_view::npos) {
part = part.substr(0, comment_pos);
}
}
part = absl::StripAsciiWhitespace(part);
if (part.empty()) continue;
// If prefix is '-', the rule is disabled. For symmetry, we also allow
// '+' to enable rule.
// Note that part is guaranteed to be at least one character because
// of absl::SkipEmpty()
const bool has_prefix = (part[0] == '+' || part[0] == '-');
const bool prefix_minus = (part[0] == '-');
RuleSetting setting = {!prefix_minus, ""};
const auto rule_name_with_config = part.substr(has_prefix ? 1 : 0);
// Independent of the enabled-ness: extract a configuration string
// if there is any assignment.
const auto equals_pos = rule_name_with_config.find('=');
if (equals_pos != absl::string_view::npos) {
const auto config = rule_name_with_config.substr(equals_pos + 1);
setting.configuration.assign(config.data(), config.size());
}
const auto rule_name = rule_name_with_config.substr(0, equals_pos);
const auto rule_name_set = analysis::GetAllRegisteredLintRuleNames();
const auto rule_iter = rule_name_set.find(rule_name);
// Check if text is a valid lint rule.
if (rule_iter == rule_name_set.end()) {
*error = absl::StrCat("invalid flag \"", rule_name, "\"");
return false;
} else {
// Map keys must use canonical registered string_views for guaranteed
// lifetime, not just any string-equivalent copy.
rules[*rule_iter] = setting;
}
}
return true;
}
// Parse and unparse for RuleBundle (for commandlineflags)
std::string RuleBundle::UnparseConfiguration(const char separator) const {
std::vector<std::string> switches;
for (const auto& rule : rules) {
switches.push_back(absl::StrCat(
// If rule is set off, prepend "-"
rule.second.enabled ? "" : "-", rule.first,
// If we have a configuration, append assignment.
rule.second.configuration.empty() ? "" : "=",
rule.second.configuration));
}
// Concatenates all of rules into text.
return absl::StrJoin(switches.rbegin(), switches.rend(),
std::string(1, separator));
}
bool LinterConfiguration::RuleIsOn(const analysis::LintRuleId& rule) const {
const auto* entry = FindOrNull(configuration_, rule);
if (entry == nullptr) return false;
return entry->enabled;
}
void LinterConfiguration::UseRuleSet(const RuleSet& rules) {
configuration_.clear();
switch (rules) {
case RuleSet::kAll: {
for (const auto& rule : analysis::RegisteredTextStructureRulesNames()) {
TurnOn(rule);
}
for (const auto& rule : analysis::RegisteredSyntaxTreeRulesNames()) {
TurnOn(rule);
}
for (const auto& rule : analysis::RegisteredTokenStreamRulesNames()) {
TurnOn(rule);
}
for (const auto& rule : analysis::RegisteredLineRulesNames()) {
TurnOn(rule);
}
break;
}
case RuleSet::kNone:
break;
case RuleSet::kDefault:
for (const auto& rule : analysis::kDefaultRuleSet) {
TurnOn(rule);
}
}
}
void LinterConfiguration::UseRuleBundle(const RuleBundle& rule_bundle) {
const auto name_set = analysis::GetAllRegisteredLintRuleNames();
for (const auto& rule_pair : rule_bundle.rules) {
// This needs to use the canonical registered key string_view, which has
// guaranteed lifetime.
configuration_[rule_pair.first] = rule_pair.second;
}
}
void LinterConfiguration::UseProjectPolicy(const ProjectPolicy& policy,
absl::string_view filename) {
if (const char* matched_path = policy.MatchesAnyPath(filename)) {
VLOG(1) << "File \"" << filename << "\" matches path \"" << matched_path
<< "\" from project policy [" << policy.name << "], applying it.";
for (const auto rule : policy.disabled_rules) {
VLOG(1) << " disabling rule: " << rule;
TurnOff(rule);
}
for (const auto rule : policy.enabled_rules) {
VLOG(1) << " enabling rule: " << rule;
TurnOn(rule);
}
}
}
std::set<analysis::LintRuleId> LinterConfiguration::ActiveRuleIds() const {
std::set<analysis::LintRuleId> result;
for (const auto& rule_pair : configuration_) {
if (rule_pair.second.enabled) {
result.insert(rule_pair.first);
}
}
return result;
}
// Iterates through all rules that are mentioned and enabled
// in the "config" map. Constructs instances using the
// "factory"-function, and configures them if a configuration string is
// available. Returns a vector of all successfully created instances.
//
// T should be a descendant of verible::LintRule.
template <typename T>
static std::vector<std::unique_ptr<T>> CreateRules(
const std::map<analysis::LintRuleId, RuleSetting>& config,
std::function<std::unique_ptr<T>(const analysis::LintRuleId&)> factory) {
std::vector<std::unique_ptr<T>> rule_instances;
for (const auto& rule_pair : config) {
const RuleSetting& setting = rule_pair.second;
if (!setting.enabled) continue;
std::unique_ptr<T> rule_ptr = factory(rule_pair.first);
if (rule_ptr == nullptr) continue;
if (!setting.configuration.empty()) {
absl::Status status;
if (!(status = rule_ptr->Configure(setting.configuration)).ok()) {
// TODO(hzeller): return error message to caller to handle ?
LOG(QFATAL) << rule_pair.first << ": " << status.message();
}
}
rule_instances.push_back(std::move(rule_ptr));
}
return rule_instances;
}
std::vector<std::unique_ptr<SyntaxTreeLintRule>>
LinterConfiguration::CreateSyntaxTreeRules() const {
return CreateRules<SyntaxTreeLintRule>(configuration_,
analysis::CreateSyntaxTreeLintRule);
}
std::vector<std::unique_ptr<TokenStreamLintRule>>
LinterConfiguration::CreateTokenStreamRules() const {
return CreateRules<TokenStreamLintRule>(configuration_,
analysis::CreateTokenStreamLintRule);
}
std::vector<std::unique_ptr<LineLintRule>>
LinterConfiguration::CreateLineRules() const {
return CreateRules<LineLintRule>(configuration_,
analysis::CreateLineLintRule);
}
std::vector<std::unique_ptr<TextStructureLintRule>>
LinterConfiguration::CreateTextStructureRules() const {
return CreateRules<TextStructureLintRule>(
configuration_, analysis::CreateTextStructureLintRule);
}
bool LinterConfiguration::operator==(const LinterConfiguration& config) const {
return ActiveRuleIds() == config.ActiveRuleIds();
}
absl::Status LinterConfiguration::AppendFromFile(
absl::string_view config_filename) {
// Read local configuration file
std::string content;
const absl::Status config_read_status =
verible::file::GetContents(config_filename, &content);
if (config_read_status.ok()) {
RuleBundle local_rules_bundle;
std::string error;
if (local_rules_bundle.ParseConfiguration(content, '\n', &error)) {
UseRuleBundle(local_rules_bundle);
} else {
LOG(WARNING) << "Unable to fully parse configuration: " << error;
}
return absl::OkStatus();
}
return config_read_status;
}
absl::Status LinterConfiguration::ConfigureFromOptions(
const LinterOptions& options) {
UseRuleSet(options.ruleset);
if (options.config_file_is_custom) {
const absl::Status config_read_status = AppendFromFile(options.config_file);
if (!config_read_status.ok()) {
LOG(WARNING) << options.config_file
<< ": Unable to read rules configuration file "
<< config_read_status << std::endl;
}
} else if (options.rules_config_search) {
// Search upward if search is enabled and no configuration file is
// specified
static constexpr absl::string_view linter_config = ".rules.verible_lint";
std::string resolved_config_file;
if (verible::file::UpwardFileSearch(options.linting_start_file,
linter_config, &resolved_config_file)
.ok()) {
const absl::Status config_read_status =
AppendFromFile(resolved_config_file);
if (!config_read_status.ok()) {
LOG(WARNING) << resolved_config_file
<< ": Unable to read rules configuration file "
<< config_read_status << std::endl;
}
}
}
// Turn on rules found in config
UseRuleBundle(options.rules);
// Apply external waivers
external_waivers = std::string(options.waiver_files);
return absl::OkStatus();
}
std::ostream& operator<<(std::ostream& stream,
const LinterConfiguration& config) {
const auto rules = config.ActiveRuleIds();
return stream << "{ " << absl::StrJoin(rules.begin(), rules.end(), ", ")
<< " }";
}
static constexpr std::initializer_list<
std::pair<const absl::string_view, RuleSet>>
kRuleSetEnumStringMap = {
{"all", RuleSet::kAll},
{"none", RuleSet::kNone},
{"default", RuleSet::kDefault},
};
std::ostream& operator<<(std::ostream& stream, RuleSet rules) {
static const auto* flag_map =
verible::MakeEnumToStringMap(kRuleSetEnumStringMap);
return stream << flag_map->find(rules)->second;
}
//
// Parse and unparse for ruleset (for commandlineflags)
//
std::string AbslUnparseFlag(const RuleSet& rules) {
std::ostringstream stream;
stream << rules;
return stream.str();
}
bool AbslParseFlag(absl::string_view text, RuleSet* rules, std::string* error) {
static const auto* flag_map =
verible::MakeStringToEnumMap(kRuleSetEnumStringMap);
return EnumMapParseFlag(*flag_map, text, rules, error);
}
std::string AbslUnparseFlag(const RuleBundle& bundle) {
return bundle.UnparseConfiguration(',');
}
bool AbslParseFlag(absl::string_view text, RuleBundle* bundle,
std::string* error) {
return bundle->ParseConfiguration(text, ',', error);
}
} // namespace verilog