diff --git a/contrib/graphql-codegen-client-preset/src/lib.rs b/contrib/graphql-codegen-client-preset/src/lib.rs index a40dc4f80..72b761d33 100644 --- a/contrib/graphql-codegen-client-preset/src/lib.rs +++ b/contrib/graphql-codegen-client-preset/src/lib.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use graphql_parser::query::parse_query; use pathdiff::diff_paths; @@ -20,6 +20,27 @@ fn capetalize(s: &str) -> String { format!("{}{}", &s[..1].to_uppercase(), &s[1..]) } +/// Returns true if `path` is an absolute path on either Unix (`/foo`) or +/// Windows (`C:/foo`, `C:\foo`). We cannot rely on `Path::is_absolute()` +/// because when the plugin runs inside a WASM runtime it always uses Unix +/// path semantics, so Windows absolute paths would be misclassified as +/// relative. +fn is_absolute_path(path: &str) -> bool { + // Unix absolute path + if path.starts_with('/') { + return true; + } + // Windows absolute path: starts with a drive letter followed by ':' and a + // slash/backslash, e.g. "C:/..." or "C:\..." + let mut chars = path.chars(); + if let (Some(drive), Some(':'), Some(sep)) = (chars.next(), chars.next(), chars.next()) { + if drive.is_ascii_alphabetic() && (sep == '/' || sep == '\\') { + return true; + } + } + false +} + #[cfg(test)] mod tests; @@ -49,21 +70,30 @@ impl GraphQLVisitor { } fn get_relative_import_path(&self, path_end: &str) -> String { + // Normalize Windows-style backslashes to forward slashes so that PathBuf + // works correctly when the plugin runs in a WASM context (which always uses + // Unix path semantics) but receives Windows paths from the host. + let cwd = self.options.cwd.replace('\\', "/"); + let filename = self.options.filename.replace('\\', "/"); + let artifact_directory = self.options.artifact_directory.replace('\\', "/"); + // using PathBuf to add the relative path to the artifact directory - let mut file_full_path = PathBuf::from(&self.options.cwd); - file_full_path.push(&self.options.filename); + let mut file_full_path = PathBuf::from(&cwd); + file_full_path.push(&filename); let file_s_dirname = file_full_path.parent().unwrap(); // The resolved artifact directory as seen from the current running SWC plugin - // working directory - let resolved_artifact_directory = - if Path::new(&self.options.artifact_directory).is_relative() { - let mut cwd = PathBuf::from(&self.options.cwd); - cwd.push(&self.options.artifact_directory); - cwd.to_string_lossy().to_string() - } else { - self.options.artifact_directory.to_string() - }; + // working directory. + // We check for absolute paths ourselves instead of relying on + // Path::is_absolute(), because on WASM (Unix semantics) a Windows absolute + // path such as "C:/project/src" would be considered relative. + let resolved_artifact_directory = if is_absolute_path(&artifact_directory) { + artifact_directory.to_string() + } else { + let mut cwd_path = PathBuf::from(&cwd); + cwd_path.push(&artifact_directory); + cwd_path.to_string_lossy().to_string() + }; let mut relative = diff_paths(resolved_artifact_directory, file_s_dirname).unwrap(); diff --git a/contrib/graphql-codegen-client-preset/src/tests.rs b/contrib/graphql-codegen-client-preset/src/tests.rs index dbe5acdff..9cfd4dc1e 100644 --- a/contrib/graphql-codegen-client-preset/src/tests.rs +++ b/contrib/graphql-codegen-client-preset/src/tests.rs @@ -79,6 +79,33 @@ fn import_files_from_other_directory(input_path: PathBuf) { ); } +fn get_windows_path_visitor() -> GraphQLVisitor { + // Simulate a Windows environment where cwd and artifact_directory use backslashes. + // The WASM plugin runs with Unix path semantics, so it must normalize these. + GraphQLVisitor::new(GraphQLCodegenOptions { + filename: "src\\App.tsx".to_string(), + cwd: "C:\\Users\\user\\project".to_string(), + artifact_directory: "C:\\Users\\user\\project\\src\\gql".to_string(), + gql_tag_name: "gql".to_string(), + naming_convention: "change-case-all#pascalCase".to_string(), + }) +} + +test!( + Default::default(), + |_| visit_mut_pass(get_windows_path_visitor()), + windows_absolute_paths_produce_valid_relative_import, + r#"import gql from "gql-tag"; + +const GetUser = gql(` + query GetUser { + user { + id + } + } +`);"# +); + test!( Default::default(), |_| visit_mut_pass(get_test_code_visitor()), diff --git a/contrib/graphql-codegen-client-preset/tests/__swc_snapshots__/src/tests.rs/windows_absolute_paths_produce_valid_relative_import.js b/contrib/graphql-codegen-client-preset/tests/__swc_snapshots__/src/tests.rs/windows_absolute_paths_produce_valid_relative_import.js new file mode 100644 index 000000000..42469c133 --- /dev/null +++ b/contrib/graphql-codegen-client-preset/tests/__swc_snapshots__/src/tests.rs/windows_absolute_paths_produce_valid_relative_import.js @@ -0,0 +1,3 @@ +import { GetUserDocument } from "./gql/graphql"; +import gql from "gql-tag"; +const GetUser = GetUserDocument;