Skip to content

Commit 18b3192

Browse files
committed
tools: add StringClass and WideStringClass support to UseIsEmptyCheck
1 parent e93edd2 commit 18b3192

File tree

4 files changed

+262
-55
lines changed

4 files changed

+262
-55
lines changed

.clang-tidy

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# TheSuperHackers @build JohnsterID 15/09/2025 Add clang-tidy configuration for code quality analysis
2+
---
3+
# Clang-tidy configuration for GeneralsGameCode project
4+
# This configuration is tailored for a legacy C++98/C++20 hybrid codebase
5+
# with Windows-specific code and COM interfaces
6+
7+
# Enable specific checks that are appropriate for this codebase
8+
Checks: >
9+
-*,
10+
bugprone-*,
11+
-bugprone-easily-swappable-parameters,
12+
-bugprone-implicit-widening-of-multiplication-result,
13+
-bugprone-narrowing-conversions,
14+
-bugprone-signed-char-misuse,
15+
cert-*,
16+
-cert-dcl21-cpp,
17+
-cert-dcl50-cpp,
18+
-cert-dcl58-cpp,
19+
-cert-env33-c,
20+
-cert-err58-cpp,
21+
clang-analyzer-*,
22+
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
23+
cppcoreguidelines-*,
24+
-cppcoreguidelines-avoid-c-arrays,
25+
-cppcoreguidelines-avoid-magic-numbers,
26+
-cppcoreguidelines-avoid-non-const-global-variables,
27+
-cppcoreguidelines-init-variables,
28+
-cppcoreguidelines-macro-usage,
29+
-cppcoreguidelines-no-malloc,
30+
-cppcoreguidelines-owning-memory,
31+
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
32+
-cppcoreguidelines-pro-bounds-constant-array-index,
33+
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
34+
-cppcoreguidelines-pro-type-cstyle-cast,
35+
-cppcoreguidelines-pro-type-reinterpret-cast,
36+
-cppcoreguidelines-pro-type-union-access,
37+
-cppcoreguidelines-pro-type-vararg,
38+
-cppcoreguidelines-special-member-functions,
39+
google-*,
40+
-google-build-using-namespace,
41+
-google-explicit-constructor,
42+
-google-readability-casting,
43+
-google-readability-todo,
44+
-google-runtime-int,
45+
-google-runtime-references,
46+
hicpp-*,
47+
-hicpp-avoid-c-arrays,
48+
-hicpp-explicit-conversions,
49+
-hicpp-no-array-decay,
50+
-hicpp-signed-bitwise,
51+
-hicpp-special-member-functions,
52+
-hicpp-uppercase-literal-suffix,
53+
-hicpp-use-auto,
54+
-hicpp-vararg,
55+
misc-*,
56+
-misc-const-correctness,
57+
-misc-include-cleaner,
58+
-misc-non-private-member-variables-in-classes,
59+
-misc-use-anonymous-namespace,
60+
modernize-*,
61+
-modernize-avoid-c-arrays,
62+
-modernize-concat-nested-namespaces,
63+
-modernize-loop-convert,
64+
-modernize-pass-by-value,
65+
-modernize-raw-string-literal,
66+
-modernize-return-braced-init-list,
67+
-modernize-use-auto,
68+
-modernize-use-default-member-init,
69+
-modernize-use-nodiscard,
70+
-modernize-use-trailing-return-type,
71+
performance-*,
72+
-performance-avoid-endl,
73+
portability-*,
74+
readability-*,
75+
-readability-avoid-const-params-in-decls,
76+
-readability-braces-around-statements,
77+
-readability-convert-member-functions-to-static,
78+
-readability-function-cognitive-complexity,
79+
-readability-identifier-length,
80+
-readability-implicit-bool-conversion,
81+
-readability-isolate-declaration,
82+
-readability-magic-numbers,
83+
-readability-named-parameter,
84+
-readability-redundant-access-specifiers,
85+
-readability-uppercase-literal-suffix
86+
87+
# Treat warnings as errors for CI/CD
88+
WarningsAsErrors: false
89+
90+
# Header filter to include project headers
91+
HeaderFilterRegex: '(Core|Generals|GeneralsMD|Dependencies)/.*\.(h|hpp)$'
92+
93+
# Check options for specific rules
94+
CheckOptions:
95+
# Naming conventions - adapted for the existing codebase style
96+
- key: readability-identifier-naming.ClassCase
97+
value: CamelCase
98+
- key: readability-identifier-naming.StructCase
99+
value: CamelCase
100+
- key: readability-identifier-naming.FunctionCase
101+
value: CamelCase
102+
- key: readability-identifier-naming.MethodCase
103+
value: CamelCase
104+
- key: readability-identifier-naming.VariableCase
105+
value: lower_case
106+
- key: readability-identifier-naming.ParameterCase
107+
value: lower_case
108+
- key: readability-identifier-naming.MemberCase
109+
value: lower_case
110+
- key: readability-identifier-naming.MemberPrefix
111+
value: m_
112+
- key: readability-identifier-naming.ConstantCase
113+
value: UPPER_CASE
114+
- key: readability-identifier-naming.EnumConstantCase
115+
value: UPPER_CASE
116+
- key: readability-identifier-naming.MacroDefinitionCase
117+
value: UPPER_CASE
118+
119+
# Performance settings
120+
- key: performance-for-range-copy.WarnOnAllAutoCopies
121+
value: true
122+
- key: performance-unnecessary-value-param.AllowedTypes
123+
value: 'AsciiString;UnicodeString;Utf8String;Utf16String'
124+
125+
# Modernize settings - be conservative for legacy code
126+
- key: modernize-use-nullptr.NullMacros
127+
value: 'NULL'
128+
- key: modernize-replace-auto-ptr.IncludeStyle
129+
value: llvm
130+
131+
# Readability settings
132+
- key: readability-function-size.LineThreshold
133+
value: 150
134+
- key: readability-function-size.StatementThreshold
135+
value: 100
136+
- key: readability-function-size.BranchThreshold
137+
value: 25
138+
- key: readability-function-size.ParameterThreshold
139+
value: 8
140+
- key: readability-function-size.NestingThreshold
141+
value: 6
142+
143+
# Bugprone settings
144+
- key: bugprone-argument-comment.StrictMode
145+
value: false
146+
- key: bugprone-suspicious-string-compare.WarnOnImplicitComparison
147+
value: true
148+
- key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison
149+
value: true
150+
151+
# Google style settings
152+
- key: google-readability-braces-around-statements.ShortStatementLines
153+
value: 2
154+
- key: google-readability-function-size.StatementThreshold
155+
value: 100
156+
157+
# CERT settings
158+
- key: cert-dcl16-c.NewSuffixes
159+
value: 'L;LL;LU;LLU'
160+
- key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
161+
value: false
162+
163+
# Use .clang-format for formatting suggestions
164+
FormatStyle: file
165+
166+
# Exclude certain directories and files
167+
# Note: This is handled by HeaderFilterRegex above, but can be extended

