blob: 458f5215008574b6925f8997b61ef8eed35af17b [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/fit/function.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <set>
#include <fidl/findings.h>
#include <fidl/linter.h>
#include <fidl/names.h>
#include <fidl/raw_ast.h>
#include <fidl/utils.h>
namespace fidl {
namespace linter {
namespace {
// Convert the SourceElement (start- and end-tokens within the SourceFile)
// to a std::string_view, spanning from the beginning of the start token, to the end
// of the end token. The three methods support classes derived from
// SourceElement, by reference, pointer, or unique_ptr.
static std::string_view to_string_view(const fidl::raw::SourceElement& element) {
return element.span().data();
}
template <typename SourceElementSubtype>
std::string_view to_string_view(const std::unique_ptr<SourceElementSubtype>& element_ptr) {
static_assert(std::is_base_of<fidl::raw::SourceElement, SourceElementSubtype>::value,
"Template parameter type is not derived from SourceElement");
return to_string_view(element_ptr.get());
}
// Convert the SourceElement to a std::string, using the method described above
// for std::string_view.
static std::string to_string(const fidl::raw::SourceElement& element) {
return std::string(to_string_view(element));
}
template <typename SourceElementSubtype>
std::string to_string(const std::unique_ptr<SourceElementSubtype>& element_ptr) {
static_assert(std::is_base_of<fidl::raw::SourceElement, SourceElementSubtype>::value,
"Template parameter type is not derived from SourceElement");
return to_string(*element_ptr.get());
}
} // namespace
std::string Linter::MakeCopyrightBlock() {
std::string copyright_block;
for (auto line : kCopyrightLines) {
copyright_block.append("\n");
copyright_block.append(line);
}
return copyright_block;
}
const std::set<std::string>& Linter::permitted_library_prefixes() const {
return kPermittedLibraryPrefixes;
}
std::string Linter::kPermittedLibraryPrefixesas_string() const {
std::ostringstream ss;
bool first = true;
for (auto& prefix : permitted_library_prefixes()) {
if (!first) {
ss << " | ";
}
ss << prefix;
first = false;
}
return ss.str();
}
// Returns itself. Overloaded to support alternative type references by
// pointer and unique_ptr as needed.
static const fidl::raw::SourceElement& GetElementAsRef(
const fidl::raw::SourceElement& source_element) {
return source_element;
}
static const fidl::raw::SourceElement& GetElementAsRef(const fidl::raw::SourceElement* element) {
return GetElementAsRef(*element);
}
// Returns the pointed-to element as a reference.
template <typename SourceElementSubtype>
const fidl::raw::SourceElement& GetElementAsRef(
const std::unique_ptr<SourceElementSubtype>& element_ptr) {
static_assert(std::is_base_of<fidl::raw::SourceElement, SourceElementSubtype>::value,
"Template parameter type is not derived from SourceElement");
return GetElementAsRef(element_ptr.get());
}
// Add a finding with |Finding| constructor arguments.
// This function is const because the Findings (TreeVisitor) object
// is not modified. It's Findings object (not owned) is updated.
Finding* Linter::AddFinding(SourceSpan span, std::string check_id, std::string message) {
auto result = current_findings_.emplace(new Finding(span, check_id, message));
// Future checks may need to allow multiple findings of the
// same check ID at the same location.
assert(result.second && "Duplicate key. Check criteria in Finding.operator==() and operator<()");
return result.first->get();
}
// Add a finding with optional suggestion and replacement
const Finding* Linter::AddFinding(SourceSpan span, const CheckDef& check,
Substitutions substitutions, std::string suggestion_template,
std::string replacement_template) {
auto* finding = AddFinding(span, check.id(), check.message_template().Substitute(substitutions));
if (finding == nullptr) {
return nullptr;
}
if (suggestion_template.size() > 0) {
if (replacement_template.size() == 0) {
finding->SetSuggestion(TemplateString(suggestion_template).Substitute(substitutions));
} else {
finding->SetSuggestion(TemplateString(suggestion_template).Substitute(substitutions),
TemplateString(replacement_template).Substitute(substitutions));
}
}
return finding;
}
// Add a finding from a SourceElement
template <typename SourceElementSubtypeRefOrPtr>
const Finding* Linter::AddFinding(const SourceElementSubtypeRefOrPtr& element,
const CheckDef& check, Substitutions substitutions,
std::string suggestion_template,
std::string replacement_template) {
return AddFinding(GetElementAsRef(element).span(), check, substitutions, suggestion_template,
replacement_template);
}
CheckDef Linter::DefineCheck(std::string check_id, std::string message_template) {
auto result = checks_.emplace(check_id, TemplateString(message_template));
assert(result.second && "DefineCheck called with a duplicate check_id");
return *result.first;
}
// Returns true if no new findings were generated
bool Linter::Lint(std::unique_ptr<raw::File> const& parsed_source, Findings* findings,
std::set<std::string>* excluded_checks_not_found) {
auto initial_findings_size = findings->size();
callbacks_.Visit(parsed_source);
for (auto& finding_ptr : current_findings_) {
auto check_id = finding_ptr->subcategory();
if (excluded_checks_not_found && !excluded_checks_not_found->empty()) {
excluded_checks_not_found->erase(check_id);
}
bool is_included = included_check_ids_.find(check_id) != included_check_ids_.end();
bool is_excluded =
exclude_by_default_ || excluded_check_ids_.find(check_id) != excluded_check_ids_.end();
if (!is_excluded || is_included) {
findings->emplace_back(std::move(*finding_ptr));
}
}
current_findings_.clear();
return findings->size() == initial_findings_size;
}
void Linter::NewFile(const raw::File& element) {
// Reset file state variables (for a new file)
line_comments_checked_ = 0;
added_invalid_copyright_finding_ = false;
good_copyright_lines_found_ = 0;
copyright_date_ = "";
auto& prefix_component = element.library_name->components.front();
library_prefix_ = to_string(prefix_component);
library_is_platform_source_library_ =
(kPermittedLibraryPrefixes.find(library_prefix_) != kPermittedLibraryPrefixes.end());
filename_ = element.span().source_file().filename();
file_is_in_platform_source_tree_ = false;
auto in_fuchsia_dir_regex = std::regex(R"REGEX(\bfuchsia/)REGEX");
if (std::regex_search(filename_, in_fuchsia_dir_regex)) {
file_is_in_platform_source_tree_ = true;
} else {
file_is_in_platform_source_tree_ = std::ifstream(filename_.c_str()).good();
}
if (library_prefix_ == "zx") {
lint_style_ = LintStyle::CStyle;
invalid_case_for_decl_name_ =
DefineCheck("invalid-case-for-decl-name", "${TYPE} must be named in lower_snake_case");
} else {
lint_style_ = LintStyle::IpcStyle;
invalid_case_for_decl_name_ =
DefineCheck("invalid-case-for-decl-name", "${TYPE} must be named in UpperCamelCase");
}
if (lint_style_ == LintStyle::IpcStyle && !library_is_platform_source_library_) {
// TODO(fxbug.dev/7871): Implement more specific test,
// comparing proposed library prefix to actual
// source path.
std::string replacement = "fuchsia, perhaps?";
AddFinding(element.library_name, kLibraryPrefixCheck,
{
{"ORIGINAL", library_prefix_},
{"REPLACEMENT", replacement},
},
"change '${ORIGINAL}' to ${REPLACEMENT}", "${REPLACEMENT}");
}
// Library names should not have more than four components.
if (element.library_name->components.size() > 4) {
AddFinding(element.library_name, kLibraryNameDepthCheck);
}
// Library name is not checked for CStyle because it must be simply "zx".
if (lint_style_ == LintStyle::IpcStyle) {
for (const auto& component : element.library_name->components) {
if (std::regex_match(to_string(component), kDisallowedLibraryComponentRegex)) {
AddFinding(component, kLibraryNameComponentCheck);
break;
}
}
}
EnterContext("library", NameLibrary(element.library_name->components), kRepeatsLibraryNameCheck);
}
const Finding* Linter::CheckCase(std::string type,
const std::unique_ptr<raw::Identifier>& identifier,
const CheckDef& check_def, const CaseType& case_type) {
std::string id = to_string(identifier);
if (!case_type.matches(id)) {
return AddFinding(identifier, check_def,
{
{"TYPE", type},
{"IDENTIFIER", id},
{"REPLACEMENT", case_type.convert(id)},
},
"change '${IDENTIFIER}' to '${REPLACEMENT}'", "${REPLACEMENT}");
}
return nullptr;
}
void Linter::CheckRepeatedName(std::string type,
const std::unique_ptr<raw::Identifier>& identifier) {
std::string id = to_string(identifier);
auto split_id = utils::id_to_words(id, kStopWords);
std::set<std::string> words;
words.insert(split_id.begin(), split_id.end());
for (auto& context : context_stack_) {
std::set<std::string> repeats;
std::set_intersection(words.begin(), words.end(), context.words().begin(),
context.words().end(), std::inserter(repeats, repeats.begin()));
if (!repeats.empty()) {
context.AddRepeatsContextNames(type, identifier->span(), repeats);
}
}
}
const Finding* Linter::AddRepeatedNameFinding(const Context& context,
const Context::RepeatsContextNames& name_repeater) {
std::string repeated_names;
for (const auto& repeat : name_repeater.repeats) {
if (!repeated_names.empty()) {
repeated_names.append(", ");
}
repeated_names.append(repeat);
}
return AddFinding(name_repeater.span, context.context_check(),
{
{"TYPE", name_repeater.type},
{"REPEATED_NAMES", repeated_names},
{"CONTEXT_TYPE", context.type()},
{"CONTEXT_ID", context.id()},
});
}
std::string Linter::GetCopyrightSuggestion() {
auto copyright_block = kCopyrightBlock;
if (!copyright_date_.empty()) {
copyright_block = TemplateString(copyright_block).Substitute({{"YYYY", copyright_date_}});
}
if (good_copyright_lines_found_ == 0) {
return "Insert missing header:\n" + copyright_block;
} else {
return "Update your header with:\n" + copyright_block;
}
}
void Linter::AddInvalidCopyrightFinding(SourceSpan span) {
if (!added_invalid_copyright_finding_) {
added_invalid_copyright_finding_ = true;
AddFinding(span, kInvalidCopyrightCheck, {}, GetCopyrightSuggestion());
}
}
void Linter::CheckInvalidCopyright(SourceSpan span, std::string line_comment,
std::string line_to_match) {
if (line_comment == line_to_match ||
// TODO(66908): Remove this branch once all platform FIDL files are updated.
line_comment == line_to_match + " All rights reserved.") {
good_copyright_lines_found_++;
return;
}
if (CopyrightCheckIsComplete()) {
return;
}
auto end_it = line_comment.end();
if (line_comment.size() > line_to_match.size()) {
end_it = line_comment.begin() + line_to_match.size();
}
auto first_mismatch = std::mismatch(line_comment.begin(), end_it, line_to_match.begin());
auto index = first_mismatch.first - line_comment.begin();
if (index > 0) {
std::string_view error_view = span.data();
error_view.remove_prefix(index);
auto& source_file = span.source_file();
span = SourceSpan(error_view, source_file);
}
AddInvalidCopyrightFinding(span);
}
bool Linter::CopyrightCheckIsComplete() {
return !file_is_in_platform_source_tree_ || added_invalid_copyright_finding_ ||
good_copyright_lines_found_ >= kCopyrightLines.size();
}
void Linter::ExitContext() {
Context context = std::move(context_stack_.front());
context_stack_.pop_front();
// Check the |RepeatsContextNames| objects in context.name_repeaters(), and
// produce Finding objects for any identifier that is not allowed to repeat
// a name from this |Context|.
//
// This check addresses the FIDL Rubric rule:
//
// Member names must not repeat names from the enclosing type (or
// library) unless the member name is ambiguous without a name from
// the enclosing type...
//
// ...a type DeviceToRoom--that associates a smart device with the room
// it's located in--may need to have members device_id and room_name,
// because id and name are ambiguous; they could refer to either the
// device or the room.
auto& repeaters = context.name_repeaters();
for (size_t i = 1; i < repeaters.size(); i++) {
std::set<std::string> diffs;
std::set_difference(repeaters[i - 1].repeats.begin(), repeaters[i - 1].repeats.end(),
repeaters[i].repeats.begin(), repeaters[i].repeats.end(),
std::inserter(diffs, diffs.begin()));
if (!diffs.empty()) {
// If there are any differences, we have to assume they may be
// disambiguating, which is allowed.
return;
}
}
// If multiple name repeaters in a given context all repeat the same thing,
// then it's obvious they don't disambiguate anything, so add Findings for
// each violator.
for (auto& repeater : repeaters) {
AddRepeatedNameFinding(context, repeater);
}
}
Linter::Linter()
: kLibraryNameDepthCheck(DefineCheck("too-many-nested-libraries",
"Avoid library names with more than three dots")),
kLibraryNameComponentCheck(
DefineCheck("disallowed-library-name-component",
"Library names must not contain the following components: common, service, "
"util, base, f<letter>l, zx<word>")),
kRepeatsLibraryNameCheck(
DefineCheck("name-repeats-library-name",
"${TYPE} names (${REPEATED_NAMES}) must not repeat names from the "
"library '${CONTEXT_ID}'")),
kLibraryPrefixCheck(DefineCheck("wrong-prefix-for-platform-source-library",
"FIDL library name is not currently allowed")),
kInvalidCopyrightCheck(
DefineCheck("invalid-copyright-for-platform-source-library",
"FIDL files defined in the Platform Source Tree (i.e., defined in "
"fuchsia.googlesource.com) must begin with the standard copyright notice")),
kCopyrightLines({
// First line may also contain " All rights reserved."
"// Copyright ${YYYY} The Fuchsia Authors.",
"// Use of this source code is governed by a BSD-style license that can be",
"// found in the LICENSE file.",
}),
kCopyrightBlock(MakeCopyrightBlock()),
kDocAttribute("Doc"),
kYearRegex(R"(\b(\d{4})\b)"),
kDisallowedLibraryComponentRegex(R"(^(common|service|util|base|f[a-z]l|zx\w*)$)"),
kPermittedLibraryPrefixes({
"fuchsia",
"fidl",
"test",
}),
kStopWords({
// clang-format off
"a",
"about",
"above",
"after",
"against",
"all",
"an",
"and",
"any",
"are",
"as",
"at",
"be",
"because",
"been",
"before",
"being",
"below",
"between",
"both",
"but",
"by",
"can",
"did",
"do",
"does",
"doing",
"down",
"during",
"each",
"few",
"for",
"from",
"further",
"had",
"has",
"have",
"having",
"here",
"how",
"if",
"in",
"into",
"is",
"it",
"its",
"itself",
"just",
"more",
"most",
"no",
"nor",
"not",
"now",
"of",
"off",
"on",
"once",
"only",
"or",
"other",
"out",
"over",
"own",
"same",
"should",
"so",
"some",
"such",
"than",
"that",
"the",
"then",
"there",
"these",
"this",
"those",
"through",
"to",
"too",
"under",
"until",
"up",
"very",
"was",
"were",
"what",
"when",
"where",
"which",
"while",
"why",
"will",
"with",
// clang-format on
}) {
// clang-format off
callbacks_.OnFile(
[& linter = *this]
//
(const raw::File& element) {
linter.NewFile(element);
});
// clang-format on
callbacks_.OnLineComment(
[&linter = *this]
//
(const SourceSpan& span, std::string_view line_prefix_view) {
linter.line_comments_checked_++;
if (linter.CopyrightCheckIsComplete() &&
linter.line_comments_checked_ > linter.kCopyrightLines.size()) {
return;
}
// span.position() is not a lightweight operation, but as long as
// the conditions above are checked first, the line number only needs
// to be computed a minimum number of times.
size_t line_number = span.position().line;
std::string line_comment = std::string(span.data());
if (line_number > linter.kCopyrightLines.size()) {
if (!linter.CopyrightCheckIsComplete()) {
linter.AddInvalidCopyrightFinding(span);
}
return;
}
if (linter.copyright_date_.empty()) {
std::smatch match_year;
if (std::regex_search(line_comment, match_year, linter.kYearRegex)) {
linter.copyright_date_ = match_year[1];
}
}
auto line_to_match = linter.kCopyrightLines[line_number - 1];
if (!linter.copyright_date_.empty()) {
line_to_match =
TemplateString(line_to_match).Substitute({{"YYYY", linter.copyright_date_}});
}
linter.CheckInvalidCopyright(span, line_comment, line_to_match);
});
callbacks_.OnExitFile([&linter = *this]
//
(const raw::File& element) {
if (!linter.CopyrightCheckIsComplete()) {
auto& source_file = element.span().source_file();
std::string_view error_view = source_file.data();
error_view.remove_suffix(source_file.data().size());
linter.AddInvalidCopyrightFinding(SourceSpan(error_view, source_file));
}
linter.ExitContext();
});
callbacks_.OnAttribute(
[&linter = *this,
todo_check = DefineCheck("todo-should-not-be-doc-comment",
"TODO comment should use a non-flow-through comment marker"),
regex = std::regex(R"REGEX(^[ \t]*TODO\W)REGEX")]
//
(const raw::Attribute& element) {
if (element.name == linter.kDocAttribute) {
if (std::regex_search(element.value, regex)) {
linter.AddFinding(element, todo_check, {}, "change '///' to '//'", "//");
}
}
});
callbacks_.OnAttribute(
[&linter = *this,
check = DefineCheck("copyright-should-not-be-doc-comment",
"Copyright notice should use non-flow-through comment markers"),
regex = std::regex(R"REGEX(^[ \t]*Copyright \d\d\d\d\W)REGEX", std::regex_constants::icase)]
//
(const raw::Attribute& element) {
if (element.name == linter.kDocAttribute && std::regex_search(element.value, regex)) {
linter.AddFinding(element, check, {}, "change '///' to '//'", "//");
}
});
// TODO(fxbug.dev/7978): Remove this check after issues are resolved with
// trailing comments in existing source and tools
// clang-format off
callbacks_.OnLineComment(
[& linter = *this,
trailing_comment_check = DefineCheck("no-trailing-comment",
"Place comments above the thing being described")]
//
(const SourceSpan& span, std::string_view line_prefix_view) {
if (!utils::IsBlank(line_prefix_view)) {
linter.AddFinding(span, trailing_comment_check);
}
});
// clang-format on
callbacks_.OnUsing(
[&linter = *this,
case_check = DefineCheck("invalid-case-for-primitive-alias",
"Primitive aliases must be named in lower_snake_case"),
&case_type = lower_snake_]
//
(const raw::Using& element) {
if (element.maybe_alias != nullptr) {
linter.CheckCase("primitive alias", element.maybe_alias, case_check, case_type);
linter.CheckRepeatedName("primitive alias", element.maybe_alias);
}
});
auto invalid_case_for_constant =
DefineCheck("invalid-case-for-constant", "${TYPE} must be named in ALL_CAPS_SNAKE_CASE");
callbacks_.OnConstDeclaration(
[&linter = *this, case_check = invalid_case_for_constant, &case_type = upper_snake_]
//
(const raw::ConstDeclaration& element) {
linter.CheckCase("constants", element.identifier, case_check, case_type);
linter.CheckRepeatedName("constant", element.identifier);
linter.in_const_declaration_ = true;
});
callbacks_.OnExitConstDeclaration(
[&linter = *this]
//
(const raw::ConstDeclaration& element) { linter.in_const_declaration_ = false; });
callbacks_.OnEnumMember(
[&linter = *this, case_check = invalid_case_for_constant, &case_type = upper_snake_]
//
(const raw::EnumMember& element) {
linter.CheckCase("enum members", element.identifier, case_check, case_type);
linter.CheckRepeatedName("enum member", element.identifier);
});
callbacks_.OnBitsMember(
[&linter = *this, case_check = invalid_case_for_constant, &case_type = upper_snake_]
//
(const raw::BitsMember& element) {
linter.CheckCase("bitfield members", element.identifier, case_check, case_type);
linter.CheckRepeatedName("bitfield member", element.identifier);
});
auto name_repeats_enclosing_type_name =
DefineCheck("name-repeats-enclosing-type-name",
"${TYPE} names (${REPEATED_NAMES}) must not repeat names from the "
"enclosing ${CONTEXT_TYPE} '${CONTEXT_ID}'");
callbacks_.OnProtocolDeclaration(
[&linter = *this, context_check = name_repeats_enclosing_type_name,
name_contains_service_check = DefineCheck("protocol-name-includes-service",
"Protocols must not include the name 'service.'")]
//
(const raw::ProtocolDeclaration& element) {
linter.CheckCase("protocols", element.identifier, linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
linter.CheckRepeatedName("protocol", element.identifier);
for (auto word : utils::id_to_words(to_string(element.identifier))) {
if (word == "service") {
linter.AddFinding(element.identifier, name_contains_service_check);
break;
}
}
linter.EnterContext("protocol", to_string(element.identifier), context_check);
});
callbacks_.OnExitProtocolDeclaration(
[&linter = *this]
//
(const raw::ProtocolDeclaration& element) { linter.ExitContext(); });
callbacks_.OnMethod([&linter = *this]
//
(const raw::ProtocolMethod& element) {
linter.CheckCase("methods", element.identifier,
linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
linter.CheckRepeatedName("method", element.identifier);
});
callbacks_.OnEvent(
[&linter = *this, event_check = DefineCheck("event-names-must-start-with-on",
"Event names must start with 'On'")]
//
(const raw::ProtocolMethod& element) {
std::string id = to_string(element.identifier);
auto finding =
linter.CheckCase("events", element.identifier, linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
if (finding && finding->suggestion().has_value()) {
auto& suggestion = finding->suggestion().value();
if (suggestion.replacement().has_value()) {
id = suggestion.replacement().value();
}
}
if ((id.compare(0, 2, "On") != 0) || !isupper(id[2])) {
std::string replacement = "On" + id;
linter.AddFinding(element.identifier, event_check,
{
{"IDENTIFIER", id},
{"REPLACEMENT", replacement},
},
"change '${IDENTIFIER}' to '${REPLACEMENT}'", "${REPLACEMENT}");
}
linter.CheckRepeatedName("event", element.identifier);
});
callbacks_.OnEnumDeclaration(
[&linter = *this, context_check = name_repeats_enclosing_type_name]
//
(const raw::EnumDeclaration& element) {
linter.CheckCase("enums", element.identifier, linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
linter.CheckRepeatedName("enum", element.identifier);
linter.EnterContext("enum", to_string(element.identifier), context_check);
});
callbacks_.OnExitEnumDeclaration([&linter = *this]
//
(const raw::EnumDeclaration& element) { linter.ExitContext(); });
callbacks_.OnBitsDeclaration(
[&linter = *this, context_check = name_repeats_enclosing_type_name]
//
(const raw::BitsDeclaration& element) {
linter.CheckCase("bitfields", element.identifier, linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
linter.CheckRepeatedName("bitfield", element.identifier);
linter.EnterContext("bitfield", to_string(element.identifier), context_check);
});
callbacks_.OnExitBitsDeclaration([&linter = *this]
//
(const raw::BitsDeclaration& element) { linter.ExitContext(); });
callbacks_.OnStructDeclaration(
[&linter = *this, context_check = name_repeats_enclosing_type_name]
//
(const raw::StructDeclaration& element) {
linter.CheckCase("structs", element.identifier, linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
linter.CheckRepeatedName("struct", element.identifier);
linter.EnterContext("struct", to_string(element.identifier), context_check);
});
callbacks_.OnExitStructDeclaration(
[&linter = *this]
//
(const raw::StructDeclaration& element) { linter.ExitContext(); });
callbacks_.OnTableDeclaration(
[&linter = *this, context_check = name_repeats_enclosing_type_name]
//
(const raw::TableDeclaration& element) {
linter.CheckCase("tables", element.identifier, linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
linter.CheckRepeatedName("table", element.identifier);
linter.EnterContext("table", to_string(element.identifier), context_check);
});
callbacks_.OnExitTableDeclaration(
[&linter = *this]
//
(const raw::TableDeclaration& element) { linter.ExitContext(); });
callbacks_.OnUnionDeclaration(
[&linter = *this, context_check = name_repeats_enclosing_type_name]
//
(const raw::UnionDeclaration& element) {
linter.CheckCase("unions", element.identifier, linter.invalid_case_for_decl_name(),
linter.decl_case_type_for_style());
linter.CheckRepeatedName("union", element.identifier);
linter.EnterContext("union", to_string(element.identifier), context_check);
});
callbacks_.OnExitUnionDeclaration(
[&linter = *this]
//
(const raw::UnionDeclaration& element) { linter.ExitContext(); });
auto invalid_case_for_decl_member =
DefineCheck("invalid-case-for-decl-member", "${TYPE} must be named in lower_snake_case");
callbacks_.OnParameter(
[&linter = *this, case_check = invalid_case_for_decl_member, &case_type = lower_snake_]
//
(const raw::Parameter& element) {
linter.CheckCase("parameters", element.identifier, case_check, case_type);
});
callbacks_.OnStructMember(
[&linter = *this, case_check = invalid_case_for_decl_member, &case_type = lower_snake_]
//
(const raw::StructMember& element) {
linter.CheckCase("struct members", element.identifier, case_check, case_type);
linter.CheckRepeatedName("struct member", element.identifier);
});
callbacks_.OnTableMember(
[&linter = *this, case_check = invalid_case_for_decl_member, &case_type = lower_snake_]
//
(const raw::TableMember& element) {
if (element.maybe_used == nullptr)
return;
linter.CheckCase("table members", element.maybe_used->identifier, case_check, case_type);
linter.CheckRepeatedName("table member", element.maybe_used->identifier);
});
callbacks_.OnUnionMember(
[&linter = *this, case_check = invalid_case_for_decl_member, &case_type = lower_snake_]
//
(const raw::UnionMember& element) {
if (element.maybe_used == nullptr)
return;
linter.CheckCase("union members", element.maybe_used->identifier, case_check, case_type);
linter.CheckRepeatedName("union member", element.maybe_used->identifier);
});
// clang-format off
callbacks_.OnTypeConstructorOld(
[& linter = *this,
string_bounds_check = DefineCheck("string-bounds-not-specified",
"Specify bounds for string"),
vector_bounds_check = DefineCheck("vector-bounds-not-specified",
"Specify bounds for vector")]
//
(const raw::TypeConstructorOld& element) {
if (element.identifier->components.size() != 1) {
return;
}
auto type = to_string(element.identifier->components[0]);
if (!linter.in_const_declaration_) {
if (type == "string" && element.maybe_size == nullptr) {
linter.AddFinding(element.identifier, string_bounds_check);
}
if (type == "vector" && element.maybe_size == nullptr) {
linter.AddFinding(element.identifier, vector_bounds_check);
}
}
});
// clang-format on
}
} // namespace linter
} // namespace fidl