| // 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_ |