blob: 1e2c9a01641a2ba1e007750e870c6fe4e755ec6e [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 <zircon/assert.h>
#include <sstream>
#include <utility>
#include "tools/fidl/fidlc/src/findings.h"
#include "tools/fidl/fidlc/src/template_string.h"
#include "tools/fidl/fidlc/src/utils.h"
#include "tools/fidl/fidlc/tests/test_library.h"
namespace fidlc {
namespace {
#define ASSERT_FINDINGS(TEST) \
{ \
SCOPED_TRACE("ASSERT_FINDINGS failed"); \
(TEST).ExpectFindings(); \
}
#define ASSERT_FINDINGS_IN_ANY_POSITION(TEST) \
{ \
SCOPED_TRACE("ASSERT_FINDINGS_IN_ANY_POSITION failed"); \
(TEST).ExpectFindingsInAnyPosition(); \
}
#define ASSERT_NO_FINDINGS(TEST) \
{ \
SCOPED_TRACE("ASSERT_NO_FINDINGS failed"); \
(TEST).ExpectNoFindings(); \
}
class LintTest {
public:
LintTest() = default;
// Adds a Finding to the back of the list of Findings.
LintTest& AddFinding(const std::string& check_id, const std::string& message,
const std::string& violation_string = "${TEST}", std::string suggestion = "",
std::string replacement = "") {
ZX_ASSERT_MSG(!source_template_.str().empty(),
"source_template() must be called before AddFinding()");
std::string template_string = source_template_.str();
size_t start = template_string.find(violation_string);
if (start == std::string::npos) {
std::cout << "ERROR: violation_string '" << violation_string
<< "' was not found in template string:\n"
<< template_string;
ZX_PANIC("violation_string not found in template string");
}
// Note, if there are any substitution variables in the template that
// precede the violation_string, the test will probably fail because the
// string location will probably be different after substitution.
std::string expanded_violation_string =
TemplateString(violation_string).Substitute(substitutions_);
auto span = library().source_span(start, expanded_violation_string.size());
auto& finding = expected_findings_.emplace_back(span, check_id, message);
if (suggestion.empty()) {
suggestion = default_suggestion_;
}
if (replacement.empty()) {
replacement = default_replacement_;
}
if (!suggestion.empty()) {
if (replacement.empty()) {
finding.SetSuggestion(suggestion);
} else {
finding.SetSuggestion(suggestion, replacement);
}
}
return *this;
}
// Adds a Finding to the back of the list of Findings using the default
// check_id and message (via previous calls to check_id() and message()).
LintTest& AddFinding(const std::string& violation_string = "${TEST}") {
return AddFinding(default_check_id_, default_message_, violation_string);
}
// Optional description of what is being tested. This can help when
// reading the code or debugging a failed test, particularly if
// it's not obvious what is being tested.
// |that_| is automatically cleared after test execution in case
// a follow-up test with a different purpose does not set a new
// value.
LintTest& that(std::string that) {
that_ = std::move(that);
return *this;
}
LintTest& filename(std::string filename) {
filename_ = std::move(filename);
return *this;
}
LintTest& check_id(std::string check_id) {
default_check_id_ = std::move(check_id);
return *this;
}
LintTest& message(std::string message) {
default_message_ = std::move(message);
return *this;
}
LintTest& suggestion(const std::string& suggestion) {
default_suggestion_ = suggestion;
if (!expected_findings_.empty()) {
Finding& finding = expected_findings_.back();
finding.SetSuggestion(suggestion);
}
return *this;
}
LintTest& replacement(const std::string& replacement) {
default_replacement_ = replacement;
if (!expected_findings_.empty()) {
Finding& finding = expected_findings_.back();
ZX_ASSERT_MSG(finding.suggestion().has_value(),
"|suggestion| must be added before |replacement|");
auto description = finding.suggestion()->description();
finding.SetSuggestion(description, replacement);
}
return *this;
}
LintTest& source_template(std::string template_str) {
source_template_ = TemplateString(std::move(template_str));
return *this;
}
LintTest& substitute(const Substitutions& substitutions) {
substitutions_ = substitutions;
return *this;
}
// Shorthand for the common occurrence of a single substitution variable.
LintTest& substitute(const std::string& var_name, const std::string& value) {
return substitute({{var_name, value}});
}
LintTest& include_checks(std::vector<std::string> included_check_ids) {
included_check_ids_ =
std::set<std::string>(included_check_ids.begin(), included_check_ids.end());
return *this;
}
LintTest& exclude_checks(std::vector<std::string> excluded_check_ids) {
excluded_check_ids_ =
std::set<std::string>(excluded_check_ids.begin(), excluded_check_ids.end());
return *this;
}
LintTest& excluded_checks_to_confirm(std::vector<std::string> excluded_check_ids_to_confirm) {
excluded_check_ids_to_confirm_ = std::set<std::string>(excluded_check_ids_to_confirm.begin(),
excluded_check_ids_to_confirm.end());
return *this;
}
LintTest& exclude_by_default(bool exclude_by_default) {
exclude_by_default_ = exclude_by_default;
return *this;
}
void ExpectNoFindings() { execute(/*expect_findings=*/false); }
void ExpectFindings() { execute(/*expect_findings=*/true); }
void ExpectFindingsInAnyPosition() {
execute(/*expect_findings=*/true,
/*assert_positions_match=*/false);
}
private:
// Removes all expected findings previously added with AddFinding().
void execute_helper(bool expect_findings, bool assert_positions_match) {
std::ostringstream ss;
if (default_check_id_.empty()) {
ss << "\nFailed test";
} else {
ss << "\nFailed test for check '" << default_check_id_ << "'";
}
if (!that_.empty()) {
ss << "\nthat " << that_;
}
ss << ":\n";
// Start with checks for invalid test construction:
auto context = (ss.str() + "Bad test!");
SCOPED_TRACE(context);
if (expect_findings && expected_findings_.empty()) {
ASSERT_FALSE(default_message_.empty());
AddFinding(default_check_id_, default_message_);
}
ASSERT_FALSE((!expect_findings) && (!expected_findings_.empty()));
ASSERT_NO_FATAL_FAILURE(ValidTest());
// The test looks good, so run the linter, and update the context
// value by replacing "Bad test!" with the FIDL source code.
bool passed = library().Lint({.included_check_ids = included_check_ids_,
.excluded_check_ids = excluded_check_ids_,
.exclude_by_default = exclude_by_default_,
.excluded_checks_not_found = &excluded_check_ids_to_confirm_});
EXPECT_EQ(passed, library().findings().empty());
if (!excluded_check_ids_to_confirm_.empty()) {
ss << "Excluded check-ids not found: \n";
for (auto& check_id : excluded_check_ids_to_confirm_) {
ss << " * " << check_id << '\n';
}
context = ss.str();
EXPECT_TRUE(excluded_check_ids_to_confirm_.empty());
}
std::string source_code = std::string(library().source_file().data());
if (source_code.back() == '\0') {
source_code.resize(source_code.size() - 1);
}
ss << source_code;
context = ss.str();
auto finding = library().findings().begin();
auto expected_finding = expected_findings_.begin();
while (finding != library().findings().end() && expected_finding != expected_findings_.end()) {
CompareExpectedToActualFinding(*expected_finding, *finding, ss.str(), assert_positions_match);
expected_finding++;
finding++;
}
if (finding != library().findings().end()) {
PrintFindings(ss, finding, library().findings().end(), "UNEXPECTED FINDINGS");
context = ss.str();
bool has_unexpected_findings = true;
EXPECT_FALSE(has_unexpected_findings);
}
if (expected_finding != expected_findings_.end()) {
PrintFindings(ss, expected_finding, expected_findings_.end(), "EXPECTED FINDINGS NOT FOUND");
context = ss.str();
bool expected_findings_not_found = true;
EXPECT_FALSE(expected_findings_not_found);
}
}
void Reset() {
library_.reset();
expected_findings_.clear();
included_check_ids_.clear();
excluded_check_ids_.clear();
excluded_check_ids_to_confirm_.clear();
exclude_by_default_ = false;
that_ = "";
}
void execute(bool expect_findings, bool assert_positions_match = true) {
execute_helper(expect_findings, assert_positions_match);
Reset();
}
void ValidTest() const {
ASSERT_FALSE(source_template_.str().empty()) << "Missing source template";
if (!substitutions_.empty()) {
ASSERT_EQ(source_template_.Substitute(substitutions_, false),
source_template_.Substitute(substitutions_, true))
<< "Missing template substitutions";
}
if (expected_findings_.empty()) {
ASSERT_FALSE(default_check_id_.empty()) << "Missing check_id";
} else {
auto& expected_finding = expected_findings_.front();
ASSERT_FALSE(expected_finding.subcategory().empty()) << "Missing check_id";
ASSERT_FALSE(expected_finding.message().empty()) << "Missing message";
ASSERT_TRUE(expected_finding.span().valid()) << "Missing position";
}
}
// Complex templates with more than one substitution variable will typically
// throw off the location match. Set |assert_positions_match| to false to
// skip this check.
static void CompareExpectedToActualFinding(const Finding& expectf, const Finding& finding,
const std::string& test_context,
bool assert_positions_match) {
std::ostringstream ss;
ss << finding.span().position_str() << ": ";
PrintFinding(ss, finding);
auto context = (test_context + ss.str());
SCOPED_TRACE(context);
ASSERT_EQ(expectf.subcategory(), finding.subcategory());
if (assert_positions_match) {
ASSERT_EQ(expectf.span().position_str(), finding.span().position_str());
}
ASSERT_EQ(expectf.message(), finding.message());
ASSERT_EQ(expectf.suggestion().has_value(), finding.suggestion().has_value());
if (finding.suggestion().has_value()) {
ASSERT_EQ(expectf.suggestion()->description(), finding.suggestion()->description());
ASSERT_EQ(expectf.suggestion()->replacement().has_value(),
finding.suggestion()->replacement().has_value());
if (finding.suggestion()->replacement().has_value()) {
ASSERT_EQ(*expectf.suggestion()->replacement(), *finding.suggestion()->replacement());
}
}
}
template <typename Iter>
void PrintFindings(std::ostream& os, Iter finding, Iter end, std::string_view title) {
os << "\n\n";
os << "============================\n";
os << title << ":\n";
os << "============================\n";
for (; finding != end; finding++) {
os << finding->span().position_str() << ": ";
PrintFinding(os, *finding);
os << '\n';
}
os << "============================\n";
}
TestLibrary& library() {
if (!library_) {
ZX_ASSERT_MSG(!source_template_.str().empty(),
"source_template() must be set before library() is called");
library_ = std::make_unique<TestLibrary>();
library_->AddSource(filename_, source_template_.Substitute(substitutions_));
}
return *library_;
}
std::string that_; // optional description of what is being tested
std::string filename_ = "example.fidl";
std::string default_check_id_;
std::string default_message_;
std::string default_suggestion_;
std::string default_replacement_;
std::set<std::string> included_check_ids_;
std::set<std::string> excluded_check_ids_;
std::set<std::string> excluded_check_ids_to_confirm_;
bool exclude_by_default_ = false;
Findings expected_findings_;
TemplateString source_template_;
Substitutions substitutions_;
std::unique_ptr<TestLibrary> library_;
};
TEST(LintFindingsTests, ModifierOrder) {
LintTest test;
test.check_id("modifier-order").source_template(R"FIDL(
library fidl.a;
type MyUnion = ${TEST} union {
1: foo bool;
};
)FIDL");
test.substitute("TEST", "flexible");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "strict");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "flexible resource");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "strict resource");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "resource flexible")
.message("Strictness modifier on union must always precede the resource modifier")
.suggestion("move 'flexible' modifier before resource modifier for union");
ASSERT_FINDINGS(test);
test.substitute("TEST", "resource strict")
.message("Strictness modifier on union must always precede the resource modifier")
.suggestion("move 'strict' modifier before resource modifier for union");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, ExplicitFlexibleModifier) {
std::map<std::string, std::string> named_templates = {
{"bitfield", R"FIDL(
library fidl.a;
type MyBits = ${TEST}bits : uint32 {
VALUE = 1;
};
)FIDL"},
{"enum", R"FIDL(
library fidl.a;
type MyEnum = ${TEST}enum {
VALUE = 1;
};
)FIDL"},
{"union", R"FIDL(
library fidl.a;
type MyUnion = ${TEST}resource union {
1: member bool;
};
)FIDL"},
{"union", R"FIDL(
library fidl.a;
type MyStruct = struct{
inner ${TEST}resource union {
1: member bool;
};
};
)FIDL"},
{"bitfield", R"FIDL(
library fidl.a;
protocol Foo {
Bar(struct {
options ${TEST}bits {
OPTION_A = 1;
OPTION_B = 2;
}
});
};
)FIDL"},
};
for (auto const& named_template : named_templates) {
LintTest test;
test.check_id("explicit-flexible-modifier").source_template(named_template.second);
test.substitute("TEST", "flexible ");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "strict ");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "")
.message(named_template.first + " must have an explicit 'flexible' modifier")
.suggestion("add 'flexible' modifier before " + named_template.first + " keyword");
ASSERT_FINDINGS(test);
}
}
TEST(LintFindingsTests, ConstantShouldUseCommonPrefixSuffixPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning for "MINIMUM_..." or "MAXIMUM...", or maybe(?) "..._CAP" Also for instance
// "SET_CLIENT_NAME_MAX_LEN" -> "MAX_CLIENT_NAME_LEN" or MAX_LEN_CLIENT_NAME", so detect
// "_MAX" or "_MIN" as separate words in middle or at end of identifier.
LintTest test;
test.check_id("constant-should-use-common-prefix-suffix")
.message(
"Constants should use the standard prefix and/or suffix for common concept, "
"such as MIN and MAX, rather than MINIMUM and MAXIMUM, respectively.")
.source_template(R"FIDL(
library fidl.a;
const ${TEST} uint64 = 1234;
)FIDL");
test.substitute("TEST", "MIN_HEIGHT");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "MAX_HEIGHT");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "NAME_MIN_LEN");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "NAME_MAX_LEN");
ASSERT_NO_FINDINGS(test);
// Not yet determined if the standard should be LEN or LENGTH, or both
// test.substitute("TEST", "BYTES_LEN");
// ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "THRESHOLD_MIN");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "THRESHOLD_MAX");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "MINIMUM_HEIGHT")
.suggestion("change 'MINIMUM_HEIGHT' to 'MIN_HEIGHT'")
.replacement("MIN_HEIGHT");
ASSERT_FINDINGS(test);
test.substitute("TEST", "MAXIMUM_HEIGHT")
.suggestion("change 'MAXIMUM_HEIGHT' to 'MAX_HEIGHT'")
.replacement("MAX_HEIGHT");
ASSERT_FINDINGS(test);
test.substitute("TEST", "NAME_MINIMUM_LEN")
.suggestion("change 'NAME_MINIMUM_LEN' to 'NAME_MIN_LEN'")
.replacement("NAME_MIN_LEN");
ASSERT_FINDINGS(test);
test.substitute("TEST", "NAME_MAXIMUM_LEN")
.suggestion("change 'NAME_MAXIMUM_LEN' to 'NAME_MAX_LEN'")
.replacement("NAME_MAX_LEN");
ASSERT_FINDINGS(test);
// Not yet determined if the standard should be LEN or LENGTH, or both
// test.substitute("TEST", "BYTES_LENGTH")
// .suggestion("change 'BYTES_LENGTH' to 'BYTES_LEN'")
// .replacement("BYTES_LEN");
// ASSERT_FINDINGS(test);
test.substitute("TEST", "THRESHOLD_MINIMUM")
.suggestion("change 'THRESHOLD_MINIMUM' to 'THRESHOLD_MIN'")
.replacement("THRESHOLD_MIN");
ASSERT_FINDINGS(test);
test.substitute("TEST", "THRESHOLD_MAXIMUM")
.suggestion("change 'THRESHOLD_MAXIMUM' to 'THRESHOLD_MAX'")
.replacement("THRESHOLD_MAX");
ASSERT_FINDINGS(test);
test.substitute("TEST", "THRESHOLD_CAP")
.suggestion("change 'THRESHOLD_CAP' to 'THRESHOLD_MAX'")
.replacement("THRESHOLD_MAX");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, CopyrightShouldNotBeDocComment) {
LintTest test;
test.check_id("copyright-should-not-be-doc-comment")
.message("Copyright notice should use non-flow-through comment markers")
.source_template(R"FIDL(${TEST} Copyright 2019 The Fuchsia Authors. All rights reserved.
${TEST} Use of this source code is governed by a BSD-style license that can be
${TEST} found in the LICENSE file.
library fidl.a;
)FIDL");
test.substitute("TEST", "//");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "///").suggestion("change '///' to '//'").replacement("//");
ASSERT_FINDINGS(test);
test.that("capitalization is not important")
.source_template(R"FIDL(${TEST} copyright 2019 The Fuchsia Authors. All rights reserved.
${TEST} Use of this source code is governed by a BSD-style license that can be
${TEST} found in the LICENSE file.
library fidl.a;
)FIDL");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, DisallowedLibraryNameComponent) {
LintTest test;
test.check_id("disallowed-library-name-component")
.message(
"Library names must not contain the following components: common, service, util, "
"base, f<letter>l, zx<word>")
.source_template(R"FIDL(
library fidl.${TEST};
)FIDL");
test.substitute("TEST", "display");
ASSERT_NO_FINDINGS(test);
// Bad test: zx<word>
test.substitute("TEST", "zxsocket");
// no suggestion
ASSERT_FINDINGS(test);
// Bad test: f<letter>l
test.substitute("TEST", "ful");
// no suggestion
ASSERT_FINDINGS(test);
// Bad test: banned words like "common"
test.substitute("TEST", "common");
// no suggestion
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, ProtocolNameIncludesService) {
// Error if ends in "Service", warning if includes "Service" as a word, but "Serviceability"
// ("Service" is only part of a word) is OK.
LintTest test;
test.check_id("protocol-name-includes-service")
.message("Protocols must not include the name 'service.'")
.source_template(R"FIDL(
library fidl.a;
protocol ${TEST} {};
)FIDL");
test.substitute("TEST", "TestProtocol");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "ServiceabilityProtocol");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "TestServiceabilityProtocol");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "ProtocolForServiceability");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "Service");
ASSERT_FINDINGS(test);
test.substitute("TEST", "ServiceProtocol");
ASSERT_FINDINGS(test);
test.substitute("TEST", "TestServiceProtocol");
ASSERT_FINDINGS(test);
test.substitute("TEST", "ProtocolForService");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, EventNamesMustStartWithOn) {
LintTest test;
test.check_id("event-names-must-start-with-on")
.message("Event names must start with 'On'")
.source_template(R"FIDL(
library fidl.a;
protocol TestProtocol {
-> ${TEST}();
};
)FIDL");
test.substitute("TEST", "OnPress");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "Press").suggestion("change 'Press' to 'OnPress'").replacement("OnPress");
ASSERT_FINDINGS(test);
test.substitute("TEST", "OntologyUpdate")
.suggestion("change 'OntologyUpdate' to 'OnOntologyUpdate'")
.replacement("OnOntologyUpdate");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, ExcessiveNumberOfSeparateProtocolsForFilePleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning(?) if a fidl file contains more than some tolerance cap number of protocols.
//
// Or if a directory of fidl files contains more than some tolerance number of files AND any
// fidl file(s) in that directory contains more than some smaller cap number of protocols per
// fidl file. The fuchsia.ledger would be a good one to look at since it defines many protocols.
// We do not have public vs private visibility yet, and the cap may only be needed for public
// things.
LintTest test;
test.check_id("excessive-number-of-separate-protocols-for-file")
.message(
"Some libraries create separate protocol instances for every logical object in "
"the protocol, but this approach has a number of disadvantages:")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, ExcessiveNumberOfSeparateProtocolsForLibraryPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Or if a directory of fidl files contains more than some tolerance number of files AND any
// fidl file(s) in that directory contains more than some smaller cap number of protocols per
// fidl file. The fuchsia.ledger would be a good one to look at since it defines many protocols.
// We do not have public vs private visibility yet, and the cap may only be needed for public
// things.
LintTest test;
test.check_id("excessive-number-of-separate-protocols-for-library")
.message(
"Some libraries create separate protocol instances for every logical object in "
"the protocol, but this approach has a number of disadvantages:")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, InconsistentTypeForRecurringFileConceptPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
LintTest test;
test.check_id("inconsistent-type-for-recurring-file-concept")
.message("Use consistent types for the same concept")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, InconsistentTypeForRecurringLibraryConceptPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
LintTest test;
test.check_id("inconsistent-type-for-recurring-library-concept")
.message("Ideally, types would be used consistently across library boundaries")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, InvalidCaseForConstant) {
std::map<std::string, std::string> named_templates = {
{"constants", R"FIDL(
library fidl.a;
const ${TEST} uint64 = 1234;
)FIDL"},
{"enum members", R"FIDL(
library fidl.a;
type Int8Enum = flexible enum : int8 {
${TEST} = -1;
};
)FIDL"},
{"bitfield members", R"FIDL(
library fidl.a;
type Uint32Bitfield = flexible bits : uint32 {
${TEST} = 0x00000004;
};
)FIDL"},
};
for (auto const& named_template : named_templates) {
LintTest test;
test.check_id("invalid-case-for-constant")
.message(named_template.first + " must be named in ALL_CAPS_SNAKE_CASE")
.source_template(named_template.second);
test.substitute("TEST", "SOME_CONST");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "some_CONST")
.suggestion("change 'some_CONST' to 'SOME_CONST'")
.replacement("SOME_CONST");
ASSERT_FINDINGS(test);
test.substitute("TEST", "kSomeConst")
.suggestion("change 'kSomeConst' to 'SOME_CONST'")
.replacement("SOME_CONST");
ASSERT_FINDINGS(test);
}
}
TEST(LintFindingsTests, InvalidCaseForDeclMember) {
std::map<std::string, std::string> named_templates = {
{"struct members", R"FIDL(
library fidl.a;
protocol TestProtocol {
SomeMethod(struct { ${TEST} string:64; });
};
)FIDL"},
{"struct members", R"FIDL(
library fidl.a;
type DeclName = struct {
${TEST} string:64;
};
)FIDL"},
{"table members", R"FIDL(
library fidl.a;
type DeclName = table {
1: ${TEST} string:64;
};
)FIDL"},
{"union members", R"FIDL(
library fidl.a;
type DeclName = flexible union {
1: ${TEST} string:64;
};
)FIDL"},
{"union members", R"FIDL(
library fidl.a;
type DeclName = struct {
decl_member flexible union {
1: ${TEST} string:64;
};
};
)FIDL"},
};
for (auto const& named_template : named_templates) {
LintTest test;
test.check_id("invalid-case-for-decl-member")
.message(named_template.first + " must be named in lower_snake_case")
.source_template(named_template.second);
test.substitute("TEST", "agent_request_count");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "agentRequestCount")
.suggestion("change 'agentRequestCount' to 'agent_request_count'")
.replacement("agent_request_count");
ASSERT_FINDINGS(test);
}
}
TEST(LintFindingsTests, InvalidCaseForDeclName) {
std::map<std::string, std::string> named_templates = {
{"protocols", R"FIDL(
library fidl.a;
protocol ${TEST} {};
)FIDL"},
{"methods", R"FIDL(
library fidl.a;
protocol TestProtocol {
${TEST}();
};
)FIDL"},
{"enums", R"FIDL(
library fidl.a;
type ${TEST} = flexible enum : int8 {
SOME_CONST = -1;
};
)FIDL"},
{"bitfields", R"FIDL(
library fidl.a;
type ${TEST} = flexible bits : uint32 {
SOME_BIT = 0x00000004;
};
)FIDL"},
{"structs", R"FIDL(
library fidl.a;
type ${TEST} = struct {
decl_member string:64;
};
)FIDL"},
{"tables", R"FIDL(
library fidl.a;
type ${TEST} = table {
1: decl_member string:64;
};
)FIDL"},
{"unions", R"FIDL(
library fidl.a;
type ${TEST} = flexible union {
1: decl_member string:64;
};
)FIDL"},
};
for (auto const& named_template : named_templates) {
LintTest test;
test.check_id("invalid-case-for-decl-name")
.message(named_template.first + " must be named in UpperCamelCase")
.source_template(named_template.second);
test.substitute("TEST", "UrlLoader");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "URLLoader")
.suggestion("change 'URLLoader' to 'UrlLoader'")
.replacement("UrlLoader");
ASSERT_FINDINGS(test);
test.substitute("TEST", "url_loader")
.suggestion("change 'url_loader' to 'UrlLoader'")
.replacement("UrlLoader");
ASSERT_FINDINGS(test);
}
}
TEST(LintFindingsTests, InvalidCaseForDeclNameForEvent) {
LintTest test;
test.check_id("invalid-case-for-decl-name")
.message("events must be named in UpperCamelCase")
.source_template(R"FIDL(
library fidl.a;
protocol TestProtocol {
-> ${TEST}();
};
)FIDL");
test.substitute("TEST", "OnUrlLoader");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "OnURLLoader")
.suggestion("change 'OnURLLoader' to 'OnUrlLoader'")
.replacement("OnUrlLoader");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, InvalidCaseForPrimitiveAlias) {
LintTest test;
test.check_id("invalid-case-for-using-alias")
.message("Using aliases must be named in lower_snake_case")
.source_template(R"FIDL(
library fidl.a;
using zx as ${TEST};
alias Unused = ${TEST}.Handle;
)FIDL");
test.substitute("TEST", "what_if_someone_does_this");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "WhatIfSomeoneDoes_This")
.suggestion("change 'WhatIfSomeoneDoes_This' to 'what_if_someone_does_this'")
.replacement("what_if_someone_does_this");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, InvalidCopyrightForPlatformSourceLibrary) {
TemplateString copyright_template(
R"FIDL(// 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.)FIDL");
auto copyright_2019 = copyright_template.Substitute({{"YYYY", "2019"}});
auto copyright_2020 = copyright_template.Substitute({{"YYYY", "2020"}});
LintTest test;
test.filename("fuchsia/example.fidl")
.check_id("invalid-copyright-for-platform-source-library")
.message(
"FIDL files defined in the Platform Source Tree (i.e., defined in "
"fuchsia.googlesource.com) must begin with the standard copyright notice");
test.source_template(copyright_2019 + R"FIDL(
library fidl.a;
)FIDL");
ASSERT_NO_FINDINGS(test);
TemplateString copyright_with_all_rights_reserved(
R"FIDL(// Copyright 2020 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.)FIDL");
test.source_template(copyright_2019 + R"FIDL(
library fidl.a;
)FIDL");
ASSERT_NO_FINDINGS(test);
test.that("the rubric does not mandate a blank line before the library name")
.source_template(copyright_2019 + R"FIDL(
library fidl.a;
)FIDL");
ASSERT_NO_FINDINGS(test);
test.that("the date doesn't have to match").source_template(copyright_2020 + R"FIDL(
library fidl.a;
)FIDL");
ASSERT_NO_FINDINGS(test);
test.that("missing copyright will produce a suggestion with ${YYYY}")
.source_template(R"FIDL(library fidl.a;
)FIDL")
.suggestion("Insert missing header:\n\n" + copyright_template.str())
.AddFinding("library fidl.a;");
ASSERT_FINDINGS(test);
test.that("the copyright must start on the first line")
.source_template("\n" + copyright_2019 + R"FIDL(
library fidl.a;
)FIDL")
.suggestion("Insert missing header:\n\n" + copyright_2019)
.AddFinding("Copyright");
ASSERT_FINDINGS(test);
test.that("a bad or missing date will produce a suggestion with ${YYYY}")
.source_template(R"FIDL(// Copyright 20019 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.
library fidl.a;
)FIDL")
.suggestion("Insert missing header:\n\n" + copyright_template.str())
.AddFinding("20019");
ASSERT_FINDINGS(test);
test.that("the words must have the correct case")
.source_template(R"FIDL(// 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.
library fidl.a;
)FIDL")
.suggestion("Insert missing header:\n\n" + copyright_2019)
.AddFinding("OPYRIGHT");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(// Sloppyright 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.
library fidl.a;
)FIDL")
.suggestion("Insert missing header:\n\n" + copyright_2019)
.AddFinding("Sloppyright");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(// Copyright 2019 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library fidl.a;
)FIDL")
.suggestion("Insert missing header:\n\n" + copyright_2019)
.AddFinding("Authors");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by an anarchy license that can be
// found in the LICENSE file.
library fidl.a;
)FIDL")
.suggestion("Update your header with:\n\n" + copyright_2019)
.AddFinding("n anarchy");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(// 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 README.md file.
library fidl.a;
)FIDL")
.suggestion("Update your header with:\n\n" + copyright_2019)
.AddFinding("README.md");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(// Copyright ${YYYY} 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.
library fidl.a;
)FIDL")
.suggestion("Update your header with:\n\n" + copyright_template.str())
.AddFinding("// Copyright");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(// 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.
library fidl.a;
)FIDL")
.suggestion("Update your header with:\n\n" + copyright_2019)
.AddFinding("// Copyright");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
library fidl.a;
)FIDL")
.suggestion("Update your header with:\n\n" + copyright_2019)
.AddFinding("// Copyright");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(${BLANK_LINE}
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library fidl.a;
)FIDL")
.substitute("BLANK_LINE", "")
.suggestion("Update your header with:\n\n" + copyright_template.str())
.AddFinding("${BLANK_LINE}");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, LibraryNameDoesNotMatchFilePathPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
LintTest test;
test.check_id("library-name-does-not-match-file-path")
.message(
"The <library> directory is named using the dot-separated name of the FIDL "
"library")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, ManagerProtocolsAreDiscouragedPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
LintTest test;
test.check_id("manager-protocols-are-discouraged")
.message(
"The name Manager may be used as a name of last resort for a protocol with broad "
"scope")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, MethodReturnStatusMissingOkPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning or error(?) if returning a "status" enum that does not have an OK value. Note there
// will be (or is) new guidance here.
//
// From the rubric:
//
// If a method can return either an error or a result, use the following pattern:
//
// enum MyStatus { OK; FOO; BAR; ... };
//
// protocol Frobinator {
// 1: Frobinate(...) -> (MyStatus status, FrobinateResult? result);
// };
LintTest test;
test.check_id("method-return-status-missing-ok")
.message("") // TBD
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, MethodReturnsStatusWithNonOptionalResultPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning if return a status and a non-optional result? we now have another more expressive
// pattern for this, this section should be updated. Specifically, see:
// https://fuchsia.dev/fuchsia-src/development/languages/fidl/reference/ftp/ftp-014.md.
LintTest test;
test.check_id("method-returns-status-with-non-optional-result")
.message("") // TBD
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, MethodShouldPipelineProtocolsPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Error(?) if the return tuple contains one value of another FIDL protocol type. Returning a
// protocol is better done by sending a request for pipelining. This will be hard to lint at the
// raw level, because you do not know to differentiate Bar from a protocol vs a message vs a bad
// name since resolution is done later. This may call for linting to be done on the JSON IR.
LintTest test;
test.check_id("method-should-pipeline-protocols")
.message("") // TBD
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, NoCommonlyReservedWordsPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
LintTest test;
test.check_id("no-commonly-reserved-words")
.message("Avoid commonly reserved words")
.source_template(R"FIDL(
library fidl.a;
using zx as ${TEST};
)FIDL");
// Unique union of reserved words from:
// FIDL, C++, Rust, Dart, Go, Java, JavaScript, and TypeScript
auto checked_words = {
"_",
"abstract",
"and",
"and_eq",
"any",
"array",
"as",
"asm",
"assert",
"async",
"auto",
"await",
"become",
"bitand",
"bitor",
"bits",
"bool",
"boolean",
"box",
"break",
"byte",
"case",
"catch",
"chan",
"char",
"class",
"compl",
"const",
"const_cast",
"constructor",
"continue",
"covariant",
"crate",
"debugger",
"declare",
"default",
"defer",
"deferred",
"delete",
"do",
"double",
"dyn",
"dynamic",
"dynamic_cast",
"else",
"enum",
"error",
"explicit",
"export",
"extends",
"extern",
"external",
"factory",
"fallthrough",
"false",
"final",
"finally",
"float",
"fn",
"for",
"friend",
"from",
"func",
"function",
"get",
"go",
"goto",
"handle",
"hide",
"if",
"impl",
"implements",
"import",
"in",
"inline",
"instanceof",
"int",
"protocol",
"is",
"let",
"library",
"long",
"loop",
"macro",
"map",
"match",
"mixin",
"mod",
"module",
"move",
"mut",
"mutable",
"namespace",
"native",
"new",
"not",
"not_eq",
"null",
"number",
"of",
"on",
"operator",
"or",
"or_eq",
"override",
"package",
"part",
"priv",
"private",
"protected",
"protocol",
"pub",
"public",
"range",
"ref",
"register",
"reinterpret_cast",
"request",
"require",
"reserved",
"rethrow",
"return",
"select",
"self",
"set",
"short",
"show",
"signed",
"sizeof",
"static",
"static_cast",
"strictfp",
"string",
"struct",
"super",
"switch",
"symbol",
"sync",
"synchronized",
"table",
"template",
"this",
"throw",
"throws",
"trait",
"transient",
"true",
"try",
"type",
"typedef",
"typeid",
"typename",
"typeof",
"union",
"unsafe",
"unsigned",
"unsized",
"use",
"using",
"var",
"vector",
"virtual",
"void",
"volatile",
"wchar_t",
"where",
"while",
"with",
"xor",
"xor_eq",
"yield",
};
for (auto word : checked_words) {
test.substitute("TEST", word);
ASSERT_FINDINGS(test);
}
}
TEST(LintFindingsTests, ServiceHubPatternIsDiscouragedPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning(?) Note this is a low-priority check.
LintTest test;
test.check_id("service-hub-pattern-is-discouraged")
.message("") // TBD
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, StringBoundsNotSpecified) {
LintTest test;
test.check_id("string-bounds-not-specified")
.message("Specify bounds for string")
.source_template(R"FIDL(
library fidl.a;
type SomeStruct = struct {
test_str ${TEST};
};
)FIDL");
test.substitute("TEST", "string:64");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "string:MAX");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "vector<string:64>:64");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "string");
ASSERT_FINDINGS(test);
test.source_template(R"FIDL(
library fidl.a;
const TEST_STRING ${TEST} = "A const str";
)FIDL");
test.substitute("TEST", "string");
ASSERT_NO_FINDINGS(test);
test.source_template(R"FIDL(
library fidl.a;
type SomeStruct = struct {
test_str vector<${TEST}>:64;
};
)FIDL");
test.substitute("TEST", "string:64");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "string:MAX");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "string");
ASSERT_FINDINGS(test);
test.that("developer cannot work around the check by aliasing").source_template(R"FIDL(
library fidl.a;
alias UnboundedStr = ${TEST};
type SomeStruct = struct {
test_string UnboundedStr;
};
)FIDL");
test.substitute("TEST", "string");
ASSERT_FINDINGS(test);
test.substitute("TEST", "string:64");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "string:MAX");
ASSERT_NO_FINDINGS(test);
test.that("'optional' constraint is not mistaken for size")
.source_template(R"FIDL(
library fidl.a;
type SomeStruct = struct {
test_str ${TEST};
};
)FIDL")
.substitute("TEST", "string:optional");
ASSERT_FINDINGS(test);
test.substitute("TEST", "string:<64, optional>");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "string:<MAX, optional>");
ASSERT_NO_FINDINGS(test);
}
TEST(LintFindingsTests, TodoShouldNotBeDocComment) {
// Warning on TODO comments.
std::string source_template = R"FIDL(
library fidl.a;
${TEST1} TODO: Finish the TestStruct declaration
type TestStruct = struct {
${TEST2}TODO: Replace the placeholder
placeholder string:64;${DOC_NOT_ALLOWED_HERE1} TODO(https://fxbug.dev/FIDL-0000): Add some more fields
};
)FIDL";
LintTest test;
test.check_id("todo-should-not-be-doc-comment")
.message("TODO comment should use a non-flow-through comment marker")
.source_template(source_template);
test.substitute({
{"TEST1", "//"},
{"TEST2", "//"},
{"DOC_NOT_ALLOWED_HERE1", "//"},
});
ASSERT_NO_FINDINGS(test);
test.substitute({
{"TEST1", "///"},
{"TEST2", "//"},
{"DOC_NOT_ALLOWED_HERE1", "//"},
})
.suggestion("change '///' to '//'")
.replacement("//")
.AddFinding("${TEST1}");
ASSERT_FINDINGS(test);
test.substitute({
{"TEST1", "//"},
{"TEST2", "///"},
{"DOC_NOT_ALLOWED_HERE1", "//"},
})
.AddFinding("${TEST2}");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
test.substitute({
{"TEST1", "///"},
{"TEST2", "///"},
{"DOC_NOT_ALLOWED_HERE1", "//"},
})
.AddFinding("${TEST1}")
.AddFinding("${TEST2}");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
// TEST1 and TEST2 as comments and DOC_NOT_ALLOWED_HERE1 as a doc comment leads to a parse error -
// see ParsingTests.BadTrailingDocCommentInDeclTest.
}
TEST(LintFindingsTests, TooManyNestedLibraries) {
LintTest test;
test.check_id("too-many-nested-libraries")
.message("Avoid library names with more than three dots")
.source_template(R"FIDL(
library ${TEST};
)FIDL");
test.substitute("TEST", "fidl.a");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "fuchsia.foo.bar.baz");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "fuchsia.foo.bar.baz.qux");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, UnexpectedTypeForWellKnownBufferConceptPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning on struct, union, and table member name patterns.
LintTest test;
test.check_id("unexpected-type-for-well-known-buffer-concept")
.message(
"Use fuchsia.mem.Buffer for images and (large) protobufs, when it makes sense to "
"buffer the data completely")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, UnexpectedTypeForWellKnownBytesConceptPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// (two suggestions) recommend either bytes or array<uint8>. warning on struct, union, and table
// member name patterns.
LintTest test;
test.check_id("unexpected-type-for-well-known-bytes-concept")
.message("Use bytes or array<uint8> for small non-text data:")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, UnexpectedTypeForWellKnownSocketHandleConceptPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning on struct, union, and table member name patterns.
LintTest test;
test.check_id("unexpected-type-for-well-known-socket-handle-concept")
.message(
"Use handle<socket> for audio and video streams because data may arrive over "
"time, or when it makes sense to process data before completely written or "
"available")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, UnexpectedTypeForWellKnownStringConceptPleaseImplementMe) {
if (true)
return; // disabled pending feature implementation
// Warning on struct, union, and table members that include certain well-known concepts (like
// "filename" and "file_name") but their types don't match the type recommended (e.g., string,
// in this case).
LintTest test;
test.check_id("unexpected-type-for-well-known-string-concept")
.message("Use string for text data:")
.source_template(R"FIDL(
library fidl.a;
PUT FIDL CONTENT HERE WITH PLACEHOLDERS LIKE:
${TEST}
TO SUBSTITUTE WITH GOOD_VALUE AND BAD_VALUE CASES.
)FIDL");
test.substitute("TEST", "!GOOD_VALUE!");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "!BAD_VALUE!")
.suggestion("change '!BAD_VALUE!' to '!GOOD_VALUE!'")
.replacement("!GOOD_VALUE!");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, VectorBoundsNotSpecified) {
LintTest test;
test.check_id("vector-bounds-not-specified")
.message("Specify bounds for vector")
.source_template(R"FIDL(
library fidl.a;
type SomeStruct = struct {
test_vector ${TEST};
};
)FIDL");
test.substitute("TEST", "vector<uint8>:64");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "vector<uint8>:MAX");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "vector<uint8>");
ASSERT_FINDINGS(test);
test.substitute("TEST", "vector<vector<uint8>:64>");
ASSERT_FINDINGS(test);
// Test nested vectors
test.source_template(R"FIDL(
library fidl.a;
type SomeStruct = struct {
test_vector vector<${TEST}>:64;
};
)FIDL");
test.substitute("TEST", "vector<uint8>:64");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "vector<uint8>:MAX");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "vector<uint8>");
ASSERT_FINDINGS(test);
test.that("developer cannot work around the check by indirect typing using an alias")
.source_template(R"FIDL(
library fidl.a;
// explanation for why we want this
alias UnboundedVector = ${TEST};
type SomeStruct = struct {
test_vector UnboundedVector;
};
)FIDL")
.substitute("TEST", "vector<uint8>");
ASSERT_FINDINGS(test);
test.substitute("TEST", "vector<uint8>:64");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "vector<uint8>:MAX");
ASSERT_NO_FINDINGS(test);
test.that("'optional' constraint is not mistaken for size")
.source_template(R"FIDL(
library fidl.a;
type SomeStruct = struct {
test_vector ${TEST};
};
)FIDL")
.substitute("TEST", "vector<uint8>:optional");
ASSERT_FINDINGS(test);
test.substitute("TEST", "vector<uint8>:<64, optional>");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "vector<uint8>:<MAX, optional>");
ASSERT_NO_FINDINGS(test);
}
TEST(LintFindingsTests, WrongPrefixForPlatformSourceLibrary) {
LintTest test;
test.check_id("wrong-prefix-for-platform-source-library")
.message("FIDL library name is not currently allowed")
.source_template(R"FIDL(
library ${TEST}.subcomponent;
)FIDL");
test.substitute("TEST", "fuchsia");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "fidl");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "test");
ASSERT_NO_FINDINGS(test);
test.substitute("TEST", "mylibs")
.suggestion("change 'mylibs' to fuchsia, perhaps?")
.replacement("fuchsia, perhaps?");
ASSERT_FINDINGS(test);
}
TEST(LintFindingsTests, IncludeAndExcludeChecks) {
LintTest test;
test.check_id("multiple checks").source_template(R"FIDL(
library ${LIBRARY};
type ${STRUCT_NAME} = struct {
${COMMENT_STYLE} TODO: Replace the placeholder
placeholder string:64;
};
)FIDL");
test.substitute({
{"LIBRARY", "fuchsia.foo.bar.baz.qux"},
{"COMMENT_STYLE", "///"},
{"STRUCT_NAME", "my_struct"},
})
.AddFinding("too-many-nested-libraries", "Avoid library names with more than three dots",
"${LIBRARY}")
.AddFinding("invalid-case-for-decl-name", "structs must be named in UpperCamelCase",
"${STRUCT_NAME}", "change 'my_struct' to 'MyStruct'", "MyStruct")
.AddFinding("todo-should-not-be-doc-comment",
"TODO comment should use a non-flow-through comment marker", "${COMMENT_STYLE}",
"change '///' to '//'", "//");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
test.exclude_checks({
"too-many-nested-libraries",
"invalid-case-for-decl-name",
"todo-should-not-be-doc-comment",
});
ASSERT_NO_FINDINGS(test);
test.exclude_by_default(true);
ASSERT_NO_FINDINGS(test);
test.exclude_by_default(true)
.include_checks({
"invalid-case-for-decl-name",
})
.AddFinding("invalid-case-for-decl-name", "structs must be named in UpperCamelCase",
"${STRUCT_NAME}", "change 'my_struct' to 'MyStruct'", "MyStruct");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
test.exclude_checks({
"invalid-case-for-decl-name",
"todo-should-not-be-doc-comment",
})
.include_checks({
"todo-should-not-be-doc-comment",
})
.AddFinding("too-many-nested-libraries", "Avoid library names with more than three dots",
"${LIBRARY}")
.AddFinding("todo-should-not-be-doc-comment",
"TODO comment should use a non-flow-through comment marker", "${COMMENT_STYLE}",
"change '///' to '//'", "//");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
test.exclude_checks({
"invalid-case-for-decl-name",
"todo-should-not-be-doc-comment",
})
.AddFinding("too-many-nested-libraries", "Avoid library names with more than three dots",
"${LIBRARY}");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
test.exclude_checks({
"invalid-case-for-decl-name",
"wrong-prefix-for-platform-source-library",
"todo-should-not-be-doc-comment",
"vector-bounds-not-specified",
})
.AddFinding("too-many-nested-libraries", "Avoid library names with more than three dots",
"${LIBRARY}");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
test.exclude_checks({
"invalid-case-for-decl-name",
"wrong-prefix-for-platform-source-library",
"todo-should-not-be-doc-comment",
"vector-bounds-not-specified",
})
.excluded_checks_to_confirm({
"invalid-case-for-decl-name",
"todo-should-not-be-doc-comment",
})
.AddFinding("too-many-nested-libraries", "Avoid library names with more than three dots",
"${LIBRARY}");
ASSERT_FINDINGS_IN_ANY_POSITION(test);
}
} // namespace
} // namespace fidlc