blob: 9ac4ed914abf4e59fc580d9abca755a3aae27fe4 [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.
#ifndef VERIBLE_COMMON_ANALYSIS_LINT_WAIVER_H_
#define VERIBLE_COMMON_ANALYSIS_LINT_WAIVER_H_
#include <cstddef>
#include <map>
#include <regex> // NOLINT
#include <set>
#include <vector>
#include "absl/strings/string_view.h"
#include "common/strings/position.h"
#include "common/text/text_structure.h"
#include "common/text/token_stream_view.h"
#include "common/util/container_util.h"
#include "common/util/interval_set.h"
namespace verible {
// LintWaiver maintains a set of line ranges per lint rule that should be
// exempt from each rule.
class LintWaiver {
using RegexVector = std::vector<const std::regex*>;
public:
LintWaiver() {}
// Construction either done in Builder function or LintWaiverBuilder class
// defined below.
// void Initialize(const LintWaiverBuilder&); // or configuration
// Scan lines for comments with linter directives.
// Lines with only space/comment tokens will apply to the next line that
// doesn't contain only spaces and newlines. Blank line will cancel the
// waiver.
// Waiver comments on lines with other tokens will only waive that line.
// Adds a single line to the set of waived lines for a single rule.
void WaiveOneLine(absl::string_view rule_name, int line_number);
// Adds a range [line_begin, line_end) over which a waiver applies.
void WaiveLineRange(absl::string_view rule_name, int line_begin,
int line_end);
// Adds a regular expression which will be used to apply a waiver.
void WaiveWithRegex(absl::string_view rule_name, const std::string& regex);
// Converts the prepared regular expressions to line numbers and applies the
// waivers.
void RegexToLines(absl::string_view content, const LineColumnMap& line_map);
// Returns true if `line_number` should be waived for a particular rule.
bool RuleIsWaivedOnLine(absl::string_view rule_name, int line_number) const;
// Returns true if there are no lines waived for any rules.
bool Empty() const;
// TODO(hzeller): The following methods break abstraction and are only
// for performance. Reconsider if this is worth it.
const LineNumberSet* LookupLineNumberSet(absl::string_view rule_name) const {
return verible::container::FindOrNull(waiver_map_, rule_name);
}
// Test if a particular line is included in the set.
static bool LineNumberSetContains(const LineNumberSet& line_set, int line) {
return line_set.Contains(line);
}
private:
// Keys in the maps below are the names of the waived rules. They can be
// string_view because the static strings for each lint rule class exist,
// and will outlive all LintWaiver objects. This applies to both waiver_map_
// and waiver_re_map_.
std::map<absl::string_view, LineNumberSet> waiver_map_;
std::map<absl::string_view, RegexVector> waiver_re_map_;
std::map<std::string, std::regex> regex_cache_;
};
// LintWaiverBuilder is a language-agnostic helper class for constructing
// LintWaiver maps. Objects of this builder type become language-specific
// through function hooks passed to the constructor.
// Alternately, a derived class can bind the constructor arguments for a
// language-specific implementation.
//
// A waiver comment on its own line applies the waiver to the next
// non-comment-line.
//
// 1: // tool_name rule_name waive
// 2: other text, this line is waived
//
// A waiver comment on a line with other non-comment text waives its own line:
//
// 1: blah blah // tool_name rule_name waive // waives this line only
//
// TODO(fangism): Support lint waiver directives in multi-line block comments.
class LintWaiverBuilder {
public:
// 'is_comment' returns true if the token passed is considered a comment.
// 'is_space' returns true if token represents whitespace.
// 'trigger' is the string that triggers waiver processing, often a tool name.
// The first argument after the trigger is the name of the rule to waive.
// 'waive_command' is the second argument after the trigger, and is the
// command for 'waive-one-line'.
LintWaiverBuilder(TokenFilterPredicate&& is_comment,
TokenFilterPredicate&& is_space, absl::string_view trigger,
absl::string_view waive_line_command,
absl::string_view waive_start_command,
absl::string_view waive_stop_command)
: waiver_trigger_keyword_(trigger),
waive_one_line_keyword_(waive_line_command),
waive_range_start_keyword_(waive_start_command),
waive_range_stop_keyword_(waive_stop_command),
is_token_comment_(std::move(is_comment)),
is_token_whitespace_(std::move(is_space)) {}
// Takes a single line's worth of tokens and determines updates to the set of
// waived lines. Pass a slice of tokens using make_range.
void ProcessLine(const TokenRange& tokens, int line_number);
// Takes a lexically analyzed text structure and determines the entire set of
// waived lines. This can be more easily unit-tested using
// TextStructureTokenized from text_structure_test_utils.h.
void ProcessTokenRangesByLine(const TextStructureView&);
// Takes a set of active linter rules and the affected filename to be linted,
// and applies waivers from waiver_filename and its content.
absl::Status ApplyExternalWaivers(
const std::set<absl::string_view>& active_rules,
absl::string_view lintee_filename, absl::string_view waiver_filename,
absl::string_view waivers_config_content);
const LintWaiver& GetLintWaiver() const { return lint_waiver_; }
protected:
// Parses a comment and extracts a waived rule name.
// If text does not match the waived form, then return an empty string.
// `comment_tokens` is just re-used memory to avoid re-allocation.
absl::string_view ExtractWaivedRuleFromComment(
absl::string_view comment_text,
std::vector<absl::string_view>* comment_tokens) const;
// Special string that leads a comment that is a waiver directive
// Typically, name of linter tool is used here.
absl::string_view waiver_trigger_keyword_;
// Command to waive one line, either the current line if there are tokens
// on the current line or the next non-comment-non-blank-line.
absl::string_view waive_one_line_keyword_; // e.g. "waive"
// Command pair to start and stop waiving ranges of lines.
// e.g. "waive-start", "waive-stop"
absl::string_view waive_range_start_keyword_;
absl::string_view waive_range_stop_keyword_;
// Returns true if token is a comment.
TokenFilterPredicate is_token_comment_;
// Returns true if token is a whitespace (still considered blank).
TokenFilterPredicate is_token_whitespace_;
// This holds the set of to-be-applied lint waivers.
// Element string_views point to string memory that outlives this builder.
std::set<absl::string_view> unapplied_oneline_waivers_;
// This holds the set of open ranges of lines, keyed by rule name.
// Value is the lower-bound of each encountered waiver range.
// string_view keys point to string memory that outlives this builder.
std::map<absl::string_view, int> waiver_open_ranges_;
// Set of waived lines per rule.
LintWaiver lint_waiver_;
};
} // namespace verible
#endif // VERIBLE_COMMON_ANALYSIS_LINT_WAIVER_H_