blob: da1ee464974bcdee83fc56205e9c1269062af484 [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.
// verilog_lint is a command-line utility to check Verilog syntax
// and style compliance for the given file.
//
// Example usage:
// verilog_lint files...
#include <algorithm>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream> // IWYU pragma: keep // for ostringstream
#include <string> // for string, allocator, etc
#include <string_view>
#include <vector>
#include "absl/flags/flag.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "verible/common/analysis/lint-rule-status.h"
#include "verible/common/analysis/violation-handler.h"
#include "verible/common/util/enum-flags.h"
#include "verible/common/util/init-command-line.h"
#include "verible/common/util/iterator-range.h"
#include "verible/common/util/logging.h" // for operator<<, LOG, LogMessage, etc
#include "verible/verilog/analysis/verilog-linter-configuration.h"
#include "verible/verilog/analysis/verilog-linter.h"
// From least to most disruptive
enum class AutofixMode {
kNo, // No Autofixes
kPatchInteractive, // Interactively choose fixes, generate patch
kPatch, // Emit a patch
kInplaceInteractive, // Interactively choose fixes, apply inplace
kInplace, // Automatically apply patch in-place.
kGenerateWaiver, // Generate waiver file for violations
};
static const verible::EnumNameMap<AutofixMode> &AutofixModeEnumStringMap() {
static const verible::EnumNameMap<AutofixMode> kAutofixModeEnumStringMap({
{"no", AutofixMode::kNo},
{"patch-interactive", AutofixMode::kPatchInteractive},
{"patch", AutofixMode::kPatch},
{"inplace-interactive", AutofixMode::kInplaceInteractive},
{"inplace", AutofixMode::kInplace},
{"generate-waiver", AutofixMode::kGenerateWaiver},
});
return kAutofixModeEnumStringMap;
}
static std::ostream &operator<<(std::ostream &stream, AutofixMode mode) {
return AutofixModeEnumStringMap().Unparse(mode, stream);
}
static std::string AbslUnparseFlag(const AutofixMode &mode) {
std::ostringstream stream;
AutofixModeEnumStringMap().Unparse(mode, stream);
return stream.str();
}
static bool AbslParseFlag(std::string_view text, AutofixMode *mode,
std::string *error) {
return AutofixModeEnumStringMap().Parse(text, mode, error, "--autofix value");
}
// LINT.IfChange
ABSL_FLAG(bool, check_syntax, true,
"If true, check for lexical and syntax errors, otherwise ignore.");
ABSL_FLAG(bool, parse_fatal, true,
"If true, exit nonzero if there are any syntax errors.");
ABSL_FLAG(bool, lint_fatal, true,
"If true, exit nonzero if linter finds violations.");
ABSL_FLAG(std::string, help_rules, "",
"[all|<rule-name>], print the description of one rule/all rules "
"and exit immediately.");
ABSL_FLAG(
bool, generate_markdown, false,
"If true, print the description of every rule formatted for the "
"Markdown and exit immediately. Intended for the output to be written "
"to a snippet of Markdown.");
ABSL_FLAG(bool, print_rules_file, false,
"Print the current set of lint rules in a format that can be used to "
"create a lint rules configuration file (i.e. .rules.verible_lint) "
"and exit immediately.");
ABSL_FLAG(bool, show_diagnostic_context, false,
"prints an additional "
"line on which the diagnostic was found,"
"followed by a line with a position marker");
ABSL_FLAG(
AutofixMode, autofix, AutofixMode::kNo,
"autofix mode; one of "
"[no|patch-interactive|patch|inplace-interactive|inplace|generate-waiver]");
ABSL_FLAG(std::string, autofix_output_file, "",
"File to write a patch with autofixes to if "
"--autofix=patch or --autofix=patch-interactive "
"or a waiver file if --autofix=generate-waiver");
// LINT.ThenChange(README.md)
using verilog::LinterConfiguration;
// LintOneFile returns 0, 1, or 2
static const int kAutofixErrorExitStatus = 3;
int main(int argc, char **argv) {
const auto usage =
absl::StrCat("usage: ", argv[0], " [options] <file> [<file>...]");
const auto args = verible::InitCommandLine(usage, &argc, &argv);
std::string help_flag = absl::GetFlag(FLAGS_help_rules);
if (!help_flag.empty()) {
verilog::GetLintRuleDescriptionsHelpFlag(&std::cout, help_flag);
return 0;
}
// In documentation generation mode, print documentation and exit immediately.
bool generate_markdown_flag = absl::GetFlag(FLAGS_generate_markdown);
if (generate_markdown_flag) {
verilog::GetLintRuleDescriptionsMarkdown(&std::cout);
return 0;
}
int exit_status = 0;
AutofixMode autofix_mode = absl::GetFlag(FLAGS_autofix);
const std::string autofix_output_file =
absl::GetFlag(FLAGS_autofix_output_file);
std::unique_ptr<std::ostream> stream_closer;
std::ostream *autofix_output_stream = nullptr;
if (autofix_mode == AutofixMode::kPatch ||
autofix_mode == AutofixMode::kPatchInteractive ||
autofix_mode == AutofixMode::kGenerateWaiver) {
if (autofix_output_file.empty() || autofix_output_file == "-") {
autofix_output_stream = &std::cout;
} else {
stream_closer.reset(new std::ofstream(autofix_output_file));
if (stream_closer->good()) {
autofix_output_stream = stream_closer.get();
} else {
LOG(ERROR) << "Failed to create/open output patch file: "
<< autofix_output_file;
}
}
if (!autofix_output_stream) {
std::cerr << "--autofix=patch needs --autofix_output_file" << std::endl;
autofix_mode = AutofixMode::kNo;
exit_status = kAutofixErrorExitStatus;
}
} else if (!autofix_output_file.empty()) {
std::cerr << "--autofix_output_file has no effect for --autofix="
<< autofix_mode << std::endl;
}
const verible::ViolationFixer::AnswerChooser applyAllFixes =
[](const verible::LintViolation &,
std::string_view) -> verible::ViolationFixer::Answer {
return {verible::ViolationFixer::AnswerChoice::kApplyAll, 0};
};
std::unique_ptr<verible::ViolationHandler> violation_handler;
switch (autofix_mode) {
case AutofixMode::kNo:
violation_handler.reset(new verible::ViolationPrinter(&std::cerr));
break;
case AutofixMode::kPatchInteractive:
CHECK(autofix_output_stream);
violation_handler.reset(
new verible::ViolationFixer(&std::cerr, autofix_output_stream));
break;
case AutofixMode::kPatch:
CHECK(autofix_output_stream);
violation_handler.reset(new verible::ViolationFixer(
&std::cerr, autofix_output_stream, applyAllFixes));
break;
case AutofixMode::kInplaceInteractive:
violation_handler.reset(new verible::ViolationFixer(&std::cerr, nullptr));
break;
case AutofixMode::kInplace:
violation_handler.reset(
new verible::ViolationFixer(&std::cerr, nullptr, applyAllFixes));
break;
case AutofixMode::kGenerateWaiver:
violation_handler.reset(new verible::ViolationWaiverPrinter(
&std::cerr, autofix_output_stream));
break;
}
// In documentation generation mode, print lint rule file and exit
// immediately.
bool print_rules_file_flag = absl::GetFlag(FLAGS_print_rules_file);
if (print_rules_file_flag) {
auto config_status = verilog::LinterConfigurationFromFlags("");
if (!config_status.ok()) {
std::cerr << config_status.status().message() << std::endl;
return 1;
}
const LinterConfiguration &config = *config_status;
verilog::GetLintRuleFile(&std::cout, config);
return 0;
}
// All positional arguments are file names. Exclude program name.
for (const std::string_view filename :
verible::make_range(args.begin() + 1, args.end())) {
// Copy configuration, so that it can be locally modified per file.
auto config_status = verilog::LinterConfigurationFromFlags(filename);
if (!config_status.ok()) {
std::cerr << config_status.status().message() << std::endl;
exit_status = 1;
continue;
}
const LinterConfiguration &config = *config_status;
const int lint_status = verilog::LintOneFile(
&std::cout, filename, config, violation_handler.get(),
absl::GetFlag(FLAGS_check_syntax), absl::GetFlag(FLAGS_parse_fatal),
absl::GetFlag(FLAGS_lint_fatal),
absl::GetFlag(FLAGS_show_diagnostic_context));
exit_status = std::max(lint_status, exit_status);
} // for each file
return exit_status;
}