blob: 25e194a978927bea57ab44419bc2a97e8e327aa6 [file] [log] [blame] [edit]
// 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