rem_extract/extract/
extraction_utils.rs

1//! Utility functions for the rem-extract crate.
2//! At some point these will be merged into rem-utils.
3
4use crate::error::ExtractionError;
5
6use std::{
7    env,
8    fs,
9    path::PathBuf,
10    io::ErrorKind,
11};
12
13use camino::Utf8PathBuf;
14
15use ra_ap_project_model::{
16    CargoConfig,
17    ProjectWorkspace,
18    ProjectManifest,
19};
20
21use ra_ap_ide::{
22    Analysis,
23    AnalysisHost,
24    DiagnosticsConfig,
25    FileRange,
26    RootDatabase,
27    SingleResolve,
28    TextRange,
29    TextSize,
30    TextEdit,
31    SnippetEdit,
32    SourceChange,
33};
34
35use ra_ap_ide_db::{
36    imports::insert_use::{
37        ImportGranularity,
38        InsertUseConfig,
39        PrefixKind,
40    },
41    SnippetCap
42};
43
44use ra_ap_ide_assists::{
45    Assist,
46    AssistConfig,
47    AssistKind,
48    AssistResolveStrategy,
49};
50
51use ra_ap_vfs::{
52    AbsPathBuf,
53    VfsPath,
54    Vfs,
55    FileId,
56};
57
58use ra_ap_load_cargo::{
59    LoadCargoConfig,
60    ProcMacroServerChoice,
61    load_workspace,
62};
63
64use ra_ap_parser::{
65    T,
66    SyntaxKind::COMMENT,
67};
68
69use ra_ap_syntax::{
70    algo,
71    AstNode,
72    SourceFile
73};
74
75
76/// Returns the path to the manifest directory of the given file
77/// The manifest directory is the directory containing the Cargo.toml file
78/// for the project.
79///
80/// ## Example
81/// Given a directory structure like:
82/// ```plaintext
83/// /path/to/project
84/// ├── Cargo.toml
85/// └── src
86///    └── main.rs
87/// ```
88/// The manifest directory of `main.rs` is `/path/to/project`
89pub fn get_manifest_dir( path: &PathBuf ) -> Result<PathBuf, ExtractionError> {
90    // Start from the directory of the file
91    let mut current_dir = if path.is_file() {
92        path.parent().unwrap_or(path)
93    } else {
94        path.as_path()
95    };
96
97    // Check if the current directory is the root and contains a Cargo.toml file
98    if fs::metadata(current_dir.join("Cargo.toml")).is_ok() {
99        return Ok(current_dir.to_path_buf());
100    }
101
102    // Traverse up the directory tree until a Cargo.toml file is found
103    while let Some(parent) = current_dir.parent() {
104        if fs::metadata(current_dir.join("Cargo.toml")).is_ok() {
105            return Ok(current_dir.to_path_buf());
106        }
107        current_dir = parent;
108    }
109
110    // Return an InvalidManifest error if no Cargo.toml file is found
111    Err(ExtractionError::InvalidManifest)
112}
113
114/// Given an `&str` path to a file, returns the `AbsPathBuf` to the file.
115/// The `AbsPathBuf` is used by the `ra_ap` crates to represent file paths.
116/// If the input is not an absolute path, it resulves the path relative to the
117/// current directory.
118/// Will also canonicalize the path before returning it.
119pub fn convert_to_abs_path_buf(path: &str) -> Result<AbsPathBuf, Utf8PathBuf> {
120    if path.is_empty() {
121        return Err(Utf8PathBuf::from_path_buf(PathBuf::new()).unwrap());
122    }
123
124    // Check if the path is valid for a file system
125    if !path.is_ascii() {
126        return Err(Utf8PathBuf::from_path_buf(PathBuf::new()).unwrap());
127    }
128
129    // Attempt to convert it as-is (absolute path).
130    match AbsPathBuf::try_from(path) {
131        Ok(abs_path_buf) => Ok(abs_path_buf),
132        Err(_) => {
133            // Resolve non-absolute path to the current working directory.
134            let current_dir = env::current_dir().expect("Failed to get current directory");
135            let utf8_current_dir = Utf8PathBuf::from_path_buf(current_dir)
136                .expect("Failed to convert current directory to Utf8PathBuf");
137
138            // println!("Current dir: {:?}", utf8_current_dir);
139            // println!("Current path: {:?}", path);
140            let resolved_path = utf8_current_dir.join(path);
141
142            // Normalize the path to eliminate unnecessary components
143            let normalized_path = resolved_path.canonicalize().unwrap_or(resolved_path.clone().into());
144
145            // Create directories leading to the resolved path if they don't exist
146            if let Some(parent) = normalized_path.parent() {
147                fs::create_dir_all(parent).expect("Failed to create directories");
148            }
149
150            // Attempt to convert the normalized path to AbsPathBuf
151            let abs_path = AbsPathBuf::try_from(normalized_path.to_str().unwrap())
152                .map_err(|e| e); // Return the error if the resolved path is still invalid
153            // println!("Resolved path: {:?}", abs_path);
154
155            // If the abs_path as a string starts with either a \ or a ? (or some
156            // combination), strip it out
157
158            let abs_path_str: String = abs_path.unwrap().to_string();
159            let abs_path_str: String = abs_path_str
160                .replace(r"\\?\", "");
161
162            let new_abs_path = AbsPathBuf::try_from(abs_path_str.as_str())
163                .map_err(|e| e);
164
165            // println!("New abs path: {:?}", new_abs_path);
166            new_abs_path
167        }
168    }
169}
170
171/// Given a `PathBuf` to a folder, returns the `AbsPathBuf` to the `Cargo.toml`
172/// file in that folder.
173pub fn get_cargo_toml( manifest_dir: &PathBuf ) -> AbsPathBuf {
174    AbsPathBuf::try_from(
175        manifest_dir
176            .join( "Cargo.toml" )
177            .to_str()
178            .unwrap()
179    ).unwrap()
180}
181
182/// Loads as `ProjectManifest` from the given `AbsPathBuf` to a `Cargo.toml` file.
183pub fn load_project_manifest( cargo_toml: &AbsPathBuf ) -> ProjectManifest {
184    ProjectManifest::from_manifest_file(
185        cargo_toml.clone()
186    ).unwrap()
187}
188
189/// Loads in the custom cargo configuration
190/// TODO This is currently just the derived default.
191pub fn get_cargo_config( _manifest: &ProjectManifest ) -> CargoConfig {
192    CargoConfig::default()
193}
194
195pub fn progress( _message: String ) -> () {
196    // println!( "{}", _message );
197}
198
199/// Loads a project workspace from a `ProjectManifest` and `CargoConfig`
200pub fn load_project_workspace(
201    project_manifest: &ProjectManifest,
202    cargo_config: &CargoConfig,
203) -> ProjectWorkspace {
204    ProjectWorkspace::load(
205        project_manifest.clone(),
206        cargo_config,
207        &progress
208    ).unwrap()
209}
210
211/// Loads a `RootDatabase` containing from a `ProjectWorkspace` and `CargoConfig`
212pub fn load_workspace_data(
213    workspace: ProjectWorkspace,
214    cargo_config: &CargoConfig,
215) -> (
216    RootDatabase,
217    Vfs
218) {
219    let load_cargo_config: LoadCargoConfig = LoadCargoConfig {
220        load_out_dirs_from_check: true,
221        with_proc_macro_server: ProcMacroServerChoice::None,
222        prefill_caches: false,
223    };
224
225    let (db,
226        vfs,
227        _proc_macro
228    ) = load_workspace(
229        workspace,
230        &cargo_config.extra_env,
231        &load_cargo_config
232    ).unwrap();
233
234    (db, vfs)
235}
236
237/// Runs the analysis on an AnalysisHost. A wrapper around `AnalysisHost::analysis`
238pub fn run_analysis( host: AnalysisHost ) -> Analysis {
239
240    let analysis: Analysis = host.analysis();
241
242    analysis
243}
244
245/// Verifies the input selection for extraction.
246/// # Input
247/// - analysis: The `&Analysis` object containing the analysis data
248/// - vfs: The `&Vfs` object containing the virtual file system data
249/// - input_path: The `&AbsPathBuf` to the input file
250/// - range: The tuple of start and end offsets for the selection
251///
252/// # Returns
253/// - Ok(()) if the input is valid
254/// - Err(ExtractionError::CommentNotApplicable) if the selection is a comment
255/// - Err(ExtractionError::BracesNotApplicable) if the selection is braces
256
257/// Gets a list of available assists for a given file and range
258pub fn get_assists (
259    // analysis: &Analysis,
260    db: &RootDatabase,
261    vfs: &Vfs,
262    input_path: &AbsPathBuf,
263    range: (u32, u32), // Tuple of start and end offsets
264) -> Vec<Assist> {
265    let assist_config: AssistConfig = generate_assist_config();
266    // let diagnostics_config: DiagnosticsConfig = generate_diagnostics_config();
267    let resolve: AssistResolveStrategy = generate_resolve_strategy();
268    let frange: FileRange = generate_frange(input_path, vfs, range);
269
270    // Call the assists_with_fixes method
271    // let assists: Vec<Assist> = analysis.assists_with_fixes(
272    //     &assist_config,
273    //     &diagnostics_config,
274    //     resolve,
275    //     frange
276    // ).unwrap();
277
278    let assists: Vec<Assist> = ra_ap_ide_assists::assists(db, &assist_config, resolve, frange);
279
280    assists
281}
282
283// Build out the AssistConfig Object
284pub fn generate_assist_config() -> AssistConfig {
285    let snippet_cap_: Option<SnippetCap> = None;
286    let allowed_assists: Vec<AssistKind> = vec![
287        // AssistKind::QuickFix,
288        // AssistKind::Refactor,
289        // AssistKind::RefactorInline,
290        // AssistKind::RefactorRewrite,
291        // AssistKind::Generate,
292        AssistKind::RefactorExtract,
293    ];
294
295    let insert_use_: InsertUseConfig = InsertUseConfig {
296        granularity: ImportGranularity::Preserve,
297        enforce_granularity: false,
298        prefix_kind: PrefixKind::ByCrate,
299        group: false,
300        skip_glob_imports: false,
301    };
302
303    let assist_config: AssistConfig = AssistConfig {
304        snippet_cap: snippet_cap_,
305        allowed: Some(allowed_assists),
306        insert_use: insert_use_,
307        prefer_no_std: false,
308        prefer_prelude: false,
309        prefer_absolute: false,
310        assist_emit_must_use: false,
311        term_search_fuel: 2048, // * NFI what this is
312        term_search_borrowck: false,
313    };
314    assist_config
315}
316
317// Build out the DiagnosticsConfig
318pub fn generate_diagnostics_config() -> DiagnosticsConfig {
319    DiagnosticsConfig::test_sample()
320}
321
322// Build out the ResolveStrategy
323pub fn generate_resolve_strategy() -> AssistResolveStrategy {
324    // FIXME: This is currently bugged it seems - Both extract_variable and extract_function are being returned
325    let single_resolve: SingleResolve = SingleResolve {
326        assist_id: "extract_function".to_string(),
327        assist_kind: AssistKind::RefactorExtract,
328    };
329
330    let resolve_strategy: AssistResolveStrategy = AssistResolveStrategy::Single(single_resolve);
331    resolve_strategy
332}
333
334// Build out the FileRange object
335pub fn generate_frange(
336    input_path: &AbsPathBuf,
337    vfs: &Vfs,
338    range: (u32, u32)
339) -> FileRange {
340    let vfs_path: VfsPath = VfsPath::new_real_path(
341        input_path
342            .as_str()
343            .to_string(),
344    );
345
346    let file_id_: FileId = vfs.file_id( &vfs_path ).unwrap();
347    let range_: TextRange = TextRange::new(
348        TextSize::try_from( range.0 ).unwrap(),
349        TextSize::try_from( range.1 ).unwrap(),
350    );
351
352    let frange: FileRange = FileRange {
353        file_id: file_id_,
354        range: range_,
355    };
356    frange
357}
358
359pub fn generate_frange_from_fileid(
360    file_id: FileId,
361    range: (u32, u32)
362) -> FileRange {
363    let range_: TextRange = TextRange::new(
364        TextSize::try_from( range.0 ).unwrap(),
365        TextSize::try_from( range.1 ).unwrap(),
366    );
367
368    let frange: FileRange = FileRange {
369        file_id: file_id,
370        range: range_,
371    };
372    frange
373}
374
375/// Filter the list of assists to only be the extract_function assist
376/// FIXME This is a hack to get around the fact that the resolve strategy is bugged
377/// and is returning both extract_variable and extract_function
378/// Throws ExtractionError::NoExtractFunction if no assist found
379pub fn filter_extract_function_assist( assists: Vec<Assist> ) -> Result<Assist, ExtractionError> {
380    if let Some(extract_assist) = assists
381        .iter()
382        .find(|assist| assist.label == "Extract into function")
383        {
384            // Return the found assist
385            Ok(extract_assist.clone())
386        } else {
387            // Return the error
388            Err(ExtractionError::NoExtractFunction( assists ))
389        }
390}
391
392/// Applies the extract_function source change to the given code
393/// Returns the String of the output code
394/// Renames the function from `fun_name` to `callee_name`.
395/// Requires the output path to be an `AbsPathBuf`.
396pub fn apply_extract_function(
397    assist: &Assist,
398    input_path: &AbsPathBuf,
399    vfs: &Vfs,
400    callee_name: &str,
401) -> Result<String, ExtractionError> {
402
403    let vfs_in_path: VfsPath = VfsPath::new_real_path(
404        input_path
405            .as_str()
406            .to_string(),
407    );
408
409    // From here, extract the source change, but apply it to the copied file
410    let src_change: SourceChange = assist.source_change
411        .as_ref()
412        .unwrap()
413        .clone();
414
415    let in_file_id: FileId = vfs.file_id( &vfs_in_path ).unwrap();
416    let (text_edit, maybe_snippet_edit) = src_change.get_source_and_snippet_edit(
417        in_file_id
418    ).unwrap();
419
420    // Get the source from the input file
421    let src_path: PathBuf = vfs_to_pathbuf( &vfs_in_path );
422    let text: String = fs::read_to_string( &src_path ).unwrap();
423    let edited_text: String = apply_edits(
424        text,
425        text_edit.clone(),
426        maybe_snippet_edit.clone(),
427    );
428
429    // Rename the function from fun_name to NEW_FUNCTION_NAME using a search and
430    // replace on the output file
431    let renamed_text: String = rename_function(
432        edited_text,
433        "fun_name",
434        callee_name,
435    );
436
437    // Ensure that the output file imports std::ops::ControlFlow if it uses it
438    let fixed_cf_text: String = fixup_controlflow( renamed_text );
439
440    Ok( fixed_cf_text )
441}
442
443/// Applies the edits to a given set of source code (as a String)
444pub fn apply_edits(
445    text: String,
446    text_edit: TextEdit,
447    maybe_snippet_edit: Option<SnippetEdit>,
448) -> String {
449    let mut text: String = text; // Make the text mutable
450    text_edit.apply( &mut text );
451    match maybe_snippet_edit {
452        Some( snippet_edit ) => {
453            snippet_edit.apply( &mut text );
454        },
455        None => (),
456    }
457    text
458}
459
460// Rename a function in a file using a search and replace
461pub fn rename_function(
462    text: String,
463    old_name: &str,
464    new_name: &str,
465) -> String {
466    let mut text = text; // Make the text mutable
467    let old_name: String = old_name.to_string();
468    let new_name: String = new_name.to_string();
469    text = text.replace( &old_name, &new_name );
470    text
471}
472
473/// Converts a `VfsPath` to a `PathBuf`
474fn vfs_to_pathbuf( vfs_path: &VfsPath ) -> PathBuf {
475    let path_str = vfs_path.to_string();
476    // println!("{}", path_str);
477    PathBuf::from( path_str )
478}
479
480/// Checks that there is some input to the function that isn't a comment
481/// # Returns
482/// - `Ok(())` if the input is not a comment
483/// - `Err(ExtractionError::CommentNotApplicable)` if the input is a comment
484pub fn check_comment(
485    source_file: &SourceFile,
486    range: &(u32, u32)
487) -> Result<(), ExtractionError> {
488    let frange: TextRange = TextRange::new(
489        TextSize::new(range.0),
490        TextSize::new(range.1),
491    );
492    let node = source_file
493        .syntax()
494        .covering_element(frange);
495
496    if node.kind() == COMMENT {
497        return Err(ExtractionError::CommentNotApplicable);
498    }
499
500    Ok(())
501}
502
503/// Checks that there is some input to the function that isn't a brace
504/// For every:
505/// - { there is a }
506/// - [ there is a ]
507/// - ( there is a )
508/// # Returns
509/// - `Ok(())` if the input is not a brace
510/// - `Err(ExtractionError::BracesNotApplicable)` if the input is a brace
511pub fn check_braces(
512    source_file: &SourceFile,
513    range: &(u32, u32)
514) -> Result<(), ExtractionError> {
515    let frange: TextRange = TextRange::new(
516        TextSize::new(range.0),
517        TextSize::new(range.1),
518    );
519    let node = source_file
520        .syntax()
521        .covering_element(frange);
522
523    if matches!(node.kind(), T!['{'] | T!['}'] | T!['('] | T![')'] | T!['['] | T![']']) {
524        return Err(ExtractionError::BracesNotApplicable);
525    }
526
527    Ok(())
528
529}
530
531/// Trims the selected range to remove any whitespace
532pub fn trim_range(
533    source_file: &SourceFile,
534    range: &(u32, u32)
535) -> (u32, u32) {
536    let start = TextSize::new(range.0);
537    let end = TextSize::new(range.1);
538    let left = source_file
539        .syntax()
540        .token_at_offset( start )
541        .right_biased()
542        .and_then(|t| algo::skip_whitespace_token(t, rowan::Direction::Next))
543        .map(|t| t.text_range().start().clamp(start, end));
544    let right = source_file
545        .syntax()
546        .token_at_offset( end )
547        .left_biased()
548        .and_then(|t| algo::skip_whitespace_token(t, rowan::Direction::Prev))
549        .map(|t| t.text_range().end().clamp(start, end));
550
551    let trimmed_range = match (left, right) {
552        (Some(left), Some(right)) if left <= right => TextRange::new(left, right),
553        // Selection solely consists of whitespace so just fall back to the original
554        _ => TextRange::new(start, end),
555    };
556
557    ( trimmed_range.start().into(), trimmed_range.end().into() )
558
559}
560
561/// Checks if a file contains a reference to ControlFlow::, and if so, adds  use
562/// std::ops::ControlFlow;\n\n to the start of the file, saving it back to the input path
563/// Returns the new text with the ControlFlow:: reference fixed up
564pub fn fixup_controlflow( text: String, ) -> String {
565    let mut text: String = text; // Make the text mutable
566    let controlflow_ref: &str = "ControlFlow::";
567    if text.contains( controlflow_ref ) {
568        text = format!("use std::ops::ControlFlow;\n\n{}", text);
569    }
570    text
571}
572
573// Check if the idx pair is valid
574pub fn check_idx(start: &u32, end: &u32) -> Result<(), ExtractionError> {
575    let start = *start;
576    let end = *end;
577    if start == end {
578        return Err(ExtractionError::SameIdx);
579    } else if start > end {
580        return Err(ExtractionError::InvalidIdxPair);
581    }
582    if end == 0 {
583        return Err(ExtractionError::InvalidEndIdx);
584    }
585    Ok(())
586}
587
588// Check if the file exists and is readable
589pub fn check_file_exists(file_path: &str) -> Result<(), ExtractionError> {
590    if fs::metadata(file_path).is_err() {
591        return Err(ExtractionError::Io(std::io::Error::new(
592            ErrorKind::NotFound,
593            format!("File not found: {}", file_path),
594        )));
595    }
596    Ok(())
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602    use std::fs::{self, File};
603    use std::io::Write;
604    use std::env;
605    use camino::Utf8Path;
606
607    // Helper function to create a temporary directory with a Cargo.toml
608    fn setup_temp_project() -> PathBuf {
609        let temp_dir = env::temp_dir().join("test_project");
610        let _ = fs::create_dir_all(&temp_dir);
611        let cargo_toml = temp_dir.join("Cargo.toml");
612
613        let mut file = File::create(cargo_toml).unwrap();
614        writeln!(file, "[package]\nname = \"test_project\"\nversion = \"0.1.0\"").unwrap();
615
616        temp_dir
617    }
618
619    // Test case when Cargo.toml exists
620    #[test]
621    fn test_get_manifest_dir_valid() {
622        let temp_dir = setup_temp_project();
623        let src_dir = temp_dir.join("src");
624        let _ = fs::create_dir_all(&src_dir);
625        let main_file = src_dir.join("main.rs");
626        File::create(&main_file).unwrap();
627
628        let result = get_manifest_dir(&main_file);
629        assert!(result.is_ok());
630        assert_eq!(result.unwrap(), temp_dir);
631    }
632
633    // Test case when Cargo.toml does not exist
634    #[test]
635    fn test_get_manifest_dir_invalid_manifest() {
636        let temp_dir = env::temp_dir().join("test_invalid_project");
637        let _ = fs::create_dir_all(&temp_dir);
638        let src_dir = temp_dir.join("src");
639        let _ = fs::create_dir_all(&src_dir);
640        let main_file = src_dir.join("main.rs");
641        File::create(&main_file).unwrap();
642
643        let result = get_manifest_dir(&main_file);
644        assert!(result.is_err());
645
646        // Check that the error is an InvalidManifest
647        if let ExtractionError::InvalidManifest = result.unwrap_err() {
648            // Correct error type
649        } else {
650            panic!("Expected InvalidManifest error");
651        }
652    }
653
654    // Test case when the path is to a directory, not a file
655    #[test]
656    fn test_get_manifest_dir_directory() {
657        let temp_dir = setup_temp_project();
658        let src_dir = temp_dir.join("src");
659        let _ = fs::create_dir_all(&src_dir);
660
661        let result = get_manifest_dir(&src_dir);
662        assert!(result.is_ok());
663        assert_eq!(result.unwrap(), temp_dir);
664    }
665
666    // Test case when the path points to a non-existent file
667    #[test]
668    fn test_get_manifest_dir_non_existent_file() {
669        let temp_dir = env::temp_dir().join("test_non_existent_project");
670        let src_dir = temp_dir.join("src");
671
672        let non_existent_file = src_dir.join("does_not_exist.rs");
673        let result = get_manifest_dir(&non_existent_file);
674        assert!(result.is_err());
675
676        // Check that the error is an InvalidManifest
677        if let ExtractionError::InvalidManifest = result.unwrap_err() {
678            // Correct error type
679        } else {
680            panic!("Expected InvalidManifest error");
681        }
682    }
683
684    #[test]
685    ///Only run this test on Windows as it tests Windows-specific paths
686    /// This test is skipped on other platforms
687    #[cfg(target_os = "windows")]
688    fn test_absolute_path_windows() {
689        // Test with an absolute path (Windows-style)
690        let abs_path = r"C:\Windows\System32";
691        let result = convert_to_abs_path_buf(abs_path);
692        assert!(result.is_ok(), "Expected absolute path conversion to succeed");
693
694        // Check if the path remains unchanged
695        let abs_path_buf = result.unwrap();
696        assert_eq!(<AbsPathBuf as AsRef<Utf8Path>>::as_ref(&abs_path_buf), Utf8Path::new(abs_path));
697    }
698
699    #[test]
700    ///Only run this test on Windows as it tests Windows-specific paths
701    /// This test is skipped on other platforms
702    #[cfg(target_os = "windows")]
703    fn test_relative_path_windows() {
704        // Test with a relative path (Windows-style)
705        let rel_path = r"src\main.rs";
706        let result = convert_to_abs_path_buf(rel_path);
707        assert!(result.is_ok(), "Expected relative path conversion to succeed");
708
709        // Check if the relative path is resolved to an absolute path
710        let current_dir = env::current_dir().unwrap();
711        let expected_abs_path = Utf8PathBuf::from_path_buf(current_dir).unwrap().join(rel_path);
712        let abs_path_buf = result.unwrap();
713
714        // Compare the canonicalized paths
715        let left_path = <AbsPathBuf as AsRef<Utf8Path>>::as_ref(&abs_path_buf).to_string().replace(r"\\?\", "");
716        let right_path = expected_abs_path.as_path().to_string().replace(r"\\?\", "");
717        assert_eq!(left_path, right_path);
718    }
719
720    #[test]
721    ///Only run this test on Windows as it tests Windows-specific paths
722    /// This test is skipped on other platforms
723    #[cfg(target_os = "windows")]
724    fn test_invalid_utf8_path_windows() {
725        // Test with a path that cannot be converted to a valid UTF-8 path
726        let invalid_utf8_path = r"C:\invalid\�path";
727        let result = convert_to_abs_path_buf(invalid_utf8_path);
728        assert!(result.is_err(), "Expected invalid UTF-8 path to fail conversion");
729    }
730
731    #[test]
732    fn test_empty_path_windows() {
733        // Test with an empty path
734        let empty_path = "";
735        let result = convert_to_abs_path_buf(empty_path);
736        assert!(result.is_err(), "Expected empty path to fail conversion");
737    }
738
739    #[test]
740    fn test_root_path_windows() {
741        // Test with a root path (Windows-style)
742        let root_path = r"C:\";
743        let result = convert_to_abs_path_buf(root_path);
744        assert!(result.is_ok(), "Expected root path conversion to succeed");
745
746        let abs_path_buf = result.unwrap();
747        assert_eq!(<AbsPathBuf as AsRef<Utf8Path>>::as_ref(&abs_path_buf), Utf8Path::new(root_path));
748    }
749
750    #[test]
751    fn test_resolve_relative_path_windows() {
752        // Test with a complex relative path (Windows-style)
753        let complex_rel_path = r"src\..\Cargo.toml";
754        let result = convert_to_abs_path_buf(complex_rel_path);
755        assert!(result.is_ok(), "Expected complex relative path conversion to succeed");
756
757        // Check if the relative path is resolved correctly
758        let current_dir = env::current_dir().unwrap();
759        let expected_abs_path = Utf8PathBuf::from_path_buf(current_dir)
760            .unwrap()
761            .join(complex_rel_path)
762            .canonicalize_utf8()
763            .unwrap();
764        let abs_path_buf = result.unwrap();
765
766        // Compare the canonicalized paths
767        let left_path = <AbsPathBuf as AsRef<Utf8Path>>::as_ref(&abs_path_buf).to_string().replace(r"\\?\", "");
768        let right_path = expected_abs_path.as_path().to_string().replace(r"\\?\", "");
769        assert_eq!( left_path, right_path );
770
771    }
772}