diff --git a/iwyu_include_picker.cc b/iwyu_include_picker.cc index dcc626a..9862115 100644 --- a/iwyu_include_picker.cc +++ b/iwyu_include_picker.cc @@ -1436,10 +1436,14 @@ void IncludePicker::ExpandRegexes() { for (const auto& incmap : quoted_includes_to_quoted_includers_) { const string& hdr = incmap.first; for (const string& regex_key : filepath_include_map_regex_keys) { + const string regex = regex_key.substr(1); const vector& map_to = filepath_include_map_[regex_key]; - if (RegexMatch(regex_dialect, hdr, regex_key.substr(1)) && + if (RegexMatch(regex_dialect, hdr, regex) && !ContainsQuotedInclude(map_to, hdr)) { - Extend(&filepath_include_map_[hdr], filepath_include_map_[regex_key]); + for (const MappedInclude& target : map_to) { + filepath_include_map_[hdr].push_back(MappedInclude( + RegexReplace(regex_dialect, hdr, regex, target.quoted_include))); + } MarkVisibility(&include_visibility_map_, hdr, include_visibility_map_[regex_key]); } diff --git a/iwyu_regex.cc b/iwyu_regex.cc index 794dc6d..21f4f01 100644 --- a/iwyu_regex.cc +++ b/iwyu_regex.cc @@ -46,4 +46,26 @@ bool RegexMatch(RegexDialect dialect, const std::string& str, CHECK_UNREACHABLE_("Unexpected regex dialect"); } +std::string RegexReplace(RegexDialect dialect, const std::string& str, + const std::string& pattern, + const std::string& replacement) { + switch (dialect) { + case RegexDialect::LLVM: { + // llvm::Regex::sub has search semantics. Enclose the pattern in ^...$ + // for start/end anchoring. + llvm::Regex r("^" + pattern + "$"); + return r.sub(replacement, str); + } + + case RegexDialect::ECMAScript: { + // std::regex_replace has search semantics. Enclose the pattern in ^...$ + // for start/end anchoring. + std::regex r("^" + pattern + "$", std::regex_constants::ECMAScript); + return std::regex_replace(str, r, replacement, + std::regex_constants::format_first_only); + } + } + CHECK_UNREACHABLE_("Unexpected regex dialect"); +} + } // namespace include_what_you_use diff --git a/iwyu_regex.h b/iwyu_regex.h index 63b4435..54f4a6c 100644 --- a/iwyu_regex.h +++ b/iwyu_regex.h @@ -23,6 +23,12 @@ bool ParseRegexDialect(const char* str, RegexDialect* dialect); bool RegexMatch(RegexDialect dialect, const std::string& str, const std::string& pattern); +// Returns input string with the first match of pattern replaced, for the given +// regex dialect. +std::string RegexReplace(RegexDialect dialect, const std::string& str, + const std::string& pattern, + const std::string& replacement); + } // namespace include_what_you_use #endif // INCLUDE_WHAT_YOU_USE_IWYU_REGEX_H_ diff --git a/tests/cxx/mapping_replace_regex_ecmascript.cc b/tests/cxx/mapping_replace_regex_ecmascript.cc new file mode 100644 index 0000000..eb440c9 --- /dev/null +++ b/tests/cxx/mapping_replace_regex_ecmascript.cc @@ -0,0 +1,38 @@ +//===--- mapping_replace_regex_ecmascript.cc - test input file for iwyu ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// IWYU_ARGS: -I . \ +// -Xiwyu --regex=ecmascript \ +// -Xiwyu --mapping_file=tests/cxx/mapping_replace_regex_ecmascript.imp + +// Generically map include paths to a different include directory: +// * The include of tests/cxx/direct.h should nominally be replaced by +// tests/cxx/indirect.h, where IndirectClass is defined +// * But we provide a mapping adding a "foobar" prefix to any include under +// "tests/cxx", resulting in suggesting foobar/tests/cxx/indirect.h. + +#include "tests/cxx/direct.h" + +void f() { + // IWYU: IndirectClass is defined in "foobar/tests/cxx/indirect.h" + IndirectClass i; +} + +/**** IWYU_SUMMARY + +tests/cxx/mapping_replace_regex_ecmascript.cc should add these lines: +#include "foobar/tests/cxx/indirect.h" + +tests/cxx/mapping_replace_regex_ecmascript.cc should remove these lines: +- #include "tests/cxx/direct.h" // lines XX-XX + +The full include-list for tests/cxx/mapping_replace_regex_ecmascript.cc: +#include "foobar/tests/cxx/indirect.h" // for IndirectClass + +***** IWYU_SUMMARY */ diff --git a/tests/cxx/mapping_replace_regex_ecmascript.imp b/tests/cxx/mapping_replace_regex_ecmascript.imp new file mode 100644 index 0000000..f26a227 --- /dev/null +++ b/tests/cxx/mapping_replace_regex_ecmascript.imp @@ -0,0 +1,5 @@ +# Maps headers to a different include path. +# The foobar directory does not exist; we just want IWYU to suggest using it. +[ + { include: ['@"(tests/cxx/.*)"', private, '"foobar/$1"', public ] }, +] diff --git a/tests/cxx/mapping_replace_regex_llvm.cc b/tests/cxx/mapping_replace_regex_llvm.cc new file mode 100644 index 0000000..ae02a22 --- /dev/null +++ b/tests/cxx/mapping_replace_regex_llvm.cc @@ -0,0 +1,38 @@ +//===--- mapping_replace_regex_llvm.cc - test input file for iwyu ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// IWYU_ARGS: -I . \ +// -Xiwyu --regex=llvm \ +// -Xiwyu --mapping_file=tests/cxx/mapping_replace_regex_llvm.imp + +// Generically map include paths to a different include directory: +// * The include of tests/cxx/direct.h should nominally be replaced by +// tests/cxx/indirect.h, where IndirectClass is defined +// * But we provide a mapping adding a "foobar" prefix to any include under +// "tests/cxx", resulting in suggesting foobar/tests/cxx/indirect.h. + +#include "tests/cxx/direct.h" + +void f() { + // IWYU: IndirectClass is defined in "foobar/tests/cxx/indirect.h" + IndirectClass i; +} + +/**** IWYU_SUMMARY + +tests/cxx/mapping_replace_regex_llvm.cc should add these lines: +#include "foobar/tests/cxx/indirect.h" + +tests/cxx/mapping_replace_regex_llvm.cc should remove these lines: +- #include "tests/cxx/direct.h" // lines XX-XX + +The full include-list for tests/cxx/mapping_replace_regex_llvm.cc: +#include "foobar/tests/cxx/indirect.h" // for IndirectClass + +***** IWYU_SUMMARY */ diff --git a/tests/cxx/mapping_replace_regex_llvm.imp b/tests/cxx/mapping_replace_regex_llvm.imp new file mode 100644 index 0000000..1c546f2 --- /dev/null +++ b/tests/cxx/mapping_replace_regex_llvm.imp @@ -0,0 +1,5 @@ +# Maps headers to a different include path. +# The foobar directory does not exist; we just want IWYU to suggest using it. +[ + { include: ['@"(tests/cxx/.*)"', private, '"foobar/\1"', public ] }, +]