Skip to content

Commit b62cf86

Browse files
committed
improved typsecriptp parser imports robustness
1 parent ebb071b commit b62cf86

1 file changed

Lines changed: 122 additions & 56 deletions

File tree

codetide/parsers/typescript_parser.py

Lines changed: 122 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -172,85 +172,151 @@ def _process_node(cls, node: Node, code: bytes, codeFile: CodeFileModel):
172172
cls._process_node(child, code, codeFile)
173173

174174
@classmethod
175-
def _process_import_clause_node(cls, node: Node, code: bytes) -> Tuple[List[str], List[Optional[str]]]:
176-
names = []
177-
aliases = []
175+
def _process_import_node(cls, node: Node, code: bytes, codeFile: CodeFileModel):
176+
"""
177+
Process different types of import statements:
178+
1. Side-effect imports: import './App.css'
179+
2. Default imports: import React from 'react'
180+
3. Named imports: import { useState } from 'react'
181+
4. Namespace imports: import * as React from 'react'
182+
5. Type-only imports: import type { User } from './types'
183+
6. Mixed imports: import React, { useState } from 'react'
184+
"""
178185

179-
for child in node.children:
180-
if child.type == "named_imports":
181-
for import_child in child.children:
182-
if import_child.type == "import_specifier":
183-
current_name = None
184-
current_alias = None
185-
next_is_alias = False
186-
187-
for alias_child in import_child.children:
188-
if alias_child.type == "identifier" and not next_is_alias:
189-
current_name = cls._get_content(code, alias_child)
190-
elif alias_child.type == "as":
191-
next_is_alias = True
192-
elif alias_child.type == "identifier" and next_is_alias:
193-
current_alias = cls._get_content(code, alias_child)
194-
next_is_alias = False
195-
196-
if current_name:
197-
names.append(current_name)
198-
aliases.append(current_alias)
199-
200-
elif child.type == "identifier":
201-
name = cls._get_content(code, child)
202-
if name:
203-
names.append(name)
204-
aliases.append(None)
186+
# Debug: Print the full import statement
187+
import_text = cls._get_content(code, node)
188+
print(f"Processing import: {import_text}")
205189

206-
return names, aliases
207-
208-
@classmethod
209-
def _process_import_node(cls, node: Node, code: bytes, codeFile: CodeFileModel):
190+
# Initialize variables
210191
source = None
211192
names = []
212193
aliases = []
213-
next_is_from_import = False
214-
next_is_import = False
194+
is_type_only = False
195+
is_namespace = False
196+
namespace_alias = None
215197

198+
# First pass: identify import type and extract source
216199
for child in node.children:
217-
if child.type == "import":
218-
next_is_import = True
219-
elif child.type == "import_clause" and next_is_import:
220-
names, aliases = cls._process_import_clause_node(child, code)
221-
next_is_import = False
222-
elif next_is_import:
223-
source = cls._get_content(code, child)
224-
next_is_import = False
225-
elif child.type == "from":
226-
next_is_from_import = True
227-
elif child.type == "string" and next_is_from_import:
228-
source = cls._get_content(code, child)
229-
if names and source is None:
230-
source = names[0] if len(names) == 1 else None
231-
if source:
232-
names = []
233-
aliases = []
234-
200+
child_text = cls._get_content(code, child)
201+
print(f" Child: {child.type} = '{child_text}'")
202+
203+
# Check for type-only import
204+
if child.type == "type" or child_text == "type":
205+
is_type_only = True
206+
207+
# Extract source (the string literal)
208+
elif child.type == "string":
209+
source = child_text.strip("'\"") # Remove quotes
210+
211+
# Second pass: process import clause if it exists
212+
import_clause = None
213+
for child in node.children:
214+
if child.type == "import_clause":
215+
import_clause = child
216+
break
217+
218+
if import_clause:
219+
names, aliases, is_namespace, namespace_alias = cls._process_import_clause_node(
220+
import_clause, code
221+
)
222+
223+
# Handle different import types
235224
if source:
236-
if names:
225+
if is_namespace and namespace_alias:
226+
# Namespace import: import * as React from 'react'
227+
importStatement = ImportStatement(
228+
source=source,
229+
name="*", # Indicates namespace import
230+
alias=namespace_alias,
231+
is_type_only=is_type_only
232+
)
233+
codeFile.add_import(importStatement)
234+
cls._generate_unique_import_id(codeFile.imports[-1])
235+
236+
elif names:
237+
# Named imports or default + named imports
237238
for name, alias in zip(names, aliases):
238239
importStatement = ImportStatement(
239240
source=source,
240241
name=name,
241-
alias=alias
242+
alias=alias,
243+
is_type_only=is_type_only
242244
)
243245
codeFile.add_import(importStatement)
244246
cls._generate_unique_import_id(codeFile.imports[-1])
245247
else:
248+
# Side-effect import: import './App.css'
246249
importStatement = ImportStatement(
247250
source=source,
248251
name=None,
249-
alias=None
252+
alias=None,
253+
is_type_only=is_type_only,
254+
is_side_effect=True
250255
)
251256
codeFile.add_import(importStatement)
252257
cls._generate_unique_import_id(codeFile.imports[-1])
253258

259+
@classmethod
260+
def _process_import_clause_node(cls, node: Node, code: bytes) -> Tuple[List[str], List[str], bool, Optional[str]]:
261+
"""
262+
Process import_clause node to extract names and aliases.
263+
Returns: (names, aliases, is_namespace, namespace_alias)
264+
"""
265+
names = []
266+
aliases = []
267+
is_namespace = False
268+
namespace_alias = None
269+
270+
def process_node_recursively(current_node: Node):
271+
nonlocal names, aliases, is_namespace, namespace_alias
272+
273+
node_text = cls._get_content(code, current_node)
274+
print(f" Processing clause node: {current_node.type} = '{node_text}'")
275+
276+
if current_node.type == "namespace_import":
277+
# Handle: * as React
278+
is_namespace = True
279+
for child in current_node.children:
280+
if child.type == "identifier":
281+
namespace_alias = cls._get_content(code, child)
282+
283+
elif current_node.type == "identifier":
284+
# Default import or named import identifier
285+
identifier = cls._get_content(code, current_node)
286+
names.append(identifier)
287+
aliases.append(None) # No alias by default
288+
289+
elif current_node.type == "import_specifier":
290+
# Named import with possible alias: { name } or { name as alias }
291+
name = None
292+
alias = None
293+
294+
for child in current_node.children:
295+
child_text = cls._get_content(code, child)
296+
if child.type == "identifier":
297+
if name is None:
298+
name = child_text
299+
else:
300+
alias = child_text # This is the alias part
301+
302+
if name:
303+
names.append(name)
304+
aliases.append(alias)
305+
306+
elif current_node.type == "named_imports":
307+
# Process children of named_imports (the part inside {})
308+
for child in current_node.children:
309+
if child.type not in ["{", "}", ","]: # Skip punctuation
310+
process_node_recursively(child)
311+
312+
else:
313+
# Recursively process children for other node types
314+
for child in current_node.children:
315+
process_node_recursively(child)
316+
317+
process_node_recursively(node)
318+
return names, aliases, is_namespace, namespace_alias
319+
254320
@classmethod
255321
def _process_class_node(cls, node: Node, code: bytes, codeFile: CodeFileModel, node_type :Literal["class", "interface", "type"]="class"):
256322
# TODO add support for modifiers at variables, classes i.e

0 commit comments

Comments
 (0)