@soda-gql/swc-transformer 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/dist/index.cjs +915 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +91 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +913 -0
- package/dist/index.mjs.map +1 -0
- package/dist/native.cjs +254 -0
- package/dist/native.cjs.map +1 -0
- package/dist/native.d.cts +46 -0
- package/dist/native.d.cts.map +1 -0
- package/dist/native.d.mts +46 -0
- package/dist/native.d.mts.map +1 -0
- package/dist/native.mjs +256 -0
- package/dist/native.mjs.map +1 -0
- package/package.json +81 -0
- package/src/index.ts +290 -0
- package/src/lib.rs +87 -0
- package/src/native/index.d.ts +42 -0
- package/src/native/index.js +316 -0
- package/src/native/swc-transformer.linux-x64-gnu.node +0 -0
- package/src/transform/analysis.rs +240 -0
- package/src/transform/imports.rs +285 -0
- package/src/transform/metadata.rs +371 -0
- package/src/transform/mod.rs +7 -0
- package/src/transform/runtime.rs +197 -0
- package/src/transform/transformer.rs +438 -0
- package/src/types/artifact.rs +107 -0
- package/src/types/config.rs +72 -0
- package/src/types/error.rs +132 -0
- package/src/types/mod.rs +12 -0
package/src/lib.rs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//! SWC-based transformer for soda-gql GraphQL code generation.
|
|
2
|
+
//!
|
|
3
|
+
//! This crate provides a native Node.js module using napi-rs that transforms
|
|
4
|
+
//! `gql.default()` calls into `gqlRuntime.*` calls at build time.
|
|
5
|
+
|
|
6
|
+
mod transform;
|
|
7
|
+
mod types;
|
|
8
|
+
|
|
9
|
+
use napi::bindgen_prelude::*;
|
|
10
|
+
use napi_derive::napi;
|
|
11
|
+
use types::config::{TransformConfig, TransformInput, TransformInputRef};
|
|
12
|
+
use types::BuilderArtifact;
|
|
13
|
+
|
|
14
|
+
/// Transform a single source file.
|
|
15
|
+
///
|
|
16
|
+
/// # Arguments
|
|
17
|
+
/// * `input_json` - JSON-serialized TransformInput containing source code, file path, artifact, and config
|
|
18
|
+
///
|
|
19
|
+
/// # Returns
|
|
20
|
+
/// JSON-serialized TransformResult containing the transformed code
|
|
21
|
+
#[napi]
|
|
22
|
+
pub fn transform(input_json: String) -> Result<String> {
|
|
23
|
+
let input: TransformInput = serde_json::from_str(&input_json)
|
|
24
|
+
.map_err(|e| Error::from_reason(format!("Failed to parse input: {}", e)))?;
|
|
25
|
+
|
|
26
|
+
let result = transform::transformer::transform_source(&input)
|
|
27
|
+
.map_err(|e| Error::from_reason(e))?;
|
|
28
|
+
|
|
29
|
+
serde_json::to_string(&result)
|
|
30
|
+
.map_err(|e| Error::from_reason(format!("Failed to serialize result: {}", e)))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Stateful transformer that caches artifact and config for multiple file transformations.
|
|
34
|
+
///
|
|
35
|
+
/// The artifact is parsed once in the constructor and reused for all subsequent
|
|
36
|
+
/// transform calls, avoiding repeated JSON parsing overhead.
|
|
37
|
+
#[napi]
|
|
38
|
+
pub struct SwcTransformer {
|
|
39
|
+
/// Pre-parsed BuilderArtifact (parsed once in constructor)
|
|
40
|
+
artifact: BuilderArtifact,
|
|
41
|
+
config: TransformConfig,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[napi]
|
|
45
|
+
impl SwcTransformer {
|
|
46
|
+
/// Create a new transformer instance.
|
|
47
|
+
///
|
|
48
|
+
/// # Arguments
|
|
49
|
+
/// * `artifact_json` - JSON-serialized BuilderArtifact
|
|
50
|
+
/// * `config_json` - JSON-serialized TransformConfig
|
|
51
|
+
#[napi(constructor)]
|
|
52
|
+
pub fn new(artifact_json: String, config_json: String) -> Result<Self> {
|
|
53
|
+
let config: TransformConfig = serde_json::from_str(&config_json)
|
|
54
|
+
.map_err(|e| Error::from_reason(format!("Failed to parse config: {}", e)))?;
|
|
55
|
+
|
|
56
|
+
// Parse artifact once in constructor to avoid repeated parsing
|
|
57
|
+
let artifact: BuilderArtifact = serde_json::from_str(&artifact_json)
|
|
58
|
+
.map_err(|e| Error::from_reason(format!("Failed to parse artifact: {}", e)))?;
|
|
59
|
+
|
|
60
|
+
Ok(SwcTransformer { artifact, config })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Transform a single source file.
|
|
64
|
+
///
|
|
65
|
+
/// # Arguments
|
|
66
|
+
/// * `source_code` - The source code to transform
|
|
67
|
+
/// * `source_path` - The file path of the source
|
|
68
|
+
///
|
|
69
|
+
/// # Returns
|
|
70
|
+
/// JSON-serialized TransformResult
|
|
71
|
+
#[napi]
|
|
72
|
+
pub fn transform(&self, source_code: String, source_path: String) -> Result<String> {
|
|
73
|
+
// Use pre-parsed artifact reference instead of re-parsing JSON
|
|
74
|
+
let input = TransformInputRef {
|
|
75
|
+
source_code,
|
|
76
|
+
source_path,
|
|
77
|
+
artifact: &self.artifact,
|
|
78
|
+
config: self.config.clone(),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let result = transform::transformer::transform_source_ref(&input)
|
|
82
|
+
.map_err(|e| Error::from_reason(e))?;
|
|
83
|
+
|
|
84
|
+
serde_json::to_string(&result)
|
|
85
|
+
.map_err(|e| Error::from_reason(format!("Failed to serialize result: {}", e)))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/* auto-generated by NAPI-RS */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transform a single source file.
|
|
8
|
+
*
|
|
9
|
+
* # Arguments
|
|
10
|
+
* * `input_json` - JSON-serialized TransformInput containing source code, file path, artifact, and config
|
|
11
|
+
*
|
|
12
|
+
* # Returns
|
|
13
|
+
* JSON-serialized TransformResult containing the transformed code
|
|
14
|
+
*/
|
|
15
|
+
export declare function transform(inputJson: string): string
|
|
16
|
+
/**
|
|
17
|
+
* Stateful transformer that caches artifact and config for multiple file transformations.
|
|
18
|
+
*
|
|
19
|
+
* The artifact is parsed once in the constructor and reused for all subsequent
|
|
20
|
+
* transform calls, avoiding repeated JSON parsing overhead.
|
|
21
|
+
*/
|
|
22
|
+
export declare class SwcTransformer {
|
|
23
|
+
/**
|
|
24
|
+
* Create a new transformer instance.
|
|
25
|
+
*
|
|
26
|
+
* # Arguments
|
|
27
|
+
* * `artifact_json` - JSON-serialized BuilderArtifact
|
|
28
|
+
* * `config_json` - JSON-serialized TransformConfig
|
|
29
|
+
*/
|
|
30
|
+
constructor(artifactJson: string, configJson: string)
|
|
31
|
+
/**
|
|
32
|
+
* Transform a single source file.
|
|
33
|
+
*
|
|
34
|
+
* # Arguments
|
|
35
|
+
* * `source_code` - The source code to transform
|
|
36
|
+
* * `source_path` - The file path of the source
|
|
37
|
+
*
|
|
38
|
+
* # Returns
|
|
39
|
+
* JSON-serialized TransformResult
|
|
40
|
+
*/
|
|
41
|
+
transform(sourceCode: string, sourcePath: string): string
|
|
42
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* prettier-ignore */
|
|
4
|
+
|
|
5
|
+
/* auto-generated by NAPI-RS */
|
|
6
|
+
|
|
7
|
+
const { existsSync, readFileSync } = require('fs')
|
|
8
|
+
const { join } = require('path')
|
|
9
|
+
|
|
10
|
+
const { platform, arch } = process
|
|
11
|
+
|
|
12
|
+
let nativeBinding = null
|
|
13
|
+
let localFileExisted = false
|
|
14
|
+
let loadError = null
|
|
15
|
+
|
|
16
|
+
function isMusl() {
|
|
17
|
+
// For Node 10
|
|
18
|
+
if (!process.report || typeof process.report.getReport !== 'function') {
|
|
19
|
+
try {
|
|
20
|
+
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
|
21
|
+
return readFileSync(lddPath, 'utf8').includes('musl')
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
const { glibcVersionRuntime } = process.report.getReport().header
|
|
27
|
+
return !glibcVersionRuntime
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (platform) {
|
|
32
|
+
case 'android':
|
|
33
|
+
switch (arch) {
|
|
34
|
+
case 'arm64':
|
|
35
|
+
localFileExisted = existsSync(join(__dirname, 'swc-transformer.android-arm64.node'))
|
|
36
|
+
try {
|
|
37
|
+
if (localFileExisted) {
|
|
38
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'android-arm64.node')
|
|
39
|
+
} else {
|
|
40
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'android-arm64')
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
loadError = e
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
case 'arm':
|
|
47
|
+
localFileExisted = existsSync(join(__dirname, 'swc-transformer.android-arm-eabi.node'))
|
|
48
|
+
try {
|
|
49
|
+
if (localFileExisted) {
|
|
50
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'android-arm-eabi.node')
|
|
51
|
+
} else {
|
|
52
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'android-arm-eabi')
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
loadError = e
|
|
56
|
+
}
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
throw new Error(`Unsupported architecture on Android ${arch}`)
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
case 'win32':
|
|
63
|
+
switch (arch) {
|
|
64
|
+
case 'x64':
|
|
65
|
+
localFileExisted = existsSync(
|
|
66
|
+
join(__dirname, 'swc-transformer.win32-x64-msvc.node')
|
|
67
|
+
)
|
|
68
|
+
try {
|
|
69
|
+
if (localFileExisted) {
|
|
70
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'win32-x64-msvc.node')
|
|
71
|
+
} else {
|
|
72
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'win32-x64-msvc')
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
loadError = e
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
case 'ia32':
|
|
79
|
+
localFileExisted = existsSync(
|
|
80
|
+
join(__dirname, 'swc-transformer.win32-ia32-msvc.node')
|
|
81
|
+
)
|
|
82
|
+
try {
|
|
83
|
+
if (localFileExisted) {
|
|
84
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'win32-ia32-msvc.node')
|
|
85
|
+
} else {
|
|
86
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'win32-ia32-msvc')
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
loadError = e
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
case 'arm64':
|
|
93
|
+
localFileExisted = existsSync(
|
|
94
|
+
join(__dirname, 'swc-transformer.win32-arm64-msvc.node')
|
|
95
|
+
)
|
|
96
|
+
try {
|
|
97
|
+
if (localFileExisted) {
|
|
98
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'win32-arm64-msvc.node')
|
|
99
|
+
} else {
|
|
100
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'win32-arm64-msvc')
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
loadError = e
|
|
104
|
+
}
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
|
108
|
+
}
|
|
109
|
+
break
|
|
110
|
+
case 'darwin':
|
|
111
|
+
localFileExisted = existsSync(join(__dirname, 'swc-transformer.darwin-universal.node'))
|
|
112
|
+
try {
|
|
113
|
+
if (localFileExisted) {
|
|
114
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'darwin-universal.node')
|
|
115
|
+
} else {
|
|
116
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'darwin-universal')
|
|
117
|
+
}
|
|
118
|
+
break
|
|
119
|
+
} catch {}
|
|
120
|
+
switch (arch) {
|
|
121
|
+
case 'x64':
|
|
122
|
+
localFileExisted = existsSync(join(__dirname, 'swc-transformer.darwin-x64.node'))
|
|
123
|
+
try {
|
|
124
|
+
if (localFileExisted) {
|
|
125
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'darwin-x64.node')
|
|
126
|
+
} else {
|
|
127
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'darwin-x64')
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
loadError = e
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
case 'arm64':
|
|
134
|
+
localFileExisted = existsSync(
|
|
135
|
+
join(__dirname, 'swc-transformer.darwin-arm64.node')
|
|
136
|
+
)
|
|
137
|
+
try {
|
|
138
|
+
if (localFileExisted) {
|
|
139
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'darwin-arm64.node')
|
|
140
|
+
} else {
|
|
141
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'darwin-arm64')
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
loadError = e
|
|
145
|
+
}
|
|
146
|
+
break
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
case 'freebsd':
|
|
152
|
+
if (arch !== 'x64') {
|
|
153
|
+
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
|
154
|
+
}
|
|
155
|
+
localFileExisted = existsSync(join(__dirname, 'swc-transformer.freebsd-x64.node'))
|
|
156
|
+
try {
|
|
157
|
+
if (localFileExisted) {
|
|
158
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'freebsd-x64.node')
|
|
159
|
+
} else {
|
|
160
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'freebsd-x64')
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
loadError = e
|
|
164
|
+
}
|
|
165
|
+
break
|
|
166
|
+
case 'linux':
|
|
167
|
+
switch (arch) {
|
|
168
|
+
case 'x64':
|
|
169
|
+
if (isMusl()) {
|
|
170
|
+
localFileExisted = existsSync(
|
|
171
|
+
join(__dirname, 'swc-transformer.linux-x64-musl.node')
|
|
172
|
+
)
|
|
173
|
+
try {
|
|
174
|
+
if (localFileExisted) {
|
|
175
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-x64-musl.node')
|
|
176
|
+
} else {
|
|
177
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-x64-musl')
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
loadError = e
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
localFileExisted = existsSync(
|
|
184
|
+
join(__dirname, 'swc-transformer.linux-x64-gnu.node')
|
|
185
|
+
)
|
|
186
|
+
try {
|
|
187
|
+
if (localFileExisted) {
|
|
188
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-x64-gnu.node')
|
|
189
|
+
} else {
|
|
190
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-x64-gnu')
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
loadError = e
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
case 'arm64':
|
|
198
|
+
if (isMusl()) {
|
|
199
|
+
localFileExisted = existsSync(
|
|
200
|
+
join(__dirname, 'swc-transformer.linux-arm64-musl.node')
|
|
201
|
+
)
|
|
202
|
+
try {
|
|
203
|
+
if (localFileExisted) {
|
|
204
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-arm64-musl.node')
|
|
205
|
+
} else {
|
|
206
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-arm64-musl')
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
loadError = e
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
localFileExisted = existsSync(
|
|
213
|
+
join(__dirname, 'swc-transformer.linux-arm64-gnu.node')
|
|
214
|
+
)
|
|
215
|
+
try {
|
|
216
|
+
if (localFileExisted) {
|
|
217
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-arm64-gnu.node')
|
|
218
|
+
} else {
|
|
219
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-arm64-gnu')
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
loadError = e
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break
|
|
226
|
+
case 'arm':
|
|
227
|
+
if (isMusl()) {
|
|
228
|
+
localFileExisted = existsSync(
|
|
229
|
+
join(__dirname, 'swc-transformer.linux-arm-musleabihf.node')
|
|
230
|
+
)
|
|
231
|
+
try {
|
|
232
|
+
if (localFileExisted) {
|
|
233
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-arm-musleabihf.node')
|
|
234
|
+
} else {
|
|
235
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-arm-musleabihf')
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
loadError = e
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
localFileExisted = existsSync(
|
|
242
|
+
join(__dirname, 'swc-transformer.linux-arm-gnueabihf.node')
|
|
243
|
+
)
|
|
244
|
+
try {
|
|
245
|
+
if (localFileExisted) {
|
|
246
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-arm-gnueabihf.node')
|
|
247
|
+
} else {
|
|
248
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-arm-gnueabihf')
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
loadError = e
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break
|
|
255
|
+
case 'riscv64':
|
|
256
|
+
if (isMusl()) {
|
|
257
|
+
localFileExisted = existsSync(
|
|
258
|
+
join(__dirname, 'swc-transformer.linux-riscv64-musl.node')
|
|
259
|
+
)
|
|
260
|
+
try {
|
|
261
|
+
if (localFileExisted) {
|
|
262
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-riscv64-musl.node')
|
|
263
|
+
} else {
|
|
264
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-riscv64-musl')
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
loadError = e
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
localFileExisted = existsSync(
|
|
271
|
+
join(__dirname, 'swc-transformer.linux-riscv64-gnu.node')
|
|
272
|
+
)
|
|
273
|
+
try {
|
|
274
|
+
if (localFileExisted) {
|
|
275
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-riscv64-gnu.node')
|
|
276
|
+
} else {
|
|
277
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-riscv64-gnu')
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
loadError = e
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
break
|
|
284
|
+
case 's390x':
|
|
285
|
+
localFileExisted = existsSync(
|
|
286
|
+
join(__dirname, 'swc-transformer.linux-s390x-gnu.node')
|
|
287
|
+
)
|
|
288
|
+
try {
|
|
289
|
+
if (localFileExisted) {
|
|
290
|
+
nativeBinding = require('./swc-transformer.'.slice(0) + 'linux-s390x-gnu.node')
|
|
291
|
+
} else {
|
|
292
|
+
nativeBinding = require('@soda-gql/swc-transformer-'.slice(0) + 'linux-s390x-gnu')
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
loadError = e
|
|
296
|
+
}
|
|
297
|
+
break
|
|
298
|
+
default:
|
|
299
|
+
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
|
300
|
+
}
|
|
301
|
+
break
|
|
302
|
+
default:
|
|
303
|
+
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!nativeBinding) {
|
|
307
|
+
if (loadError) {
|
|
308
|
+
throw loadError
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Failed to load native binding`)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { transform, SwcTransformer } = nativeBinding
|
|
314
|
+
|
|
315
|
+
module.exports.transform = transform
|
|
316
|
+
module.exports.SwcTransformer = SwcTransformer
|
|
Binary file
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
//! GQL call analysis module.
|
|
2
|
+
//!
|
|
3
|
+
//! This module is responsible for:
|
|
4
|
+
//! - Detecting `gql.default()` call patterns
|
|
5
|
+
//! - Extracting the inner builder call
|
|
6
|
+
//! - Mapping calls to their corresponding artifacts
|
|
7
|
+
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
use swc_core::common::Span;
|
|
10
|
+
use swc_core::ecma::ast::*;
|
|
11
|
+
use swc_core::ecma::visit::{Visit, VisitWith};
|
|
12
|
+
|
|
13
|
+
use crate::types::{BuilderArtifact, BuilderArtifactElement, CanonicalId, PluginError};
|
|
14
|
+
|
|
15
|
+
use super::metadata::MetadataMap;
|
|
16
|
+
|
|
17
|
+
/// Information about a detected GQL call that needs to be transformed.
|
|
18
|
+
#[allow(dead_code)]
|
|
19
|
+
#[derive(Debug, Clone)]
|
|
20
|
+
pub struct GqlCallInfo {
|
|
21
|
+
/// The canonical ID for this call
|
|
22
|
+
pub canonical_id: CanonicalId,
|
|
23
|
+
/// The artifact element from the builder
|
|
24
|
+
pub artifact: BuilderArtifactElement,
|
|
25
|
+
/// Span of the original gql.default() call
|
|
26
|
+
pub call_span: Span,
|
|
27
|
+
/// The inner builder call (e.g., fragment.User(), query.slice())
|
|
28
|
+
pub builder_call_args: Vec<ExprOrSpread>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Replacement information for a GQL call.
|
|
32
|
+
#[derive(Debug)]
|
|
33
|
+
pub struct GqlReplacement {
|
|
34
|
+
#[allow(dead_code)]
|
|
35
|
+
pub canonical_id: CanonicalId,
|
|
36
|
+
pub artifact: BuilderArtifactElement,
|
|
37
|
+
pub builder_args: Vec<ExprOrSpread>,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Finds GQL calls in the AST and prepares them for transformation.
|
|
41
|
+
pub struct GqlCallFinder<'a> {
|
|
42
|
+
artifact: &'a BuilderArtifact,
|
|
43
|
+
metadata: &'a MetadataMap,
|
|
44
|
+
source_path: &'a str,
|
|
45
|
+
/// Map from call span to replacement info
|
|
46
|
+
replacements: HashMap<Span, GqlReplacement>,
|
|
47
|
+
has_transforms: bool,
|
|
48
|
+
/// Errors encountered during analysis
|
|
49
|
+
errors: Vec<PluginError>,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl<'a> GqlCallFinder<'a> {
|
|
53
|
+
pub fn new(artifact: &'a BuilderArtifact, metadata: &'a MetadataMap, source_path: &'a str) -> Self {
|
|
54
|
+
Self {
|
|
55
|
+
artifact,
|
|
56
|
+
metadata,
|
|
57
|
+
source_path,
|
|
58
|
+
replacements: HashMap::new(),
|
|
59
|
+
has_transforms: false,
|
|
60
|
+
errors: Vec::new(),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Check if any transformations were found.
|
|
65
|
+
pub fn has_transformations(&self) -> bool {
|
|
66
|
+
self.has_transforms
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Get the replacement for a call expression if it should be transformed.
|
|
70
|
+
pub fn get_replacement(&self, call: &CallExpr) -> Option<&GqlReplacement> {
|
|
71
|
+
self.replacements.get(&call.span)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Take collected errors.
|
|
75
|
+
pub fn take_errors(&mut self) -> Vec<PluginError> {
|
|
76
|
+
std::mem::take(&mut self.errors)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Process a potential GQL call expression.
|
|
80
|
+
fn process_call(&mut self, call: &CallExpr) {
|
|
81
|
+
// Check if this is a gql.default() or gql.* call
|
|
82
|
+
if let Some(builder_call) = find_gql_builder_call(call) {
|
|
83
|
+
// Get metadata for this call
|
|
84
|
+
if let Some(meta) = self.metadata.get(&call.span) {
|
|
85
|
+
let canonical_id = resolve_canonical_id(self.source_path, &meta.ast_path);
|
|
86
|
+
|
|
87
|
+
// Look up the artifact
|
|
88
|
+
if let Some(artifact) = self.artifact.get(&canonical_id) {
|
|
89
|
+
self.replacements.insert(
|
|
90
|
+
call.span,
|
|
91
|
+
GqlReplacement {
|
|
92
|
+
canonical_id,
|
|
93
|
+
artifact: artifact.clone(),
|
|
94
|
+
builder_args: builder_call.args.clone(),
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
self.has_transforms = true;
|
|
98
|
+
} else {
|
|
99
|
+
let error = PluginError::artifact_not_found(self.source_path, &canonical_id);
|
|
100
|
+
eprintln!("[swc-transformer] {}", error.format());
|
|
101
|
+
self.errors.push(error);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
let error = PluginError::metadata_not_found(self.source_path);
|
|
105
|
+
eprintln!("[swc-transformer] {}", error.format());
|
|
106
|
+
self.errors.push(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
impl Visit for GqlCallFinder<'_> {
|
|
113
|
+
fn visit_call_expr(&mut self, call: &CallExpr) {
|
|
114
|
+
// First check this call
|
|
115
|
+
self.process_call(call);
|
|
116
|
+
|
|
117
|
+
// Then visit children
|
|
118
|
+
call.visit_children_with(self);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Find the inner builder call from a gql.default() call.
|
|
123
|
+
///
|
|
124
|
+
/// Supports both arrow functions and function expressions:
|
|
125
|
+
/// - `gql.default(({ fragment }) => fragment.User(...))`
|
|
126
|
+
/// - `gql.default(function({ fragment }) { return fragment.User(...); })`
|
|
127
|
+
///
|
|
128
|
+
/// Returns: The inner builder call expression (e.g., `fragment.User(...)`)
|
|
129
|
+
fn find_gql_builder_call(call: &CallExpr) -> Option<&CallExpr> {
|
|
130
|
+
// Check if callee is gql.* pattern
|
|
131
|
+
if !is_gql_member_expression(&call.callee) {
|
|
132
|
+
return None;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Should have exactly one argument
|
|
136
|
+
if call.args.len() != 1 {
|
|
137
|
+
return None;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// The argument should be an arrow function or function expression
|
|
141
|
+
let arg = &call.args[0];
|
|
142
|
+
if arg.spread.is_some() {
|
|
143
|
+
return None;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
match &*arg.expr {
|
|
147
|
+
Expr::Arrow(arrow) => extract_builder_call_from_arrow(arrow),
|
|
148
|
+
Expr::Fn(fn_expr) => extract_builder_call_from_fn(fn_expr),
|
|
149
|
+
_ => None,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Check if the callee is a gql.* member expression.
|
|
154
|
+
fn is_gql_member_expression(callee: &Callee) -> bool {
|
|
155
|
+
match callee {
|
|
156
|
+
Callee::Expr(expr) => {
|
|
157
|
+
if let Expr::Member(member) = &**expr {
|
|
158
|
+
is_gql_reference(&member.obj)
|
|
159
|
+
} else {
|
|
160
|
+
false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
_ => false,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Recursively check if an expression is a reference to `gql`.
|
|
168
|
+
fn is_gql_reference(expr: &Expr) -> bool {
|
|
169
|
+
match expr {
|
|
170
|
+
Expr::Ident(ident) => atom_eq(&ident.sym, "gql"),
|
|
171
|
+
Expr::Member(member) => {
|
|
172
|
+
// Check if property is "gql"
|
|
173
|
+
if let MemberProp::Ident(ident) = &member.prop {
|
|
174
|
+
if atom_eq(&ident.sym, "gql") {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Recursively check the object
|
|
179
|
+
is_gql_reference(&member.obj)
|
|
180
|
+
}
|
|
181
|
+
_ => false,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Helper to compare an atom with a string.
|
|
186
|
+
fn atom_eq<T: AsRef<str>>(atom: &T, s: &str) -> bool {
|
|
187
|
+
atom.as_ref() == s
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Extract the builder call from an arrow function body.
|
|
191
|
+
fn extract_builder_call_from_arrow(arrow: &ArrowExpr) -> Option<&CallExpr> {
|
|
192
|
+
match &*arrow.body {
|
|
193
|
+
BlockStmtOrExpr::Expr(expr) => {
|
|
194
|
+
if let Expr::Call(call) = &**expr {
|
|
195
|
+
Some(call)
|
|
196
|
+
} else {
|
|
197
|
+
None
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
BlockStmtOrExpr::BlockStmt(block) => extract_call_from_block(block),
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// Extract the builder call from a function expression body.
|
|
205
|
+
fn extract_builder_call_from_fn(fn_expr: &FnExpr) -> Option<&CallExpr> {
|
|
206
|
+
// Function body is Option<BlockStmt>
|
|
207
|
+
fn_expr
|
|
208
|
+
.function
|
|
209
|
+
.body
|
|
210
|
+
.as_ref()
|
|
211
|
+
.and_then(|block| extract_call_from_block(block))
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Extract a call expression from a block statement (shared by arrow and fn).
|
|
215
|
+
fn extract_call_from_block(block: &BlockStmt) -> Option<&CallExpr> {
|
|
216
|
+
// Look for a return statement with a call expression
|
|
217
|
+
for stmt in &block.stmts {
|
|
218
|
+
if let Stmt::Return(ret) = stmt {
|
|
219
|
+
if let Some(arg) = &ret.arg {
|
|
220
|
+
if let Expr::Call(call) = &**arg {
|
|
221
|
+
return Some(call);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
None
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Resolve a canonical ID from file path and AST path.
|
|
230
|
+
/// The canonical ID format is: {normalizedAbsPath}::{astPath}
|
|
231
|
+
///
|
|
232
|
+
/// This mirrors the TypeScript implementation in @soda-gql/common:
|
|
233
|
+
/// - Normalizes path separators to forward slashes (cross-platform)
|
|
234
|
+
/// - Format matches builder artifact keys exactly
|
|
235
|
+
fn resolve_canonical_id(file_path: &str, ast_path: &str) -> CanonicalId {
|
|
236
|
+
// Normalize path separators to forward slashes for cross-platform compatibility
|
|
237
|
+
// This matches the TypeScript normalizePath function behavior
|
|
238
|
+
let normalized_path = file_path.replace('\\', "/");
|
|
239
|
+
format!("{}::{}", normalized_path, ast_path)
|
|
240
|
+
}
|