tools/clang-tidy-plugin/README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,26 @@ This is a custom clang-tidy plugin that provides checks specific to the Generals
66

77
### `generals-use-is-empty`
88

9-
Finds uses of `getLength() == 0` or `getLength() > 0` on `AsciiString` and `UnicodeString` and suggests using `isEmpty()` or `!isEmpty()` instead.
9+
Finds uses of `getLength() == 0` or `getLength() > 0` on `AsciiString` and `UnicodeString`, and `Get_Length() == 0` on `StringClass` and `WideStringClass`, and suggests using `isEmpty()`/`Is_Empty()` or `!isEmpty()`/`!Is_Empty()` instead.
1010

1111
**Examples:**
1212

1313
```cpp
14-
// Before
14+
// Before (AsciiString/UnicodeString)
1515
if (str.getLength() == 0) { ... }
1616
if (str.getLength() > 0) { ... }
1717

18-
// After
18+
// After (AsciiString/UnicodeString)
1919
if (str.isEmpty()) { ... }
2020
if (!str.isEmpty()) { ... }
21+
22+
// Before (StringClass/WideStringClass)
23+
if (str.Get_Length() == 0) { ... }
24+
if (str.Get_Length() > 0) { ... }
25+
26+
// After (StringClass/WideStringClass)
27+
if (str.Is_Empty()) { ... }
28+
if (!str.Is_Empty()) { ... }
2129
```
2230
2331
## Building

