| // 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/checkers/line_length_rule.h" |
| |
| #include <cstddef> |
| #include <iterator> |
| #include <set> |
| #include <string> |
| |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "common/analysis/lint_rule_status.h" |
| #include "common/strings/comment_utils.h" |
| #include "common/strings/utf8.h" |
| #include "common/text/config_utils.h" |
| #include "common/text/constants.h" |
| #include "common/text/text_structure.h" |
| #include "common/text/token_info.h" |
| #include "common/text/token_stream_view.h" |
| #include "common/util/iterator_range.h" |
| #include "common/util/logging.h" |
| #include "verilog/analysis/descriptions.h" |
| #include "verilog/analysis/lint_rule_registry.h" |
| #include "verilog/analysis/verilog_linter_constants.h" |
| #include "verilog/parser/verilog_token_classifications.h" |
| #include "verilog/parser/verilog_token_enum.h" |
| |
| namespace verilog { |
| namespace analysis { |
| |
| using verible::LintRuleStatus; |
| using verible::LintViolation; |
| using verible::TextStructureView; |
| using verible::TokenInfo; |
| using verible::TokenSequence; |
| |
| // Register the lint rule |
| VERILOG_REGISTER_LINT_RULE(LineLengthRule); |
| |
| static const char kMessage[] = "Line length exceeds max: "; |
| |
| #if 0 // See comment below about comment-reflowing being implemented |
| static bool ContainsAnyWhitespace(absl::string_view s) { |
| for (char c : s) { |
| if (absl::ascii_isspace(c)) return true; |
| } |
| return false; |
| } |
| #endif |
| |
| const LintRuleDescriptor& LineLengthRule::GetDescriptor() { |
| static const LintRuleDescriptor d{ |
| .name = "line-length", |
| .topic = "line-length", |
| .desc = |
| "Checks that all lines do not exceed the maximum allowed " |
| "length. ", |
| .param = {{"length", absl::StrCat(kDefaultLineLength), |
| "Desired line length"}}, |
| }; |
| return d; |
| } |
| |
| // Returns true if line is an exceptional case that should allow excessive |
| // length. |
| static bool AllowLongLineException(TokenSequence::const_iterator token_begin, |
| TokenSequence::const_iterator token_end) { |
| // There may be no tokens on this line if the lexer skipped them. |
| // TODO(b/134180314): Preserve all text in lexer. |
| if (token_begin == token_end) return true; // Conservatively ignore. |
| auto last_token = token_end - 1; // Point to last token. |
| if (last_token->token_enum() == verible::TK_EOF) --last_token; |
| // Point to last non-newline. |
| if (last_token->token_enum() == TK_NEWLINE) --last_token; |
| |
| // Ignore leading whitespace, to find first non-space token. |
| while (token_begin->token_enum() == TK_SPACE) ++token_begin; |
| |
| // Single token case: |
| // If there is only one token on this line, forgive non-comment tokens, |
| // but examine comment tokens deeper. |
| if (token_begin == last_token) { |
| switch (token_begin->token_enum()) { |
| case TK_EOL_COMMENT: { |
| // TODO(b/72010240): formatter: reflow comments |
| // Ideally, a comment whose contents can be split on spaces |
| // should be reflowed to spill onto new commented lines. |
| // However the formatter hasn't implemented this yet, and comments |
| // remain as atomic tokens, so fixing comment indentation may cause |
| // line-length violations. The compromise for now is to forgive |
| // this case (no matter what the length). |
| return true; |
| |
| // Once comment-reflowing is implemented, re-enable the following: |
| // If comment consist of more than one token, it should be split. |
| // const absl::string_view comment_contents = |
| // verible::StripCommentAndSpacePadding(token_begin->text); |
| // return !ContainsAnyWhitespace(comment_contents); |
| } |
| // TODO(fangism): examine "long string literals" |
| // TODO(fangism): case TK_COMMENT_BLOCK: |
| // Multi-line comments need deeper inspection. |
| default: |
| return true; |
| } |
| } |
| |
| // Multi-token cases: |
| switch (token_begin->token_enum()) { |
| case PP_include: |
| // TODO(fangism): Could try to be more specific and inspect this line's |
| // tokens further, but it is acceptable to forgive all `include lines. |
| return true; |
| case PP_ifdef: |
| case PP_ifndef: |
| case PP_endif: |
| // Include guards (if they reflect the full path) can be long. |
| // TODO(fangism): Could examine lines further and determine whether or |
| // not length could have been reduced, but not bothering for now. |
| return true; |
| // TODO(fangism): Consider whether or not PP_else and PP_elsif should |
| // be exempt from length checks as well. |
| default: |
| break; |
| } |
| |
| if (IsComment(verilog_tokentype(last_token->token_enum()))) { |
| // Check for end-of-line comment that contain lint waivers. |
| const absl::string_view text = |
| verible::StripCommentAndSpacePadding(last_token->text()); |
| if (absl::StartsWith(text, "ri lint_check_waive")) { |
| // TODO(fangism): Could make this pattern more space-insensitive |
| return true; |
| } |
| if (absl::StartsWith(text, kLinterTrigger)) { |
| // This is the waiver for this linter tool. |
| // verible/verilog/tools/lint/README.md |
| return true; |
| } |
| // TODO(fangism): add "noformat" formatter directives. |
| } |
| |
| return false; |
| } |
| |
| void LineLengthRule::Lint(const TextStructureView& text_structure, |
| absl::string_view) { |
| size_t lineno = 0; |
| for (const auto& line : text_structure.Lines()) { |
| VLOG(2) << "Examining line: " << lineno + 1; |
| const int observed_line_length = verible::utf8_len(line); |
| if (observed_line_length > line_length_limit_) { |
| const auto token_range = text_structure.TokenRangeOnLine(lineno); |
| // Recall that token_range is *unfiltered* and may contain non-essential |
| // whitespace 'tokens'. |
| if (!AllowLongLineException(token_range.begin(), token_range.end())) { |
| // Fake a token that marks the offending range of text. |
| TokenInfo token(TK_OTHER, line.substr(line_length_limit_)); |
| const std::string msg = absl::StrCat(kMessage, line_length_limit_, |
| "; is: ", observed_line_length); |
| violations_.insert(LintViolation(token, msg)); |
| } |
| } |
| ++lineno; |
| } |
| } |
| |
| absl::Status LineLengthRule::Configure(absl::string_view configuration) { |
| using verible::config::SetInt; |
| return verible::ParseNameValues( |
| configuration, {{"length", SetInt(&line_length_limit_, kMinimumLineLength, |
| kMaximumLineLength)}}); |
| } |
| |
| LintRuleStatus LineLengthRule::Report() const { |
| return LintRuleStatus(violations_, GetDescriptor()); |
| } |
| |
| } // namespace analysis |
| } // namespace verilog |