blob: 2f6b134bab42f64d821e6a38641b925d7acd4f61 [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 <lib/cmdline/status.h>
#include <stdio.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "tools/fidl/fidlc/cmd/fidl-lint/command_line_options.h"
#include "tools/fidl/fidlc/src/findings.h"
#include "tools/fidl/fidlc/src/findings_json.h"
#include "tools/fidl/fidlc/src/lexer.h"
#include "tools/fidl/fidlc/src/linter.h"
#include "tools/fidl/fidlc/src/parser.h"
#include "tools/fidl/fidlc/src/source_manager.h"
#include "tools/fidl/fidlc/src/tree_visitor.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 << fidlc::Usage(argv0) << '\n';
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
}
fidlc::Finding DiagnosticToFinding(const fidlc::Diagnostic& diag) {
const char* check_id = nullptr;
switch (diag.def.kind) {
case fidlc::DiagnosticKind::kError:
check_id = "parse-error";
break;
case fidlc::DiagnosticKind::kWarning:
check_id = "parse-warning";
break;
case fidlc::DiagnosticKind::kRetired:
ZX_PANIC("should never emit a retired diagnostic");
}
return fidlc::Finding(diag.span, check_id, diag.Format());
}
void Lint(const fidlc::SourceFile& source_file, fidlc::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) {
fidlc::Reporter reporter;
fidlc::Lexer lexer(source_file, &reporter);
fidlc::ExperimentalFlagSet experimental_flags;
fidlc::Parser parser(&lexer, &reporter, experimental_flags);
std::unique_ptr<fidlc::File> ast = parser.Parse();
for (auto* diag : reporter.Diagnostics()) {
findings->push_back(DiagnosticToFinding(*diag));
}
if (!parser.Success()) {
return;
}
fidlc::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[]) {
fidlc::CommandLineOptions options;
std::vector<std::string> filepaths;
cmdline::Status status =
fidlc::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");
}
fidlc::SourceManager source_manager;
// Process filenames.
for (const auto& filepath : filepaths) {
const char* reason;
if (!source_manager.CreateSource(filepath, &reason)) {
Fail("Couldn't read in source data from %s: %s\n", filepath.c_str(), reason);
}
}
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.empty() && 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_checks =
std::set<std::string>(options.excluded_checks.begin(), options.excluded_checks.end());
// Add experimental checks to included checks. Experimental checks don't count
// for enabling exclude_by_default, but do get added added to included_checks
// to turn them on. Merging included-checks and experimental-checks allows
// experimental checks to be enabled through either the --include-checks flag
// or the --experimental-checks flag, which makes it possible to use
// exclude-by-default mode even if you only want to turn on experimental
// checks, by passing them through --include-checks rather than
// --experimental-checks.
//
// Note that this works in reverse as well; it is possible to enable a normal
// check via --experimental-checks, however this has no effect unless the
// check is also being excluded via --exclude-checks or exclude-by-default in
// being used because some other check was passed with --include-checks.
// Allowing non-experimental checks to be enabled via --experimental-checks
// ensures forward compatibility when a previously-experimental check is
// officially released an so no-longer experimental.
included_checks.insert(options.experimental_checks.begin(), options.experimental_checks.end());
fidlc::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_checks, exclude_by_default,
&excluded_checks_not_found);
}
if (options.format == "text") {
auto lints = fidlc::FormatFindings(findings, enable_color);
for (const auto& lint : lints) {
fprintf(stderr, "%s\n", lint.c_str());
}
} else {
ZX_ASSERT(options.format == "json"); // should never be false
std::cout << fidlc::FindingsJson(findings).Produce().str();
}
if (!excluded_checks_not_found.empty()) {
std::ostringstream os;
os << "The following checks were excluded but were never encountered:\n";
for (auto& check_id : excluded_checks_not_found) {
os << " * " << check_id << '\n';
}
os << "Please remove these checks from your excluded_checks list and try again.\n";
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;
}