diff options
author | Devin Moore <devinmoore@google.com> | 2023-03-20 22:15:44 +0000 |
---|---|---|
committer | Devin Moore <devinmoore@google.com> | 2023-05-05 16:54:15 +0000 |
commit | 9ca4195d0f746ec2bc0be39e692105fefb864819 (patch) | |
tree | 659e0614b3f4beb2ea6b15194fb2e070a14a1184 | |
parent | 2c8aff3dc17f1b5a24ec0603214c808cd71e4aaf (diff) | |
download | aidl-9ca4195d0f746ec2bc0be39e692105fefb864819.tar.gz |
Add AIDL lint check for redundant prefixes
This checks constants and enumerators for this pattern.
It's a common pattern to see:
enum MyStatus {
STATUS_A,
STATUS_B,
};
Test: atest --host aidl_unittests
Bug: 268551304
Change-Id: I7645fbd27ea06efd2607844ed605cc426b9494a9
-rw-r--r-- | diagnostics.cpp | 93 | ||||
-rw-r--r-- | diagnostics.inc | 1 | ||||
-rw-r--r-- | diagnostics_unittest.cpp | 52 |
3 files changed, 142 insertions, 4 deletions
diff --git a/diagnostics.cpp b/diagnostics.cpp index d3f04b5b..5112173b 100644 --- a/diagnostics.cpp +++ b/diagnostics.cpp @@ -50,6 +50,12 @@ std::string to_string(DiagnosticID id) { return kDiagnosticsNames.at(id); } +static std::string ToUpper(const std::string& name) { + std::string nameCopy(name); + for (auto& c : nameCopy) c = std::toupper(c); + return nameCopy; +} + class DiagnosticsContext { public: DiagnosticsContext(DiagnosticMapping mapping) : mapping_({std::move(mapping)}) {} @@ -160,10 +166,6 @@ struct DiagnoseConstName : DiagnosticsVisitor { << "Constants should be named in upper case: " << c.GetName(); } } - static std::string ToUpper(std::string name) { - for (auto& c : name) c = std::toupper(c); - return name; - } }; struct DiagnoseExplicitDefault : DiagnosticsVisitor { @@ -336,6 +338,88 @@ struct DiagnosePermissionAnnotations : DiagnosticsVisitor { } }; +struct DiagnoseRedundantNames : DiagnosticsVisitor { + DiagnoseRedundantNames(DiagnosticsContext& diag) : DiagnosticsVisitor(diag) {} + + // tokenize the name with either capital letters or '_' being the delimiters + static std::vector<std::string> TokenizeName(const std::string& name) { + // if a name is all capitals with no '_', then we don't want to tokenize it. + if (std::all_of(name.begin(), name.end(), [](unsigned char c) { return isupper(c); })) { + return {name}; + } + // if a name has an `_` in it, it will be tokenized based on '_', + // otherwise based on capital letters + if (name.find('_') != std::string::npos) { + return base::Tokenize(name, "_"); + } + + std::vector<std::string> tokens; + size_t size = name.size(); + std::string tmp{name.front()}; + // Skip the first character to avoid an empty substring for common cases + for (size_t i = 1; i < size; i++) { + if (std::isupper(name[i])) { + tokens.push_back(tmp); + tmp.clear(); + // This uppercase letter belongs to the next token + tmp += name[i]; + } else { + tmp += name[i]; + } + } + + if (!tmp.empty()) tokens.push_back(tmp); + + return tokens; + } + + void Visit(const AidlEnumDeclaration& e) override { + const std::vector<std::string> parent = TokenizeName(e.GetName()); + for (const auto& enumerator : e.GetEnumerators()) { + const std::vector<std::string> child = TokenizeName(enumerator->GetName()); + for (const auto& parentSubStr : parent) { + for (const auto& childSubStr : child) { + if (ToUpper(parentSubStr) == ToUpper(childSubStr)) { + diag.Report(e.GetLocation(), DiagnosticID::redundant_name) + << "The enumerator '" << enumerator->GetName() << "' has a redundant substring '" + << childSubStr << "' being defined in '" << e.GetName() << "'"; + } + } + } + } + } + + void CheckConstantDeclarations( + const std::string& name, + const std::vector<std::unique_ptr<AidlConstantDeclaration>>& consts) { + const std::vector<std::string> parent = TokenizeName(name); + for (const auto& member : consts) { + const std::vector<std::string> child = TokenizeName(member->GetName()); + for (const auto& parentSubStr : parent) { + for (const auto& childSubStr : child) { + if (ToUpper(parentSubStr) == ToUpper(childSubStr)) { + diag.Report(member->GetLocation(), DiagnosticID::redundant_name) + << "The constant '" << member->GetName() << "' has a redundant substring '" + << childSubStr << "' being defined in '" << name << "'"; + } + } + } + } + } + + void Visit(const AidlInterface& t) override { + CheckConstantDeclarations(t.GetName(), t.GetConstantDeclarations()); + } + + void Visit(const AidlUnionDecl& t) override { + CheckConstantDeclarations(t.GetName(), t.GetConstantDeclarations()); + } + + void Visit(const AidlStructuredParcelable& t) override { + CheckConstantDeclarations(t.GetName(), t.GetConstantDeclarations()); + } +}; + bool Diagnose(const AidlDocument& doc, const DiagnosticMapping& mapping) { DiagnosticsContext diag(mapping); @@ -350,6 +434,7 @@ bool Diagnose(const AidlDocument& doc, const DiagnosticMapping& mapping) { DiagnoseImports{diag}.Check(doc); DiagnoseUntypedCollection{diag}.Check(doc); DiagnosePermissionAnnotations{diag}.Check(doc); + DiagnoseRedundantNames{diag}.Check(doc); return diag.ErrorCount() == 0; } diff --git a/diagnostics.inc b/diagnostics.inc index 6a0570ae..85b52033 100644 --- a/diagnostics.inc +++ b/diagnostics.inc @@ -11,3 +11,4 @@ DIAG(out_nullable, "out-nullable", false) DIAG(unique_import, "unique-import", false) DIAG(unknown_warning, "unknown-warning", false) DIAG(untyped_collection, "untyped-collection", false) +DIAG(redundant_name, "redundant-name", false) diff --git a/diagnostics_unittest.cpp b/diagnostics_unittest.cpp index 3a45d09c..dcbb7011 100644 --- a/diagnostics_unittest.cpp +++ b/diagnostics_unittest.cpp @@ -281,3 +281,55 @@ TEST_F(DiagnosticsTest, PermissionInterface) { expect_diagnostic = {}; ParseFiles({{"IFoo.aidl", "@EnforcePermission(\"INTERNET\") interface IFoo { void food(); }"}}); } + +TEST_F(DiagnosticsTest, RedundantPrefixConstantInterface) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles({{"SomethingStatus.aidl", "interface SomethingStatus { const int STATUS_ONE = 1; }"}}); +} + +TEST_F(DiagnosticsTest, RedundantPrefixConstantParcelable) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles( + {{"SomethingStatus.aidl", "parcelable SomethingStatus { const int STATUS_ONE = 1; }"}}); +} + +TEST_F(DiagnosticsTest, RedundantSuffixConstantParcelable) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles( + {{"SomethingStatus.aidl", "parcelable SomethingStatus { const int ONE_STATUS = 1; }"}}); +} + +TEST_F(DiagnosticsTest, RedundantSuffixConstantParcelable2) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles( + {{"SomethingStatus.aidl", "parcelable SomethingStatus { const int ONE_SOMETHING = 1; }"}}); +} + +TEST_F(DiagnosticsTest, RedundantConstantUnion) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles({{"SomethingStatus.aidl", + "union SomethingStatus { const int ONE_SOMETHING = 1; int a; int b;}"}}); +} + +TEST_F(DiagnosticsTest, RedundantPrefixEnum) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles({{"SomethingStatus.aidl", "enum SomethingStatus { STATUS_ONE = 1, }"}}); +} + +TEST_F(DiagnosticsTest, RedundantSuffixEnum) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles({{"SomethingStatus.aidl", "enum SomethingStatus { ONE_STATUS = 1, }"}}); +} + +TEST_F(DiagnosticsTest, RedundantSuffixEnum2) { + enable_diagnostic = DiagnosticID::redundant_name; + expect_diagnostic = DiagnosticID::redundant_name; + ParseFiles({{"SomethingStatus.aidl", "enum SomethingStatus { ONE_SOMETHING = 1, }"}}); +} |