11#include < gtest/gtest.h>
22
33import std;
4+ import mcpp.modgraph.glob;
45import mcpp.modgraph.graph;
56import mcpp.modgraph.scanner;
67import mcpp.modgraph.validate;
@@ -151,7 +152,7 @@ TEST(Scanner, GlobLiteralPrefixDerivation) {
151152 // Wildcard already in the first segment: no literal directory to bound to.
152153 EXPECT_EQ (glob_literal_prefix (" **/*.c" ), " " );
153154 // No wildcard at all: the full parent directory path is the prefix.
154- EXPECT_EQ (glob_literal_prefix (" a/b/c.cpp" ), " a/b" );
155+ EXPECT_EQ (glob_literal_prefix (" a/b/c.cpp" ). generic_string () , " a/b" );
155156 // Truncate back to the last COMPLETE '/' before the first wildcard char —
156157 // "x*.cpp" is a partial segment, not a real directory named "x".
157158 EXPECT_EQ (glob_literal_prefix (" src/x*.cpp" ), " src" );
@@ -161,6 +162,72 @@ TEST(Scanner, GlobLiteralPrefixDerivation) {
161162 EXPECT_EQ (glob_literal_prefix (" a/{x,y}/z" ), " a" );
162163}
163164
165+ // MSVC's std::filesystem::path preserves the separators of the string it was
166+ // constructed from, so a raw `a/b` prefix stays generic and `root / p` turns
167+ // into a MIXED `root\a/b` — which used to leak into compile_commands.json
168+ // (`file` / `-c` for every source under a multi-segment glob) and break CLion.
169+ // glob_literal_prefix must return NATIVE separators so the walk and everything
170+ // downstream is native too.
171+ TEST (Scanner, GlobLiteralPrefixUsesNativeSeparators) {
172+ EXPECT_EQ (glob_literal_prefix (" a/b/c.cpp" ).generic_string (), " a/b" );
173+ if constexpr (std::filesystem::path::preferred_separator == ' \\ ' ) {
174+ EXPECT_EQ (glob_literal_prefix (" a/b/c.cpp" ).string (), " a\\ b" );
175+ EXPECT_EQ (glob_literal_prefix (" a/b/c.cpp" ).string ().find (' /' ),
176+ std::string::npos);
177+ }
178+ }
179+
180+ // The exported converter itself — both spelling directions.
181+ TEST (Glob, NativePathFromGeneric) {
182+ auto p = mcpp::modgraph::native_path_from_generic (" a/b/c" );
183+ EXPECT_EQ (p.generic_string (), " a/b/c" );
184+ if constexpr (std::filesystem::path::preferred_separator == ' \\ ' ) {
185+ EXPECT_EQ (p.string (), " a\\ b\\ c" );
186+ // Already-native input is untouched.
187+ EXPECT_EQ (mcpp::modgraph::native_path_from_generic (" C:\\ x\\ y" ).string (),
188+ " C:\\ x\\ y" );
189+ }
190+ }
191+
192+ // The end-to-end shape of the reported bug: a source under a multi-segment
193+ // glob (`generated/modules/**/*.cppm`) must come out of expand_glob with
194+ // NATIVE separators on Windows — the mixed `root\generated/modules\a.cppm`
195+ // was what compile_commands.json's `file` field showed before the fix.
196+ TEST (Scanner, ExpandGlobMultiSegmentPrefixUsesNativeSeparators) {
197+ auto dir = make_tempdir (" mcpp-scanner-multi" );
198+ write (dir / " generated" / " modules" / " a.cppm" , " export module a;\n " );
199+
200+ auto files = expand_glob (dir, " generated/modules/**/*.cppm" );
201+
202+ ASSERT_EQ (files.size (), 1u );
203+ if constexpr (std::filesystem::path::preferred_separator == ' \\ ' ) {
204+ EXPECT_EQ (files[0 ].string ().find (' /' ), std::string::npos) << files[0 ];
205+ }
206+ EXPECT_EQ (files[0 ].generic_string (),
207+ (dir / " generated" / " modules" / " a.cppm" ).generic_string ());
208+
209+ std::filesystem::remove_all (dir);
210+ }
211+
212+ // Same contract for the INCLUDE-DIR channel (expand_dir_glob): a multi-segment
213+ // `third_party/inc` entry must yield a native path or the CDB's -I carries the
214+ // mixed form.
215+ TEST (Scanner, ExpandDirGlobMultiSegmentUsesNativeSeparators) {
216+ auto dir = make_tempdir (" mcpp-scanner-dirglob" );
217+ std::filesystem::create_directories (dir / " third_party" / " inc" );
218+
219+ auto dirs = expand_dir_glob (dir, " third_party/inc" );
220+
221+ ASSERT_EQ (dirs.size (), 1u );
222+ if constexpr (std::filesystem::path::preferred_separator == ' \\ ' ) {
223+ EXPECT_EQ (dirs[0 ].string ().find (' /' ), std::string::npos) << dirs[0 ];
224+ }
225+ EXPECT_EQ (dirs[0 ].generic_string (),
226+ (dir / " third_party" / " inc" ).generic_string ());
227+
228+ std::filesystem::remove_all (dir);
229+ }
230+
164231// mcpp#225: expand_glob must bound its walk to the glob's literal directory
165232// prefix ("src" for "src/**/*.cppm") instead of always walking the whole
166233// root and lexically filtering afterward. This is the FUNCTIONAL half of the
0 commit comments