1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! Import processing utilities.
//!
//! This module handles processing import statements, including
//! removing TypeScript type-only imports and extracting identifiers.
use oxc_allocator::Allocator;
use oxc_ast::ast::{ImportDeclarationSpecifier, Statement};
use oxc_parser::Parser;
use oxc_span::SourceType;
use vize_carton::ToCompactString;
/// Process import statement to remove TypeScript type-only imports using OXC
/// Returns None if the entire import should be removed, Some(processed) otherwise
pub fn process_import_for_types(import: &str) -> Option<vize_carton::String> {
let import = import.trim();
// Parse the import statement with OXC
let allocator = Allocator::default();
let source_type = SourceType::ts();
let parser = Parser::new(&allocator, import, source_type);
let result = parser.parse();
if result.errors.is_empty() {
for stmt in &result.program.body {
if let Statement::ImportDeclaration(decl) = stmt {
// Skip type-only imports: import type { ... } from '...'
if decl.import_kind.is_type() {
return None;
}
// Check if there are any specifiers
if let Some(specifiers) = &decl.specifiers {
// Filter out type-only specifiers
let value_specifiers: Vec<&ImportDeclarationSpecifier> = specifiers
.iter()
.filter(|spec| match spec {
ImportDeclarationSpecifier::ImportSpecifier(s) => {
!s.import_kind.is_type()
}
_ => true,
})
.collect();
if value_specifiers.is_empty() {
// All specifiers were type imports
return None;
}
if value_specifiers.len() != specifiers.len() {
// Some specifiers were filtered out, rebuild the import
let source = decl.source.value.as_str();
// Separate default/namespace imports from named imports
let mut default_part: Option<vize_carton::String> = None;
let mut named_parts: Vec<vize_carton::String> = Vec::new();
for spec in &value_specifiers {
match spec {
ImportDeclarationSpecifier::ImportSpecifier(s) => {
let imported = s.imported.name().as_str();
let local = s.local.name.as_str();
if imported == local {
named_parts.push(imported.to_compact_string());
} else {
let mut name = vize_carton::String::with_capacity(
imported.len() + local.len() + 4,
);
name.push_str(imported);
name.push_str(" as ");
name.push_str(local);
named_parts.push(name);
}
}
ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
default_part = Some(s.local.name.to_compact_string());
}
ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
let local = s.local.name.as_str();
let mut name =
vize_carton::String::with_capacity(local.len() + 5);
name.push_str("* as ");
name.push_str(local);
default_part = Some(name);
}
}
}
let mut new_import = vize_carton::String::with_capacity(source.len() + 30);
new_import.push_str("import ");
if let Some(ref def) = default_part {
new_import.push_str(def);
if !named_parts.is_empty() {
new_import.push_str(", ");
}
}
if !named_parts.is_empty() {
new_import.push_str("{ ");
// [CompactString].join() returns std String, convert back
let joined = named_parts
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ");
new_import.push_str(&joined);
new_import.push_str(" }");
}
new_import.push_str(" from '");
new_import.push_str(source);
new_import.push_str("'\n");
return Some(new_import);
}
}
}
}
}
// Regular import or parse failed, return as-is
let mut result = import.to_compact_string();
result.push('\n');
Some(result)
}
/// Extract all identifiers from an import statement (including default imports)
pub fn extract_import_identifiers(import: &str) -> Vec<vize_carton::String> {
let import = import.trim();
let mut identifiers = Vec::new();
// Parse the import statement with OXC
let allocator = Allocator::default();
let source_type = SourceType::ts();
let parser = Parser::new(&allocator, import, source_type);
let result = parser.parse();
if result.errors.is_empty() {
for stmt in &result.program.body {
if let Statement::ImportDeclaration(decl) = stmt {
// Skip type-only imports
if decl.import_kind.is_type() {
continue;
}
if let Some(specifiers) = &decl.specifiers {
for spec in specifiers {
match spec {
ImportDeclarationSpecifier::ImportSpecifier(s) => {
// Skip type-only specifiers
if !s.import_kind.is_type() {
identifiers.push(s.local.name.to_compact_string());
}
}
ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
identifiers.push(s.local.name.to_compact_string());
}
ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
identifiers.push(s.local.name.to_compact_string());
}
}
}
}
}
}
}
identifiers
}
#[cfg(test)]
mod tests {
use super::process_import_for_types;
#[test]
fn test_default_import_with_type_named_import() {
// `import Foo, { type Bar }` should become `import Foo from '...'`
// NOT `import { Foo } from '...'`
let input = "import AtriumSegmentedTabs, { type AtriumSegmentedTabConfig } from '../AtriumSegmentedTabs/AtriumSegmentedTabs.vue'";
let result = process_import_for_types(input);
let output = result.expect("should produce an import");
assert!(
output.starts_with("import AtriumSegmentedTabs from"),
"Default import should be preserved as default import, not named. Got: {}",
output
);
assert!(
!output.contains("{ AtriumSegmentedTabs }"),
"Default import should NOT be inside braces. Got: {}",
output
);
}
#[test]
fn test_default_import_with_mixed_named_imports() {
// `import Foo, { type Bar, baz }` should become `import Foo, { baz } from '...'`
let input = "import Foo, { type Bar, baz } from 'module'";
let result = process_import_for_types(input);
let output = result.expect("should produce an import");
assert!(
output.contains("import Foo, { baz }"),
"Should have default + named imports. Got: {}",
output
);
}
#[test]
fn test_type_only_import_returns_none() {
let input = "import type { Foo } from 'bar'";
let result = process_import_for_types(input);
assert!(result.is_none(), "Type-only import should return None");
}
#[test]
fn test_all_named_type_imports_returns_none() {
let input = "import { type Foo, type Bar } from 'baz'";
let result = process_import_for_types(input);
assert!(
result.is_none(),
"All type-only named imports should return None"
);
}
#[test]
fn test_normal_import_unchanged() {
let input = "import { foo, bar } from 'module'";
let result = process_import_for_types(input);
assert!(result.is_some(), "Normal import should be returned as-is");
}
}