tools/clang-tidy-plugin/readability/UseIsEmptyCheck.cpp

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
// - AsciiString::getLength() > 0 -> !AsciiString::isEmpty()
66
// - UnicodeString::getLength() == 0 -> UnicodeString::isEmpty()
77
// - UnicodeString::getLength() > 0 -> !UnicodeString::isEmpty()
8+
// - StringClass::Get_Length() == 0 -> StringClass::Is_Empty()
9+
// - WideStringClass::Get_Length() == 0 -> WideStringClass::Is_Empty()
810
//
911
//===----------------------------------------------------------------------===//
1012

@@ -19,59 +21,73 @@ using namespace clang::ast_matchers;
1921
namespace clang::tidy::generalsgamecode::readability {
2022

2123
void UseIsEmptyCheck::registerMatchers(MatchFinder *Finder) {
24+
// Matcher for AsciiString/UnicodeString with getLength()
2225
auto GetLengthCall = cxxMemberCallExpr(
2326
callee(cxxMethodDecl(hasName("getLength"))),
2427
on(hasType(hasUnqualifiedDesugaredType(
2528
recordType(hasDeclaration(cxxRecordDecl(
2629
hasAnyName("AsciiString", "UnicodeString"))))))));
2730

28-
Finder->addMatcher(
29-
binaryOperator(
30-
hasOperatorName("=="),
31-
hasLHS(ignoringParenImpCasts(GetLengthCall.bind("getLengthCall"))),
32-
hasRHS(integerLiteral(equals(0)).bind("zero")))
33-
.bind("comparison"),
34-
this);
35-
36-
Finder->addMatcher(
37-
binaryOperator(
38-
hasOperatorName("!="),
39-
hasLHS(ignoringParenImpCasts(GetLengthCall.bind("getLengthCall"))),
40-
hasRHS(integerLiteral(equals(0)).bind("zero")))
41-
.bind("comparison"),
42-
this);
43-
44-
Finder->addMatcher(
45-
binaryOperator(
46-
hasOperatorName(">"),
47-
hasLHS(ignoringParenImpCasts(GetLengthCall.bind("getLengthCall"))),
48-
hasRHS(integerLiteral(equals(0)).bind("zero")))
49-
.bind("comparison"),
50-
this);
51-
52-
Finder->addMatcher(
53-
binaryOperator(
54-
hasOperatorName("<="),
55-
hasLHS(ignoringParenImpCasts(GetLengthCall.bind("getLengthCall"))),
56-
hasRHS(integerLiteral(equals(0)).bind("zero")))
57-
.bind("comparison"),
58-
this);
59-
60-
Finder->addMatcher(
61-
binaryOperator(
62-
hasOperatorName("=="),
63-
hasLHS(integerLiteral(equals(0)).bind("zero")),
64-
hasRHS(ignoringParenImpCasts(GetLengthCall.bind("getLengthCall"))))
65-
.bind("comparison"),
66-
this);
67-
68-
Finder->addMatcher(
69-
binaryOperator(
70-
hasOperatorName("!="),
71-
hasLHS(integerLiteral(equals(0)).bind("zero")),
72-
hasRHS(ignoringParenImpCasts(GetLengthCall.bind("getLengthCall"))))
73-
.bind("comparison"),
74-
this);
31+
// Matcher for StringClass/WideStringClass with Get_Length()
32+
auto GetLengthCallWWVegas = cxxMemberCallExpr(
33+
callee(cxxMethodDecl(hasName("Get_Length"))),
34+
on(hasType(hasUnqualifiedDesugaredType(
35+
recordType(hasDeclaration(cxxRecordDecl(
36+
hasAnyName("StringClass", "WideStringClass"))))))));
37+
38+
// Helper function to add matchers for a given GetLength call matcher
39+
auto addMatchersForGetLength = [&](const auto &GetLengthMatcher) {
40+
Finder->addMatcher(
41+
binaryOperator(
42+
hasOperatorName("=="),
43+
hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))),
44+
hasRHS(integerLiteral(equals(0)).bind("zero")))
45+
.bind("comparison"),
46+
this);
47+
48+
Finder->addMatcher(
49+
binaryOperator(
50+
hasOperatorName("!="),
51+
hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))),
52+
hasRHS(integerLiteral(equals(0)).bind("zero")))
53+
.bind("comparison"),
54+
this);
55+
56+
Finder->addMatcher(
57+
binaryOperator(
58+
hasOperatorName(">"),
59+
hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))),
60+
hasRHS(integerLiteral(equals(0)).bind("zero")))
61+
.bind("comparison"),
62+
this);
63+
64+
Finder->addMatcher(
65+
binaryOperator(
66+
hasOperatorName("<="),
67+
hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))),
68+
hasRHS(integerLiteral(equals(0)).bind("zero")))
69+
.bind("comparison"),
70+
this);
71+
72+
Finder->addMatcher(
73+
binaryOperator(
74+
hasOperatorName("=="),
75+
hasLHS(integerLiteral(equals(0)).bind("zero")),
76+
hasRHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))))
77+
.bind("comparison"),
78+
this);
79+
80+
Finder->addMatcher(
81+
binaryOperator(
82+
hasOperatorName("!="),
83+
hasLHS(integerLiteral(equals(0)).bind("zero")),
84+
hasRHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))))
85+
.bind("comparison"),
86+
this);
87+
};
88+
89+
addMatchersForGetLength(GetLengthCall);
90+
addMatchersForGetLength(GetLengthCallWWVegas);
7591
}
7692

