[clang-tools-extra] [clang-tidy] Add readability-string-view-substr check (PR #120055)
Helmut Januschka via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 18 01:04:33 PST 2026
https://github.com/hjanuschka updated https://github.com/llvm/llvm-project/pull/120055
>From 58fb255883435b90aa40ad5662b39997f6f7e06c Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Wed, 18 Feb 2026 10:04:16 +0100
Subject: [PATCH] [clang-tidy] Add readability-stringview-substr check
Add a new check that suggests using string_view::remove_prefix() and
remove_suffix() instead of substr() when the intent is to remove
characters from either end of a string_view.
Transformations:
sv = sv.substr(n) -> sv.remove_prefix(n)
sv = sv.substr(0, sv.length() - n) -> sv.remove_suffix(n)
sv = sv.substr(0, sv.length()) -> redundant self-copy (removed)
sv1 = sv.substr(0, sv.length()) -> sv1 = sv
sv1 = sv2.substr(0, sv2.length()-n) -> sv1 = sv2; sv1.remove_suffix(n)
These replacements make the intent clearer and are more efficient as they
modify the string_view in place rather than creating a new one.
---
.../clang-tidy/readability/CMakeLists.txt | 1 +
.../readability/ReadabilityTidyModule.cpp | 3 +
.../readability/StringViewSubstrCheck.cpp | 191 ++++++++++++++++++
.../readability/StringViewSubstrCheck.h | 44 ++++
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../checks/readability/stringview-substr.rst | 19 ++
.../readability/stringview_substr.cpp | 155 ++++++++++++++
8 files changed, 420 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/readability/stringview-substr.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/stringview_substr.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
index f1f3cde32feff..5b7ce0baa0e83 100644
--- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -57,6 +57,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
StaticAccessedThroughInstanceCheck.cpp
StaticDefinitionInAnonymousNamespaceCheck.cpp
StringCompareCheck.cpp
+ StringViewSubstrCheck.cpp
SuspiciousCallArgumentCheck.cpp
TrailingCommaCheck.cpp
UniqueptrDeleteReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
index c582dc98eac6b..1c2d17d584029 100644
--- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -59,6 +59,7 @@
#include "StaticAccessedThroughInstanceCheck.h"
#include "StaticDefinitionInAnonymousNamespaceCheck.h"
#include "StringCompareCheck.h"
+#include "StringViewSubstrCheck.h"
#include "SuspiciousCallArgumentCheck.h"
#include "TrailingCommaCheck.h"
#include "UniqueptrDeleteReleaseCheck.h"
@@ -160,6 +161,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-static-definition-in-anonymous-namespace");
CheckFactories.registerCheck<StringCompareCheck>(
"readability-string-compare");
+ CheckFactories.registerCheck<StringViewSubstrCheck>(
+ "readability-stringview-substr");
CheckFactories.registerCheck<readability::NamedParameterCheck>(
"readability-named-parameter");
CheckFactories.registerCheck<NonConstParameterCheck>(
diff --git a/clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.cpp b/clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.cpp
new file mode 100644
index 0000000000000..252af164ff073
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.cpp
@@ -0,0 +1,191 @@
+//===--- StringViewSubstrCheck.cpp - clang-tidy------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "StringViewSubstrCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::readability {
+
+void StringViewSubstrCheck::registerMatchers(MatchFinder *Finder) {
+ // Match string_view type
+ const auto StringViewDecl = recordDecl(hasName("::std::basic_string_view"));
+ const auto IsStringView = qualType(
+ hasUnqualifiedDesugaredType(recordType(hasDeclaration(StringViewDecl))));
+
+ // Length/size matcher reused in multiple places
+ const auto LengthMatcher = cxxMemberCallExpr(callee(memberExpr(hasDeclaration(
+ cxxMethodDecl(anyOf(hasName("length"), hasName("size")))))));
+
+ // Match various forms of zero
+ const auto IsZero =
+ expr(anyOf(ignoringParenImpCasts(integerLiteral(equals(0))),
+ ignoringParenImpCasts(declRefExpr(
+ to(varDecl(hasInitializer(integerLiteral(equals(0)))))))));
+
+ // Match substr() call patterns
+ const auto SubstrCall =
+ cxxMemberCallExpr(
+ callee(memberExpr(hasDeclaration(cxxMethodDecl(hasName("substr"))))),
+ on(expr(hasType(IsStringView)).bind("source")),
+ // substr always has 2 args (second one may be defaulted)
+ argumentCountIs(2),
+ anyOf(
+ // Case 1: sv.substr(n, npos) -> remove_prefix
+ allOf(hasArgument(0, expr().bind("prefix_n")),
+ hasArgument(1, cxxDefaultArgExpr())),
+
+ // Case 2: sv.substr(0, sv.length()) or sv.substr(0, sv.length() -
+ // 0) -> redundant self-copy
+ allOf(hasArgument(0, IsZero.bind("zero")),
+ hasArgument(
+ 1, anyOf(LengthMatcher.bind("full_length"),
+ binaryOperator(
+ hasOperatorName("-"),
+ hasLHS(LengthMatcher.bind("full_length")),
+ hasRHS(IsZero))))),
+
+ // Case 3: sv.substr(0, sv.length() - n) -> remove_suffix
+ allOf(hasArgument(0, IsZero),
+ hasArgument(
+ 1, binaryOperator(
+ hasOperatorName("-"),
+ hasLHS(LengthMatcher.bind("length_call")),
+ hasRHS(expr(unless(IsZero)).bind("suffix_n")))
+ .bind("length_minus_n")))))
+ .bind("substr_call");
+
+ // Only match assignments not part of larger expressions
+ Finder->addMatcher(
+ stmt(cxxOperatorCallExpr(
+ unless(isInTemplateInstantiation()),
+ hasOverloadedOperatorName("="),
+ hasArgument(0, expr(hasType(IsStringView)).bind("target")),
+ hasArgument(1, SubstrCall))
+ .bind("assignment"),
+ unless(anyOf(hasAncestor(varDecl()), hasAncestor(callExpr())))),
+ this);
+}
+
+void StringViewSubstrCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Assignment =
+ Result.Nodes.getNodeAs<CXXOperatorCallExpr>("assignment");
+ const auto *Target = Result.Nodes.getNodeAs<Expr>("target");
+ const auto *Source = Result.Nodes.getNodeAs<Expr>("source");
+ const auto *SubstrCall =
+ Result.Nodes.getNodeAs<CXXMemberCallExpr>("substr_call");
+ const auto *PrefixN = Result.Nodes.getNodeAs<Expr>("prefix_n");
+ const auto *FullLength = Result.Nodes.getNodeAs<Expr>("full_length");
+ const auto *LengthCall = Result.Nodes.getNodeAs<Expr>("length_call");
+ const auto *SuffixN = Result.Nodes.getNodeAs<Expr>("suffix_n");
+
+ if (!Assignment || !Target || !Source || !SubstrCall)
+ return;
+
+ const auto *TargetDRE = dyn_cast<DeclRefExpr>(Target->IgnoreParenImpCasts());
+ const auto *SourceDRE = dyn_cast<DeclRefExpr>(Source->IgnoreParenImpCasts());
+
+ if (!TargetDRE || !SourceDRE)
+ return;
+
+ const bool IsSameVar = (TargetDRE->getDecl() == SourceDRE->getDecl());
+ const std::string TargetName = TargetDRE->getNameInfo().getAsString();
+ const std::string SourceName = SourceDRE->getNameInfo().getAsString();
+
+ // Case 1: remove_prefix
+ if (PrefixN) {
+ if (!IsSameVar)
+ return;
+
+ std::string PrefixText =
+ Lexer::getSourceText(
+ CharSourceRange::getTokenRange(PrefixN->getSourceRange()),
+ *Result.SourceManager, Result.Context->getLangOpts())
+ .str();
+
+ std::string Replacement = TargetName + ".remove_prefix(" + PrefixText + ")";
+ diag(Assignment->getBeginLoc(), "prefer 'remove_prefix' over 'substr' for "
+ "removing characters from the start")
+ << FixItHint::CreateReplacement(Assignment->getSourceRange(),
+ Replacement);
+ return;
+ }
+
+ // Case 2: redundant full copy
+ if (FullLength) {
+ const auto *LengthObject =
+ cast<CXXMemberCallExpr>(FullLength)->getImplicitObjectArgument();
+ const auto *LengthDRE =
+ dyn_cast<DeclRefExpr>(LengthObject->IgnoreParenImpCasts());
+
+ if (!LengthDRE || LengthDRE->getDecl() != SourceDRE->getDecl())
+ return;
+
+ if (IsSameVar) {
+ // Remove redundant self copy including trailing semicolon
+ SourceLocation EndLoc = Lexer::findLocationAfterToken(
+ Assignment->getEndLoc(), tok::semi, *Result.SourceManager,
+ Result.Context->getLangOpts(), false);
+
+ if (EndLoc.isValid()) {
+ diag(Assignment->getBeginLoc(), "redundant self-copy")
+ << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
+ Assignment->getBeginLoc(), EndLoc));
+ }
+ } else {
+ // Direct copy between different variables
+ std::string Replacement = TargetName + " = " + SourceName;
+ diag(Assignment->getBeginLoc(), "prefer direct copy over substr")
+ << FixItHint::CreateReplacement(Assignment->getSourceRange(),
+ Replacement);
+ }
+ return;
+ }
+
+ // Case 3: remove_suffix
+ if (LengthCall && SuffixN) {
+ const auto *LengthObject =
+ cast<CXXMemberCallExpr>(LengthCall)->getImplicitObjectArgument();
+ const auto *LengthDRE =
+ dyn_cast<DeclRefExpr>(LengthObject->IgnoreParenImpCasts());
+
+ if (!LengthDRE || LengthDRE->getDecl() != SourceDRE->getDecl())
+ return;
+
+ std::string SuffixText =
+ Lexer::getSourceText(
+ CharSourceRange::getTokenRange(SuffixN->getSourceRange()),
+ *Result.SourceManager, Result.Context->getLangOpts())
+ .str();
+
+ if (IsSameVar) {
+ std::string Replacement =
+ TargetName + ".remove_suffix(" + SuffixText + ")";
+ diag(Assignment->getBeginLoc(), "prefer 'remove_suffix' over 'substr' "
+ "for removing characters from the end")
+ << FixItHint::CreateReplacement(Assignment->getSourceRange(),
+ Replacement);
+ } else {
+ std::string Replacement = TargetName + " = " + SourceName + ";\n" + " " +
+ TargetName + ".remove_suffix(" + SuffixText +
+ ")";
+
+ diag(Assignment->getBeginLoc(),
+ "prefer assignment and remove_suffix over substr")
+ << FixItHint::CreateReplacement(Assignment->getSourceRange(),
+ Replacement);
+ }
+
+ return;
+ }
+}
+
+} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.h b/clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.h
new file mode 100644
index 0000000000000..6a9051a265b12
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/StringViewSubstrCheck.h
@@ -0,0 +1,44 @@
+//===--- StringViewSubstrCheck.h - clang-tidy -------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGVIEWSUBSTRCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGVIEWSUBSTRCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::readability {
+
+/// Finds string_view substr() calls that can be replaced with remove_prefix()
+/// or remove_suffix().
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/readability/string-view-substr.html
+///
+/// The check matches two patterns:
+/// sv = sv.substr(N) -> sv.remove_prefix(N)
+/// sv = sv.substr(0, sv.length() - N) -> sv.remove_suffix(N)
+/// sv = sv.substr(0, sv.length()) -> // Remove redundant self-copy
+/// sv1 = sv2.substr(0, sv2.length()) -> sv1 = sv2
+/// sv1 = sv2.substr(0, sv2.length() - N) -> sv1 = sv2; sv1.remove_suffix(N)
+///
+/// These replacements make the intent clearer and are more efficient as they
+/// modify the string_view in place rather than creating a new one.
+class StringViewSubstrCheck : public ClangTidyCheck {
+public:
+ StringViewSubstrCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus17;
+ }
+};
+
+} // namespace clang::tidy::readability
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGVIEWSUBSTRCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 2bb4800df47c9..7acadffeb1a62 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -139,6 +139,12 @@ New checks
Checks for presence or absence of trailing commas in enum definitions and
initializer lists.
+- New :doc:`readability-stringview-substr
+ <clang-tidy/checks/readability/stringview-substr>` check.
+
+ Finds ``string_view`` ``substr()`` calls that can be replaced with
+ ``remove_prefix()`` or ``remove_suffix()``.
+
New check aliases
^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 0eabd9929dc39..804a8bd63721d 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -424,6 +424,7 @@ Clang-Tidy Checks
:doc:`readability-static-accessed-through-instance <readability/static-accessed-through-instance>`, "Yes"
:doc:`readability-static-definition-in-anonymous-namespace <readability/static-definition-in-anonymous-namespace>`, "Yes"
:doc:`readability-string-compare <readability/string-compare>`, "Yes"
+ :doc:`readability-stringview-substr <readability/stringview-substr>`, "Yes"
:doc:`readability-suspicious-call-argument <readability/suspicious-call-argument>`,
:doc:`readability-trailing-comma <readability/trailing-comma>`, "Yes"
:doc:`readability-uniqueptr-delete-release <readability/uniqueptr-delete-release>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/stringview-substr.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/stringview-substr.rst
new file mode 100644
index 0000000000000..451a5764b9ab3
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/stringview-substr.rst
@@ -0,0 +1,19 @@
+.. title:: clang-tidy - readability-stringview-substr
+
+readability-stringview-substr
+==============================
+
+Finds ``string_view substr()`` calls that can be replaced with clearer alternatives
+using ``remove_prefix()`` or ``remove_suffix()``.
+
+The check suggests the following transformations:
+
+=========================================== =======================================
+Expression Replacement
+=========================================== =======================================
+``sv = sv.substr(n)`` ``sv.remove_prefix(n)``
+``sv = sv.substr(0, sv.length()-n)`` ``sv.remove_suffix(n)``
+``sv = sv.substr(0, sv.length())`` Redundant self-copy
+``sv1 = sv.substr(0, sv.length())`` ``sv1 = sv``
+``sv1 = sv2.substr(0, sv2.length()-n)`` ``sv1 = sv2;`` ``sv1.remove_suffix(n)``
+=========================================== =======================================
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/stringview_substr.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/stringview_substr.cpp
new file mode 100644
index 0000000000000..f2dae554b689a
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/stringview_substr.cpp
@@ -0,0 +1,155 @@
+// RUN: %check_clang_tidy %s readability-stringview-substr -std=c++17 %t
+
+namespace std {
+template <typename T>
+class basic_string_view {
+public:
+ using size_type = unsigned long;
+ static constexpr size_type npos = -1;
+
+ basic_string_view(const char*) {}
+ basic_string_view substr(size_type pos, size_type count = npos) const { return *this; }
+ void remove_prefix(size_type n) {}
+ void remove_suffix(size_type n) {}
+ size_type length() const { return 0; }
+ size_type size() const { return 0; }
+ basic_string_view& operator=(const basic_string_view&) { return *this; }
+};
+
+using string_view = basic_string_view<char>;
+} // namespace std
+
+void test_basic() {
+ std::string_view sv("test");
+ std::string_view sv2("test");
+
+ // Case 1: remove_prefix
+ sv = sv.substr(5);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_prefix' over 'substr' for removing characters from the start [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_prefix(5);
+
+ // Case 3: remove_suffix with length()
+ sv = sv.substr(0, sv.length() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix(3);
+
+ // Case 3: remove_suffix with complex expression
+ sv = sv.substr(0, sv.length() - (3 + 2));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix((3 + 2));
+}
+
+void test_size_method() {
+ std::string_view sv("test");
+ std::string_view sv2("test");
+
+ // Case 3: remove_suffix with size()
+ sv = sv.substr(0, sv.size() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix(3);
+
+ // Case 2: redundant self-copy with size()
+ sv = sv.substr(0, sv.size());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant self-copy [readability-stringview-substr]
+
+ // Case 2: redundant self-copy with size() - 0
+ sv = sv.substr(0, sv.size() - 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant self-copy [readability-stringview-substr]
+
+ // Case 2: different vars, direct copy
+ sv2 = sv.substr(0, sv.size());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer direct copy over substr [readability-stringview-substr]
+ // CHECK-FIXES: sv2 = sv;
+}
+
+template <typename T>
+void test_template_instantiation() {
+ std::basic_string_view<T> sv("test");
+ std::basic_string_view<T> sv2("test");
+
+ // Should not match: inside a template instantiation
+ sv = sv.substr(0, sv.size() - 3); // No warning
+ sv = sv.substr(0, sv.length()); // No warning
+ sv2 = sv.substr(0, sv.size()); // No warning
+}
+
+// No matches when instantiated
+void test_template_no_matches() {
+ test_template_instantiation<char>(); // No warnings
+ test_template_instantiation<wchar_t>(); // No warnings
+}
+
+void test_copies() {
+ std::string_view sv("test");
+ std::string_view sv1("test");
+ std::string_view sv2("test");
+
+ // Redundant self-copies
+ sv = sv.substr(0, sv.length());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant self-copy [readability-stringview-substr]
+
+ sv = sv.substr(0, sv.length() - 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant self-copy [readability-stringview-substr]
+
+ // Direct copies between different variables
+ sv1 = sv.substr(0, sv.length());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer direct copy over substr [readability-stringview-substr]
+ // CHECK-FIXES: sv1 = sv;
+
+ sv2 = sv.substr(0, sv.length() - 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer direct copy over substr [readability-stringview-substr]
+ // CHECK-FIXES: sv2 = sv;
+}
+
+void test_zero_forms() {
+ std::string_view sv("test");
+ const int kZero = 0;
+ #define START_POS 0
+
+ // Various forms of zero in first argument
+ sv = sv.substr(kZero, sv.length() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix(3);
+
+ sv = sv.substr(START_POS, sv.length() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix(3);
+
+ sv = sv.substr((0), sv.length() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix(3);
+
+ sv = sv.substr(0u, sv.length() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix(3);
+
+ sv = sv.substr(0UL, sv.length() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer 'remove_suffix' over 'substr' for removing characters from the end [readability-stringview-substr]
+ // CHECK-FIXES: sv.remove_suffix(3);
+}
+
+void test_should_not_match() {
+ std::string_view sv("test");
+ std::string_view sv2("test");
+
+ // No match: substr used for prefix or mid-view
+ sv = sv.substr(1, sv.length() - 3); // No warning
+}
+
+void test_different_vars_remove_suffix() {
+ std::string_view sv("test");
+ std::string_view sv2("test");
+
+ // Different string_views with remove_suffix pattern
+ sv = sv2.substr(0, sv2.length() - 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer assignment and remove_suffix over substr [readability-stringview-substr]
+ // CHECK-FIXES: sv = sv2;
+ // CHECK-FIXES: sv.remove_suffix(3);
+}
+
+void f(std::string_view) {}
+void test_expr_with_cleanups() {
+ std::string_view sv("test");
+ const auto copy = sv = sv.substr(0, sv.length() - 3); // No warning
+ f(sv = sv.substr(0, sv.length() - 3)); // No warning
+}
More information about the cfe-commits
mailing list