xref: /llvm-project/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp (revision 2f02b5af6ecb973d3a7faad9b0daff22646e724d)
1 // RUN: %check_clang_tidy -std=c++20 %s modernize-use-starts-ends-with %t -- \
2 // RUN:   -- -isystem %clang_tidy_headers
3 
4 #include <string.h>
5 #include <string>
6 
7 std::string foo(std::string);
8 std::string bar();
9 
10 class sub_string : public std::string {};
11 class sub_sub_string : public sub_string {};
12 
13 struct string_like {
14   bool starts_with(const char *s) const;
15   size_t find(const char *s, size_t pos = 0) const;
16 };
17 
18 struct string_like_camel {
19   bool startsWith(const char *s) const;
20   size_t find(const char *s, size_t pos = 0) const;
21 };
22 
23 struct prefer_underscore_version {
24   bool starts_with(const char *s) const;
25   bool startsWith(const char *s) const;
26   size_t find(const char *s, size_t pos = 0) const;
27 };
28 
29 struct prefer_underscore_version_flip {
30   bool startsWith(const char *s) const;
31   bool starts_with(const char *s) const;
32   size_t find(const char *s, size_t pos = 0) const;
33 };
34 
35 void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
36           string_like sl, string_like_camel slc, prefer_underscore_version puv,
37           prefer_underscore_version_flip puvf) {
38   s.find("a") == 0;
39   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find [modernize-use-starts-ends-with]
40   // CHECK-FIXES: s.starts_with("a");
41 
42   (((((s)).find("a")))) == ((0));
43   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
44   // CHECK-FIXES: ((s)).starts_with("a");
45 
46   (s + "a").find("a") == ((0));
47   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
48   // CHECK-FIXES: (s + "a").starts_with("a");
49 
50   s.find(s) == 0;
51   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
52   // CHECK-FIXES: s.starts_with(s);
53 
54   s.find("aaa") != 0;
55   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
56   // CHECK-FIXES: !s.starts_with("aaa");
57 
58   s.find(foo(foo(bar()))) != 0;
59   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
60   // CHECK-FIXES: !s.starts_with(foo(foo(bar())));
61 
62   if (s.find("....") == 0) { /* do something */ }
63   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
64   // CHECK-FIXES: if (s.starts_with("...."))
65 
66   0 != s.find("a");
67   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
68   // CHECK-FIXES: !s.starts_with("a");
69 
70   s.rfind("a", 0) == 0;
71   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind [modernize-use-starts-ends-with]
72   // CHECK-FIXES: s.starts_with("a");
73 
74   s.rfind(s, 0) == 0;
75   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
76   // CHECK-FIXES: s.starts_with(s);
77 
78   s.rfind("aaa", 0) != 0;
79   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
80   // CHECK-FIXES: !s.starts_with("aaa");
81 
82   s.rfind(foo(foo(bar())), 0) != 0;
83   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
84   // CHECK-FIXES: !s.starts_with(foo(foo(bar())));
85 
86   if (s.rfind("....", 0) == 0) { /* do something */ }
87   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
88   // CHECK-FIXES: if (s.starts_with("...."))
89 
90   0 != s.rfind("a", 0);
91   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
92   // CHECK-FIXES: !s.starts_with("a");
93 
94   #define FIND find
95   s.FIND("a") == 0;
96   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
97   // CHECK-FIXES: s.starts_with("a")
98 
99   #define PREFIX "a"
100   s.find(PREFIX) == 0;
101   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
102   // CHECK-FIXES: s.starts_with(PREFIX)
103 
104   #define ZERO 0
105   s.find("a") == ZERO;
106   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
107   // CHECK-FIXES: s.starts_with("a")
108 
109   sv.find("a") == 0;
110   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
111   // CHECK-FIXES: sv.starts_with("a");
112 
113   sv.rfind("a", 0) != 0;
114   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
115   // CHECK-FIXES: !sv.starts_with("a");
116 
117   ss.find("a") == 0;
118   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
119   // CHECK-FIXES: ss.starts_with("a");
120 
121   sss.find("a") == 0;
122   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
123   // CHECK-FIXES: ss.starts_with("a");
124 
125   sl.find("a") == 0;
126   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
127   // CHECK-FIXES: sl.starts_with("a");
128 
129   slc.find("a") == 0;
130   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use startsWith
131   // CHECK-FIXES: slc.startsWith("a");
132 
133   puv.find("a") == 0;
134   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
135   // CHECK-FIXES: puv.starts_with("a");
136 
137   puvf.find("a") == 0;
138   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
139   // CHECK-FIXES: puvf.starts_with("a");
140 
141   s.compare(0, 1, "a") == 0;
142   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare [modernize-use-starts-ends-with]
143   // CHECK-FIXES: s.starts_with("a");
144 
145   s.compare(0, 1, "a") != 0;
146   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare [modernize-use-starts-ends-with]
147   // CHECK-FIXES: !s.starts_with("a");
148 
149   s.compare(0, strlen("a"), "a") == 0;
150   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
151   // CHECK-FIXES: s.starts_with("a");
152 
153   s.compare(0, std::strlen("a"), "a") == 0;
154   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
155   // CHECK-FIXES: s.starts_with("a");
156 
157   s.compare(0, std::strlen(("a")), "a") == 0;
158   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
159   // CHECK-FIXES: s.starts_with("a");
160 
161   s.compare(0, std::strlen(("a")), (("a"))) == 0;
162   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
163   // CHECK-FIXES: s.starts_with("a");
164 
165   s.compare(0, s.size(), s) == 0;
166   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
167   // CHECK-FIXES: s.starts_with(s);
168 
169   s.compare(0, s.length(), s) == 0;
170   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
171   // CHECK-FIXES: s.starts_with(s);
172 
173   0 != s.compare(0, sv.length(), sv);
174   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
175   // CHECK-FIXES: s.starts_with(sv);
176 
177   #define LENGTH(x) (x).length()
178   s.compare(0, LENGTH(s), s) == 0;
179   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
180   // CHECK-FIXES: s.starts_with(s);
181 
182   s.compare(ZERO, LENGTH(s), s) == ZERO;
183   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
184   // CHECK-FIXES: s.starts_with(s);
185 
186   s.compare(ZERO, LENGTH(sv), sv) != 0;
187   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
188   // CHECK-FIXES: !s.starts_with(sv);
189 
190   s.compare(s.size() - 6, 6, "suffix") == 0;
191   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
192   // CHECK-FIXES: s.ends_with("suffix");
193 
194   s.compare(s.size() - 6, strlen("abcdef"), "suffix") == 0;
195   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
196   // CHECK-FIXES: s.ends_with("suffix");
197 
198   std::string suffix = "suffix";
199   s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
200   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
201   // CHECK-FIXES: s.ends_with(suffix);
202 
203   s.rfind("suffix") == s.size() - 6;
204   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
205   // CHECK-FIXES: s.ends_with("suffix");
206 
207   s.rfind("suffix") == s.size() - strlen("suffix");
208   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
209   // CHECK-FIXES: s.ends_with("suffix");
210 
211   s.rfind(suffix) == s.size() - suffix.size();
212   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
213   // CHECK-FIXES: s.ends_with(suffix);
214 
215   s.rfind(suffix, std::string::npos) == s.size() - suffix.size();
216   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
217   // CHECK-FIXES: s.ends_with(suffix);
218 
219   s.rfind(suffix) == (s.size() - suffix.size());
220   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
221   // CHECK-FIXES: s.ends_with(suffix);
222 
223   s.rfind(suffix, s.npos) == (s.size() - suffix.size());
224   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
225   // CHECK-FIXES: s.ends_with(suffix);
226 
227   s.rfind(suffix, s.npos) == (((s.size()) - (suffix.size())));
228   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
229   // CHECK-FIXES: s.ends_with(suffix);
230 
231   s.rfind(suffix) != s.size() - suffix.size();
232   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
233   // CHECK-FIXES: !s.ends_with(suffix);
234 
235   (s.size() - suffix.size()) == s.rfind(suffix);
236   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
237   // CHECK-FIXES: s.ends_with(suffix);
238 
239   struct S {
240     std::string s;
241   } t;
242   t.s.rfind(suffix) == (t.s.size() - suffix.size());
243   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
244   // CHECK-FIXES: t.s.ends_with(suffix);
245 
246   // Expressions that don't trigger the check are here.
247   #define EQ(x, y) ((x) == (y))
248   EQ(s.find("a"), 0);
249 
250   #define DOTFIND(x, y) (x).find(y)
251   DOTFIND(s, "a") == 0;
252 
253   #define STARTS_WITH_COMPARE(x, y) (x).compare(0, (x).size(), (y))
254   STARTS_WITH_COMPARE(s, s) == 0;
255 
256   s.compare(0, 1, "ab") == 0;
257   s.rfind(suffix, 1) == s.size() - suffix.size();
258 
259   #define STR(x) std::string(x)
260   0 == STR(s).find("a");
261 
262   #define STRING s
263   if (0 == STRING.find("ala")) { /* do something */}
264 }
265 
266 void test_substr() {
267     std::string str("hello world");
268     std::string prefix = "hello";
269 
270     // Basic pattern
271     str.substr(0, 5) == "hello";
272     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr [modernize-use-starts-ends-with]
273     // CHECK-FIXES: str.starts_with("hello");
274 
275     // With string literal on left side
276     "hello" == str.substr(0, 5);
277     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr [modernize-use-starts-ends-with]
278     // CHECK-FIXES: str.starts_with("hello");
279 
280     // Inequality comparison
281     str.substr(0, 5) != "world";
282     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr [modernize-use-starts-ends-with]
283     // CHECK-FIXES: !str.starts_with("world");
284 
285     // Ensure non-zero start position is not transformed
286     str.substr(1, 5) == "hello";
287     str.substr(0, 4) == "hello"; // Length mismatch
288 
289     size_t len = 5;
290     str.substr(0, len) == "hello"; // Non-constant length
291 
292     // String literal with size calculation
293     str.substr(0, strlen("hello")) == "hello";
294     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr [modernize-use-starts-ends-with]
295     // CHECK-FIXES: str.starts_with("hello");
296 
297     str.substr(0, prefix.size()) == prefix;
298     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr [modernize-use-starts-ends-with]
299     // CHECK-FIXES: str.starts_with(prefix);
300 
301     str.substr(0, prefix.length()) == prefix;
302     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr [modernize-use-starts-ends-with]
303     // CHECK-FIXES: str.starts_with(prefix);
304 
305     // Tests to verify macro behavior
306     #define MSG "hello"
307     str.substr(0, strlen(MSG)) == MSG;
308     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr [modernize-use-starts-ends-with]
309     // CHECK-FIXES: str.starts_with(MSG);
310 
311     #define STARTS_WITH(X, Y) (X).substr(0, (Y).size()) == (Y)
312     STARTS_WITH(str, prefix);
313 
314     #define SUBSTR(X, A, B) (X).substr((A), (B))
315     SUBSTR(str, 0, 6) == "prefix";
316 
317     #define STR() str
318     SUBSTR(STR(), 0, 6) == "prefix";
319     "prefix" == SUBSTR(STR(), 0, 6);
320 
321     str.substr(0, strlen("hello123")) == "hello";
322 }
323 
324 void test_operator_rewriting(std::string str, std::string prefix) {
325   str.substr(0, prefix.size()) == prefix;
326   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr
327   // CHECK-FIXES: str.starts_with(prefix);
328 
329   str.substr(0, prefix.size()) != prefix;
330   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr
331   // CHECK-FIXES: !str.starts_with(prefix);
332 }
333