7793
void UseIsEmptyCheck::check(const MatchFinder::MatchResult &Result) {
@@ -86,6 +102,19 @@ void UseIsEmptyCheck::check(const MatchFinder::MatchResult &Result) {
86102
if (!ObjectExpr)
87103
return;
88104

105+
// Determine which method name to use based on the called method
106+
StringRef GetLengthMethodName = GetLengthCall->getMethodDecl()->getName();
107+
std::string IsEmptyMethodName;
108+
std::string GetLengthMethodNameStr;
109+
110+
if (GetLengthMethodName == "Get_Length") {
111+
IsEmptyMethodName = "Is_Empty()";
112+
GetLengthMethodNameStr = "Get_Length()";
113+
} else {
114+
IsEmptyMethodName = "isEmpty()";
115+
GetLengthMethodNameStr = "getLength()";
116+
}
117+
89118
StringRef Operator = Comparison->getOpcodeStr();
90119
bool ShouldNegate = false;
91120

@@ -107,17 +136,17 @@ void UseIsEmptyCheck::check(const MatchFinder::MatchResult &Result) {
107136

108137
std::string Replacement;
109138
if (ShouldNegate) {
110-
Replacement = "!" + ObjectText.str() + ".isEmpty()";
139+
Replacement = "!" + ObjectText.str() + "." + IsEmptyMethodName;
111140
} else {
112-
Replacement = ObjectText.str() + ".isEmpty()";
141+
Replacement = ObjectText.str() + "." + IsEmptyMethodName;
113142
}
114143

115144
SourceLocation StartLoc = Comparison->getBeginLoc();
116145
SourceLocation EndLoc = Comparison->getEndLoc();
117146

118147
diag(Comparison->getBeginLoc(),
119-
"use %0 instead of comparing getLength() with 0")
120-
<< Replacement
148+
"use %0 instead of comparing %1 with 0")
149+
<< Replacement << GetLengthMethodNameStr
121150
<< FixItHint::CreateReplacement(
122151
CharSourceRange::getTokenRange(StartLoc, EndLoc), Replacement);
123152
}

tools/clang-tidy-plugin/readability/UseIsEmptyCheck.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
// - AsciiString::getLength() > 0 -> !AsciiString::isEmpty()
66
// - UnicodeString::getLength() == 0 -> UnicodeString::isEmpty()
77
// - UnicodeString::getLength() > 0 -> !UnicodeString::isEmpty()
8+
// - StringClass::Get_Length() == 0 -> StringClass::Is_Empty()
9+
// - WideStringClass::Get_Length() == 0 -> WideStringClass::Is_Empty()
810
//
911
//===----------------------------------------------------------------------===//
1012

@@ -16,7 +18,8 @@
1618
namespace clang::tidy::generalsgamecode::readability {
1719

1820
/// Finds uses of getLength() == 0 or getLength() > 0 on AsciiString and
19-
/// UnicodeString and suggests using isEmpty() or !isEmpty() instead.
21+
/// UnicodeString, and Get_Length() == 0 on StringClass and WideStringClass,
22+
/// and suggests using isEmpty()/Is_Empty() or !isEmpty()/!Is_Empty() instead.
2023
///
2124
/// For the user-facing documentation see:
2225
/// http://clang.llvm.org/extra/clang-tidy/checks/generals-use-is-empty.html

0 commit comments

Comments
 (0)