blob: 223a9ebc916a899a65ab1ec76a92d1185339fbf4 [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.
#ifndef TOOLS_FIDL_FIDLC_INCLUDE_FIDL_LINTER_H_
#define TOOLS_FIDL_FIDLC_INCLUDE_FIDL_LINTER_H_
#include <array>
#include <deque>
#include <regex>
#include <set>
#include <fidl/check_def.h>
#include <fidl/findings.h>
#include <fidl/linting_tree_callbacks.h>
#include <fidl/tree_visitor.h>
#include <fidl/utils.h>
namespace fidl {
namespace linter {
// The primary business logic for lint-checks on a FIDL file is implemented in
// the |Linter| class. This class is not thread safe.
class Linter {
public:
// On initialization, the |Linter| constructs the |CheckDef|
// objects with associated check logic (lambdas registered via
// |CheckCallbacks|).
Linter();
// If a user specifies the command line flag --include-check=<check-id>
// (one or more times) without any --exclude_check flags, the default
// state is to exclude all checks EXCEPT those specifically indicated.
void set_exclude_by_default(bool exclude_by_default) { exclude_by_default_ = exclude_by_default; }
// Copies the given list of check-ids to be included, if otherwise excluded or disabled.
// By default, all checks are included unless explicitly disabled (see linter/main.cc), excluded
// by command line option, or if |excluded_by_default| is set to true.
void set_included_checks(const std::set<std::string>& included_check_ids) {
included_check_ids_ = included_check_ids;
}
// Copies the given list of check-ids to be excluded, unless |exclude_by_default| is true, in
// which case, all checks are excluded if not explicitly included.
void set_excluded_checks(const std::set<std::string>& excluded_check_ids) {
excluded_check_ids_ = excluded_check_ids;
}
// Calling Lint() invokes the callbacks for elements
// of the given |SourceFile|. If a check fails, the callback generates a
// |Finding| and adds it to the given Findings (vector of Finding).
// Lint() is single-threaded, and modifies state of the |Linter| class
// as it evaluates a single file at a time.
//
// * parsed_source - The abstract syntax tree (AST) returned from the FIDL Parser.
// * findings - a std::list of Finding objects (ordered by filename and location) to add
// additional Finding objects to, if triggered for a check. The list may or may not be
// empty.
// * excluded_checks_not_found (optional) - If not nullptr, excluded_checks_not_found is a
// pointer to the caller's std::set of check-ids that were
// excluded and have not yet been removed via a pevious invocation of Lint() (if any). If this
// set is not empty, and a matching check-id is triggered, remove the check-id. An error
// status will be returned by the linter program, after all files have been linted, if any
// excluded check IDs remain in this set.
//
// Returns true if no new findings were generated.
bool Lint(std::unique_ptr<raw::File> const& parsed_source, Findings* findings,
std::set<std::string>* excluded_checks_not_found);
bool Lint(std::unique_ptr<raw::File> const& parsed_source, Findings* findings) {
return Lint(parsed_source, findings, nullptr);
}
private:
// Holds function pointers for an identify case type. For example,
// for "UpperCamelCase" type, |matches| points to is_upper_camel_case()
// and |convert| points to to_upper_camel_case().
struct CaseType {
fit::function<bool(std::string)> matches;
fit::function<std::string(std::string)> convert;
};
// Holds information about a nesting context in a FIDL file, for checks
// that must compare information about the context with information about
// a nested entity. The outer-most context is the FIDL file itself
// (including the file's declared library name). Contexts nested in a
// file's context include type definitions with nested entities, such as
// enum, bits, struct, table, and union.
class Context {
public:
struct RepeatsContextNames;
Context(std::string type, std::string id, CheckDef context_check)
: type_(type), id_(id), context_check_(context_check) {}
// Enables move construction and assignment
Context(Context&& rhs) = default;
Context& operator=(Context&&) = default;
// no copy or assign (move-only or pass by reference)
Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
std::string type() const { return type_; }
std::string id() const { return id_; }
// A |vector| of information about potential violations of the FIDL rubric
// rule that prohibits repeating names from the outer type or library.
// Exceptions to this rule cannot be determined until all nested identifiers
// are reviewed, so this vector saves the required information until that
// time.
std::vector<RepeatsContextNames>& name_repeaters() { return name_repeaters_; }
const std::set<std::string>& words() {
if (words_.empty()) {
auto words = utils::id_to_words(id_);
words_.insert(words.begin(), words.end());
}
return words_;
}
const CheckDef& context_check() const { return context_check_; }
template <typename... Args>
void AddRepeatsContextNames(Args&&... args) {
name_repeaters_.emplace_back(args...);
}
// Stores minimum information needed to construct a |Finding| if a
// nested identifier repeats names from one of its contexts.
// Determination is deferred until all nested identifiers are evaluated
// because some cases of repeated names are allowed if the repeated
// names help differentiate two identifiers that represent different
// parts of the concept represented by the context identifier.
struct RepeatsContextNames {
RepeatsContextNames(std::string a_type, SourceSpan a_span, std::set<std::string> a_repeats)
: type(std::move(a_type)), span(a_span), repeats(std::move(a_repeats)) {}
const std::string type;
const SourceSpan span;
const std::set<std::string> repeats;
};
private:
std::string type_;
std::string id_;
std::set<std::string> words_;
CheckDef context_check_;
std::vector<RepeatsContextNames> name_repeaters_;
};
const std::set<std::string>& permitted_library_prefixes() const;
std::string kPermittedLibraryPrefixesas_string() const;
CheckDef DefineCheck(std::string check_id, std::string message_template);
Finding* AddFinding(SourceSpan source_span, std::string check_id, std::string message);
const Finding* AddFinding(SourceSpan span, const CheckDef& check,
Substitutions substitutions = {}, std::string suggestion_template = "",
std::string replacement_template = "");
template <typename SourceElementSubtypeRefOrPtr>
const Finding* AddFinding(const SourceElementSubtypeRefOrPtr& element, const CheckDef& check,
Substitutions substitutions = {}, std::string suggestion_template = "",
std::string replacement_template = "");
const Finding* AddRepeatedNameFinding(const Context& context,
const Context::RepeatsContextNames& name_repeater);
bool CurrentLibraryIsPlatformSourceLibrary();
bool CurrentFileIsInPlatformSourceTree();
// Initialization and checks at the start of a new file. The |Linter|
// can be called multiple times with many different files.
void NewFile(const raw::File& element);
// If a finding was added, return a pointer to that finding.
const Finding* CheckCase(std::string type, const std::unique_ptr<raw::Identifier>& identifier,
const CheckDef& check_def, const CaseType& case_type);
// CheckRepeatedName() does not add Finding objects immediately. It checks for
// potential violations, but must wait until ExitContext() so the potential
// violation can be compared to its peers.
void CheckRepeatedName(std::string type, const std::unique_ptr<raw::Identifier>& id);
template <typename... Args>
void EnterContext(Args&&... args) {
context_stack_.emplace_front(args...);
}
// Pops the context stack. If any contained types repeat names from the
// context, this function compares the nested identifiers with each other.
// If two nested identifiers repeat different names from the context,
// assume the repeated names were necessary in order to disambiguate the
// concepts represented by each of the nested entities. If not, add Finding
// objects for violating the repeated name rule.
void ExitContext();
std::string GetCopyrightSuggestion();
void AddInvalidCopyrightFinding(SourceSpan span);
void CheckInvalidCopyright(SourceSpan span, std::string line_comment, std::string line_to_match);
bool CopyrightCheckIsComplete();
std::string MakeCopyrightBlock();
// All check types created in during |Linter| construction. The |std::set|
// ensures each CheckDef has a unique |id|, and an iterator will traverse
// the set in lexicographical order.
// Must be defined before constant checks.
std::set<CheckDef> checks_;
// const variables not trivially destructible (static storage is forbidden)
// (https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables)
const CheckDef kLibraryNameDepthCheck;
const CheckDef kLibraryNameComponentCheck;
const CheckDef kRepeatsLibraryNameCheck;
const CheckDef kLibraryPrefixCheck;
const CheckDef kInvalidCopyrightCheck;
const std::vector<std::string> kCopyrightLines;
const std::string kCopyrightBlock;
const std::string kDocAttribute;
const std::regex kYearRegex;
const std::regex kDocCommentRegex;
const std::regex kDisallowedLibraryComponentRegex;
const std::set<std::string> kPermittedLibraryPrefixes;
const std::set<std::string> kStopWords;
std::deque<Context> context_stack_;
size_t line_comments_checked_ = 0;
// Set to true for the first line that does not match the standard
// Copyright block (if checked) so subsequent lines do not have to
// be checked. (Prevents duplicate findings.)
bool added_invalid_copyright_finding_ = false;
// If good copyright lines
size_t good_copyright_lines_found_ = 0;
// 4 digits assumed to be the intended copyright date.
std::string copyright_date_;
// true if a const declaration was entered, and not yet exited.
bool in_const_declaration_ = false;
// The first name in the FIDL library declaration; for example, for:
// library fidl.types;
// |library_prefix_| will be "fidl"
std::string library_prefix_;
bool library_is_platform_source_library_;
std::string filename_;
bool file_is_in_platform_source_tree_;
enum class LintStyle {
IpcStyle,
CStyle,
};
LintStyle lint_style_;
LintingTreeCallbacks callbacks_;
// Case type functions used by CheckCase().
CaseType lower_snake_{utils::is_lower_snake_case, utils::to_lower_snake_case};
CaseType upper_snake_{utils::is_upper_snake_case, utils::to_upper_snake_case};
CaseType upper_camel_{utils::is_upper_camel_case, utils::to_upper_camel_case};
// In IpcStyle, bits, protocols, protocol methods, structs, tables, unions,
// and enums use UpperCamelCase. In CStyle used for syscalls, they
// use lower_camel_case. This member is set in NewFile once the style that's
// being used has been determined.
CheckDef invalid_case_for_decl_name_{"", TemplateString()};
const CheckDef& invalid_case_for_decl_name() const { return invalid_case_for_decl_name_; }
const CaseType& decl_case_type_for_style() const {
switch (lint_style_) {
case LintStyle::IpcStyle:
return upper_camel_;
case LintStyle::CStyle:
return lower_snake_;
}
}
bool exclude_by_default_ = false;
std::set<std::string> included_check_ids_;
std::set<std::string> excluded_check_ids_;
using FindingPtr = std::unique_ptr<Finding>;
// Compare type provides the function for ordering elements in the set, and
// ensuring elements are set-wise unique. The current assumption is that no
// two findings from the same subcategory at the same location are expected.
// By including both source span and subcategory, all expected Findings
// will be unique, and any Finding added with the same location and
// subcategory as another will be considered a bug.
struct FindingPtrCompare {
// Return true if lhs < rhs.
bool operator()(const FindingPtr& lhs, const FindingPtr& rhs) const {
return lhs->span() < rhs->span() ||
(lhs->span() == rhs->span() && lhs->subcategory() < rhs->subcategory());
}
};
// An ordered collection of |std::unique_ptr| to |Finding| objects,
// populated only for the duration of the Visit(), to lint a given FIDL
// file.
//
// Some lint checks can only be assessed after processing more of the FIDL
// (for example, "name-repeats-enclosing-type-name" checks cannot be added
// until all members of the enclosing type are available). Other checks on
// the same members will be added as they are encountered. Since |Finding|
// objects are collected out of source order, an ordered collecttion
// ensures |Finding| objects are in sorted order.
//
// |Finding| objects must be returned in source order (filename, starting
// character, and ending character).
//
// |Finding| objects are sorted by SourceSpan, with additional criteria
// to sort multiple findings for the same source element. If a Finding is
// added to an ordered collection that does not allow duplicates, and the
// sort criteria is not specific enough to avoid a key collision, an
// assertion error will halt the program, to be resolved either by adding
// additional sort criteriea or changing the Finding objects to ensure
// uinqueness.
//
// When the Visit() is over, the |Finding| objects are transferred, in order
// (by filename and source location of each finding, with any duplicates
// also included in the order they were discovered/added) into the |Findings|
// list passed into the Lint() method, and |current_findings_| is cleared.
std::set<FindingPtr, FindingPtrCompare> current_findings_;
};
} // namespace linter
} // namespace fidl
#endif // TOOLS_FIDL_FIDLC_INCLUDE_FIDL_LINTER_H_