Skip to content

Commit 903317a

Browse files
fix(graphql-codegen-client-preset): handle Windows-style paths in WASM runtime (#595)
The SWC plugin runs inside a WASM runtime which always uses Unix path semantics, but on Windows the host passes paths with backslashes (e.g. `C:\Users\user\project\src\gql`). `PathBuf` in WASM does not treat backslashes as separators, so path computation was silently wrong and produced invalid relative import paths. Fix by: 1. Normalizing all input paths (`cwd`, `filename`, `artifactDirectory`) by replacing backslashes with forward slashes before any `PathBuf` usage. 2. Replacing the `Path::is_absolute()` check with an explicit helper that also recognises Windows drive-letter paths (`C:/...`) as absolute, since `Path::is_absolute()` returns false for those under WASM/Unix semantics. Closes #524 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Donny/강동윤 <kdy1@users.noreply.github.com>
1 parent bdaf2f5 commit 903317a

3 files changed

Lines changed: 72 additions & 12 deletions

File tree

contrib/graphql-codegen-client-preset/src/lib.rs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::path::{Path, PathBuf};
1+
use std::path::PathBuf;
22

33
use graphql_parser::query::parse_query;
44
use pathdiff::diff_paths;
@@ -88,6 +88,27 @@ fn apply_naming_convention(s: &str, naming_convention: &str) -> String {
8888
}
8989
}
9090

91+
/// Returns true if `path` is an absolute path on either Unix (`/foo`) or
92+
/// Windows (`C:/foo`, `C:\foo`). We cannot rely on `Path::is_absolute()`
93+
/// because when the plugin runs inside a WASM runtime it always uses Unix
94+
/// path semantics, so Windows absolute paths would be misclassified as
95+
/// relative.
96+
fn is_absolute_path(path: &str) -> bool {
97+
// Unix absolute path
98+
if path.starts_with('/') {
99+
return true;
100+
}
101+
// Windows absolute path: starts with a drive letter followed by ':' and a
102+
// slash/backslash, e.g. "C:/..." or "C:\..."
103+
let mut chars = path.chars();
104+
if let (Some(drive), Some(':'), Some(sep)) = (chars.next(), chars.next(), chars.next()) {
105+
if drive.is_ascii_alphabetic() && (sep == '/' || sep == '\\') {
106+
return true;
107+
}
108+
}
109+
false
110+
}
111+
91112
#[cfg(test)]
92113
mod tests;
93114

@@ -118,21 +139,30 @@ impl GraphQLVisitor {
118139
}
119140

120141
fn get_relative_import_path(&self, path_end: &str) -> String {
142+
// Normalize Windows-style backslashes to forward slashes so that PathBuf
143+
// works correctly when the plugin runs in a WASM context (which always uses
144+
// Unix path semantics) but receives Windows paths from the host.
145+
let cwd = self.options.cwd.replace('\\', "/");
146+
let filename = self.options.filename.replace('\\', "/");
147+
let artifact_directory = self.options.artifact_directory.replace('\\', "/");
148+
121149
// using PathBuf to add the relative path to the artifact directory
122-
let mut file_full_path = PathBuf::from(&self.options.cwd);
123-
file_full_path.push(&self.options.filename);
150+
let mut file_full_path = PathBuf::from(&cwd);
151+
file_full_path.push(&filename);
124152
let file_s_dirname = file_full_path.parent().unwrap();
125153

126154
// The resolved artifact directory as seen from the current running SWC plugin
127-
// working directory
128-
let resolved_artifact_directory =
129-
if Path::new(&self.options.artifact_directory).is_relative() {
130-
let mut cwd = PathBuf::from(&self.options.cwd);
131-
cwd.push(&self.options.artifact_directory);
132-
cwd.to_string_lossy().to_string()
133-
} else {
134-
self.options.artifact_directory.to_string()
135-
};
155+
// working directory.
156+
// We check for absolute paths ourselves instead of relying on
157+
// Path::is_absolute(), because on WASM (Unix semantics) a Windows absolute
158+
// path such as "C:/project/src" would be considered relative.
159+
let resolved_artifact_directory = if is_absolute_path(&artifact_directory) {
160+
artifact_directory.to_string()
161+
} else {
162+
let mut cwd_path = PathBuf::from(&cwd);
163+
cwd_path.push(&artifact_directory);
164+
cwd_path.to_string_lossy().to_string()
165+
};
136166

137167
let mut relative = diff_paths(resolved_artifact_directory, file_s_dirname).unwrap();
138168

contrib/graphql-codegen-client-preset/src/tests.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,33 @@ fn import_files_from_other_directory(input_path: PathBuf) {
9292
);
9393
}
9494

95+
fn get_windows_path_visitor() -> GraphQLVisitor {
96+
// Simulate a Windows environment where cwd and artifact_directory use backslashes.
97+
// The WASM plugin runs with Unix path semantics, so it must normalize these.
98+
GraphQLVisitor::new(GraphQLCodegenOptions {
99+
filename: "src\\App.tsx".to_string(),
100+
cwd: "C:\\Users\\user\\project".to_string(),
101+
artifact_directory: "C:\\Users\\user\\project\\src\\gql".to_string(),
102+
gql_tag_name: "gql".to_string(),
103+
naming_convention: "change-case-all#pascalCase".to_string(),
104+
})
105+
}
106+
107+
test!(
108+
Default::default(),
109+
|_| visit_mut_pass(get_windows_path_visitor()),
110+
windows_absolute_paths_produce_valid_relative_import,
111+
r#"import gql from "gql-tag";
112+
113+
const GetUser = gql(`
114+
query GetUser {
115+
user {
116+
id
117+
}
118+
}
119+
`);"#
120+
);
121+
95122
test!(
96123
Default::default(),
97124
|_| visit_mut_pass(get_test_code_visitor()),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { GetUserDocument } from "./gql/graphql";
2+
import gql from "gql-tag";
3+
const GetUser = GetUserDocument;

0 commit comments

Comments
 (0)