blob: 3f50cb6e47273389c758063bf37ecd351a22ddfe [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <lib/cmdline/status.h>
#include <stdio.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <fidl/findings.h>
#include <fidl/findings_json.h>
#include <fidl/lexer.h>
#include <fidl/linter.h>
#include <fidl/parser.h>
#include <fidl/source_manager.h>
#include <fidl/tree_visitor.h>
#include "command_line_options.h"
namespace {
[[noreturn]] void FailWithUsage(const std::string& argv0, const char* message, ...) {
va_list args;
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
std::cerr << fidl::linter::Usage(argv0) << std::endl;
exit(2); // Exit code 1 is reserved to indicate lint findings
}
[[noreturn]] void Fail(const char* message, ...) {
va_list args;
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
exit(2); // Exit code 1 is reserved to indicate lint findings
}
fidl::Finding DiagnosticToFinding(const fidl::Diagnostic& diag) {
const char* check_id;
switch (diag.kind) {
case fidl::DiagnosticKind::kError:
check_id = "parse-error";
break;
case fidl::DiagnosticKind::kWarning:
check_id = "parse-warning";
break;
}
assert(diag.span.has_value() && "Parser diagnostics should always have a source span");
return fidl::Finding(diag.span.value(), check_id, diag.msg);
}
void Lint(const fidl::SourceFile& source_file, fidl::Findings* findings,
const std::set<std::string>& included_checks,
const std::set<std::string>& excluded_checks, bool exclude_by_default,
std::set<std::string>* excluded_checks_not_found) {
fidl::Reporter reporter;
fidl::Lexer lexer(source_file, &reporter);
fidl::ExperimentalFlags experimental_flags;
fidl::Parser parser(&lexer, &reporter, experimental_flags);
std::unique_ptr<fidl::raw::File> ast = parser.Parse();
for (auto* diag : reporter.diagnostics()) {
findings->push_back(DiagnosticToFinding(*diag));
}
if (!parser.Success()) {
return;
}
fidl::linter::Linter linter;
linter.set_included_checks(included_checks);
linter.set_excluded_checks(excluded_checks);
linter.set_exclude_by_default(exclude_by_default);
linter.Lint(ast, findings, excluded_checks_not_found);
}
} // namespace
int main(int argc, char* argv[]) {
fidl::linter::CommandLineOptions options;
std::vector<std::string> filepaths;
cmdline::Status status =
fidl::linter::ParseCommandLine(argc, const_cast<const char**>(argv), &options, &filepaths);
if (status.has_error()) {
Fail("%s\n", status.error_message().c_str());
}
if (filepaths.empty()) {
FailWithUsage(argv[0], "No files provided\n");
}
fidl::SourceManager source_manager;
// Process filenames.
for (auto filepath : filepaths) {
if (!source_manager.CreateSource(filepath)) {
Fail("Couldn't read in source data from %s\n", filepath.c_str());
}
}
std::set<std::string> excluded_checks_not_found;
if (options.must_find_excluded_checks) {
// copy excluded checks specified in command line options, and the linter will remove each one
// encountered during linting.
excluded_checks_not_found =
std::set<std::string>(options.excluded_checks.begin(), options.excluded_checks.end());
}
bool exclude_by_default = (options.included_checks.size() > 0) && options.excluded_checks.empty();
// Convert command line vectors to sets, and add internally-disabled checks to excluded
auto included_checks =
std::set<std::string>(options.included_checks.begin(), options.included_checks.end());
auto excluded_and_disabled_checks =
std::set<std::string>(options.excluded_checks.begin(), options.excluded_checks.end());
// The following checks can be opted-in via command line option "included-checks",
// but are otherwise disabled for the reasons described in the comments:
// The name-repeats-* checks are very noisy, and sometimes produce
// unexpected findings. Rules are being refined, but for now, these
// are suppressed.
excluded_and_disabled_checks.insert("name-repeats-library-name");
excluded_and_disabled_checks.insert("name-repeats-enclosing-type-name");
// This check does currently highlight some potential issues with
// formatting and with 2-slash comments that will be converted to
// 3-slash Doc-Comments, but the rule cannot currently check 3-slash
// Doc-Comments (they are stripped out before they reach the linter,
// and converted to Attributes), and trailing non-Doc comments are
// supposed to be allowed. Therefore, the rule will eventually be
// removed, once the valid issues it currently surfaces have been
// addressed.
excluded_and_disabled_checks.insert("no-trailing-comment");
fidl::Findings findings;
bool enable_color = !std::getenv("NO_COLOR") && isatty(fileno(stderr));
for (const auto& source_file : source_manager.sources()) {
Lint(*source_file, &findings, included_checks, excluded_and_disabled_checks, exclude_by_default,
&excluded_checks_not_found);
}
if (options.format == "text") {
auto lints = fidl::utils::FormatFindings(findings, enable_color);
for (const auto& lint : lints) {
fprintf(stderr, "%s\n", lint.c_str());
}
} else {
assert(options.format == "json"); // should never be false
std::cout << fidl::FindingsJson(findings).Produce().str();
}
if (!excluded_checks_not_found.empty()) {
std::ostringstream os;
os << "The following checks were excluded but were never encountered:" << std::endl;
for (auto& check_id : excluded_checks_not_found) {
os << " * " << check_id << std::endl;
}
os << "Please remove these checks from your excluded_checks list and try again." << std::endl;
Fail(os.str().c_str());
}
// Exit with a status of '1' if there were any findings (at least one file was not "lint-free")
return findings.empty() ? 0 : 1;
}