svelte
This commit is contained in:
+259
-92
@@ -1786,153 +1786,295 @@ def parse_java_file(filepath: Path) -> List[Dict]:
|
|||||||
# -----------------------------
|
# -----------------------------
|
||||||
@lru_cache(maxsize=500)
|
@lru_cache(maxsize=500)
|
||||||
def parse_svelte_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
def parse_svelte_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
||||||
"""Cached Svelte parsing with enhanced multi-language graph relationships."""
|
"""Cached Svelte parsing with enhanced multi-language graph relationships.
|
||||||
|
ALWAYS returns a tuple of (text, metadata_tuple) entries. If parsing
|
||||||
|
fails or produces no chunks, return a single fallback chunk containing
|
||||||
|
the whole file text and minimal metadata.
|
||||||
|
"""
|
||||||
filepath = Path(filepath_str)
|
filepath = Path(filepath_str)
|
||||||
chunks = []
|
chunks = []
|
||||||
|
|
||||||
|
logger.info(f"🎨 Parsing Svelte file: {filepath}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
text = filepath.read_text(encoding="utf-8")
|
text = filepath.read_text(encoding="utf-8")
|
||||||
lines = text.splitlines(keepends=True)
|
lines = text.splitlines(keepends=True)
|
||||||
|
|
||||||
# Track component props and context for graph relationships
|
|
||||||
component_props = []
|
|
||||||
component_context = []
|
|
||||||
|
|
||||||
# Extract component name from filename for graph relationships
|
# Extract component name from filename for graph relationships
|
||||||
component_name = filepath.stem
|
component_name = filepath.stem
|
||||||
if component_name[0].islower():
|
if component_name and component_name[0].islower():
|
||||||
component_name = component_name[0].upper() + component_name[1:] # PascalCase
|
component_name = component_name[0].upper() + component_name[1:] # PascalCase
|
||||||
|
|
||||||
script_matches = list(re.finditer(r"<script(?:\s+[^>]*)?>(.*?)</script>", text, flags=re.DOTALL))
|
# Track component props and context for graph relationships
|
||||||
idx = 0
|
component_props = []
|
||||||
|
component_stores = []
|
||||||
|
component_imports = []
|
||||||
|
reactive_declarations = []
|
||||||
|
|
||||||
for m in script_matches:
|
# Parse script blocks
|
||||||
script_content = m.group(1)
|
script_matches = list(re.finditer(r"<script(?:\s+([^>]*?))?>(.*?)</script>", text, flags=re.DOTALL))
|
||||||
script_start_line = text[:m.start(1)].count("\n") + 1
|
script_idx = 0
|
||||||
script_attrs = m.group(0).split('>')[0] # Get <script ...> attributes
|
|
||||||
|
|
||||||
# Extract script context (module, context="module")
|
for script_match in script_matches:
|
||||||
is_module = 'context="module"' in script_attrs
|
script_attrs = script_match.group(1) or ""
|
||||||
|
script_content = script_match.group(2)
|
||||||
|
script_start_pos = script_match.start(2)
|
||||||
|
script_start_line = text[:script_start_pos].count("\n") + 1
|
||||||
|
|
||||||
|
# Determine script context
|
||||||
|
is_module = 'context="module"' in script_attrs or "context='module'" in script_attrs
|
||||||
|
is_typescript = 'lang="ts"' in script_attrs or "lang='ts'" in script_attrs
|
||||||
script_type = "script_module" if is_module else "script"
|
script_type = "script_module" if is_module else "script"
|
||||||
|
script_lang = "typescript" if is_typescript else "javascript"
|
||||||
|
|
||||||
|
logger.info(f" Processing {script_type} block (lang: {script_lang})")
|
||||||
|
|
||||||
|
# Try TypeScript/JavaScript parser
|
||||||
ts_helper = Path("./tools/parse_ts.js")
|
ts_helper = Path("./tools/parse_ts.js")
|
||||||
if ts_helper.exists():
|
parsed_with_helper = False
|
||||||
tmp = filepath.parent / f".tmp_{filepath.name}_{idx}.ts"
|
|
||||||
|
if ts_helper.is_file():
|
||||||
|
tmp = None
|
||||||
try:
|
try:
|
||||||
|
# Create temporary file for parsing
|
||||||
|
tmp = filepath.parent / f".tmp_{filepath.name}_{script_idx}.ts"
|
||||||
tmp.write_text(script_content, encoding="utf-8")
|
tmp.write_text(script_content, encoding="utf-8")
|
||||||
|
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["node", str(ts_helper), str(tmp)],
|
["node", str(ts_helper), str(tmp)],
|
||||||
capture_output=True, text=True, check=True, timeout=15
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
timeout=15
|
||||||
)
|
)
|
||||||
decls = json.loads(proc.stdout)
|
decls = json.loads(proc.stdout)
|
||||||
script_lines = script_content.splitlines(keepends=True)
|
script_lines = script_content.splitlines(keepends=True)
|
||||||
|
|
||||||
for d in decls:
|
logger.info(f" TS helper found {len(decls)} declarations")
|
||||||
|
|
||||||
|
for i, d in enumerate(decls):
|
||||||
start_rel = max(0, d.get("start_line", 1) - 1)
|
start_rel = max(0, d.get("start_line", 1) - 1)
|
||||||
end_rel = d.get("end_line", start_rel + 1)
|
end_rel = d.get("end_line", start_rel + 1)
|
||||||
|
|
||||||
|
# Guard the end_line
|
||||||
|
if end_rel <= start_rel:
|
||||||
|
end_rel = start_rel + 1
|
||||||
|
|
||||||
chunk_code = "".join(script_lines[start_rel:end_rel])
|
chunk_code = "".join(script_lines[start_rel:end_rel])
|
||||||
doc = d.get("doc_comment", "").strip()
|
doc = d.get("doc_comment", "").strip()
|
||||||
abs_start = script_start_line + start_rel
|
abs_start = script_start_line + start_rel
|
||||||
|
|
||||||
# Extract component relationships
|
decl_name = d.get("name", "")
|
||||||
decl_name = d.get("name")
|
decl_type = d.get("type", "unknown")
|
||||||
decl_type = d.get("type")
|
|
||||||
|
|
||||||
# Track props for component interface
|
# Skip invalid declarations
|
||||||
if decl_type == "variable" and decl_name and not is_module:
|
if not decl_name or decl_type == "unknown":
|
||||||
# Look for prop patterns: export let prop = ...
|
continue
|
||||||
if re.search(r'export\s+let\s+' + re.escape(decl_name), chunk_code):
|
|
||||||
|
# Analyze declaration for Svelte-specific patterns
|
||||||
|
is_exported = "export" in chunk_code
|
||||||
|
is_prop = False
|
||||||
|
is_reactive = False
|
||||||
|
is_store = False
|
||||||
|
|
||||||
|
if decl_type == "variable" and not is_module:
|
||||||
|
# Check for prop: export let prop = ...
|
||||||
|
if re.search(r'\bexport\s+let\s+' + re.escape(decl_name), chunk_code):
|
||||||
component_props.append(decl_name)
|
component_props.append(decl_name)
|
||||||
|
is_prop = True
|
||||||
decl_type = "prop"
|
decl_type = "prop"
|
||||||
# Track reactive declarations: $: reactiveVar = ...
|
|
||||||
elif chunk_code.strip().startswith('$:'):
|
# Check for reactive declaration: $: reactiveVar = ...
|
||||||
component_context.append(decl_name)
|
if chunk_code.strip().startswith('$:'):
|
||||||
|
reactive_declarations.append(decl_name)
|
||||||
|
is_reactive = True
|
||||||
decl_type = "reactive"
|
decl_type = "reactive"
|
||||||
|
|
||||||
chunk_text = f"File: {filepath}\nType: {decl_type}\nName: {decl_name}\nDoc: {doc}\nCode:\n{chunk_code}"
|
# Check for store: $storeName
|
||||||
|
if decl_name.startswith('$'):
|
||||||
|
component_stores.append(decl_name)
|
||||||
|
is_store = True
|
||||||
|
|
||||||
|
# Track imports
|
||||||
|
if "import" in chunk_code and "from" in chunk_code:
|
||||||
|
import_match = re.search(r'from\s+["\']([^"\']+)["\']', chunk_code)
|
||||||
|
if import_match:
|
||||||
|
component_imports.append(import_match.group(1))
|
||||||
|
|
||||||
|
# Build chunk text
|
||||||
|
chunk_text = f"File: {filepath}\nType: {decl_type}\nName: {decl_name}\n"
|
||||||
|
chunk_text += f"Component: {component_name}\nScript Type: {script_type}\n"
|
||||||
|
if doc:
|
||||||
|
chunk_text += f"Doc:\n{doc}\n\n"
|
||||||
|
chunk_text += f"Code:\n{chunk_code}"
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
"file": str(filepath),
|
"file": str(filepath),
|
||||||
"name": decl_name,
|
"name": decl_name,
|
||||||
"type": decl_type,
|
"type": decl_type,
|
||||||
"line": abs_start,
|
"line": abs_start,
|
||||||
"language": "typescript",
|
"language": script_lang,
|
||||||
"block_type": script_type,
|
"block_type": script_type,
|
||||||
"component_name": component_name
|
"component_name": component_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add specific metadata for different declaration types
|
# Add Svelte-specific metadata
|
||||||
if decl_type == "prop":
|
if is_prop:
|
||||||
metadata["is_exported"] = True
|
metadata["is_exported"] = True
|
||||||
metadata["component_prop"] = True
|
metadata["component_prop"] = True
|
||||||
elif decl_type == "reactive":
|
if is_reactive:
|
||||||
metadata["is_reactive"] = True
|
metadata["is_reactive"] = True
|
||||||
elif decl_type == "function":
|
if is_store:
|
||||||
metadata["is_function"] = True
|
metadata["is_store"] = True
|
||||||
# Try to detect event dispatchers
|
if is_exported and not is_prop:
|
||||||
|
metadata["is_exported"] = True
|
||||||
|
|
||||||
|
# Function-specific metadata
|
||||||
|
if decl_type == "function":
|
||||||
if "createEventDispatcher" in chunk_code:
|
if "createEventDispatcher" in chunk_code:
|
||||||
metadata["is_event_dispatcher"] = True
|
metadata["is_event_dispatcher"] = True
|
||||||
|
if "dispatch(" in chunk_code:
|
||||||
|
metadata["dispatches_events"] = True
|
||||||
|
|
||||||
|
if doc:
|
||||||
|
metadata["docstring"] = doc
|
||||||
|
|
||||||
chunks.append({
|
chunks.append({
|
||||||
"text": chunk_text,
|
"text": chunk_text,
|
||||||
"metadata": metadata
|
"metadata": metadata
|
||||||
})
|
})
|
||||||
|
|
||||||
|
logger.info(f" Declaration {i+1}: {decl_type} {decl_name}")
|
||||||
|
|
||||||
|
parsed_with_helper = True
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.warning(f" TS parser subprocess failed for {filepath}: {e.stderr}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.warning(f" TS parser output invalid JSON for {filepath}: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"TS parser failed for {filepath}: {e}; falling back to heuristic")
|
logger.warning(f" TS parser failed for {filepath}: {e}")
|
||||||
chunks.append(_make_script_chunk(filepath, script_content, script_start_line, script_type, component_name))
|
|
||||||
finally:
|
finally:
|
||||||
tmp.unlink(missing_ok=True)
|
if tmp and tmp.exists():
|
||||||
else:
|
tmp.unlink(missing_ok=True)
|
||||||
chunks.append(_make_script_chunk(filepath, script_content, script_start_line, script_type, component_name))
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
# Style blocks with CSS relationship extraction
|
# Fallback: Create basic script chunk if parser failed
|
||||||
for m in re.finditer(r"<style(?:\s+[^>]*)?>(.*?)</style>", text, flags=re.DOTALL):
|
if not parsed_with_helper:
|
||||||
style_content = m.group(1)
|
logger.info(f" Using fallback script chunk")
|
||||||
style_start_line = text[:m.start(1)].count("\n") + 1
|
chunks.append(_make_script_chunk(
|
||||||
|
filepath, script_content, script_start_line,
|
||||||
|
script_type, component_name, script_lang
|
||||||
|
))
|
||||||
|
|
||||||
# Extract CSS classes and relationships
|
script_idx += 1
|
||||||
css_classes = re.findall(r'\.([a-zA-Z][\w-]*)\s*{', style_content)
|
|
||||||
css_selectors = re.findall(r'([a-zA-Z][\w-]*)\s*{', style_content)
|
# Parse style blocks
|
||||||
|
style_matches = list(re.finditer(r"<style(?:\s+([^>]*?))?>(.*?)</style>", text, flags=re.DOTALL))
|
||||||
|
|
||||||
|
for style_idx, style_match in enumerate(style_matches):
|
||||||
|
style_attrs = style_match.group(1) or ""
|
||||||
|
style_content = style_match.group(2)
|
||||||
|
style_start_pos = style_match.start(2)
|
||||||
|
style_start_line = text[:style_start_pos].count("\n") + 1
|
||||||
|
|
||||||
|
# Determine style language
|
||||||
|
is_scss = 'lang="scss"' in style_attrs or "lang='scss'" in style_attrs
|
||||||
|
is_less = 'lang="less"' in style_attrs or "lang='less'" in style_attrs
|
||||||
|
is_scoped = "scoped" not in style_attrs # Svelte styles are scoped by default
|
||||||
|
|
||||||
|
style_lang = "scss" if is_scss else ("less" if is_less else "css")
|
||||||
|
|
||||||
|
# Extract CSS classes and selectors
|
||||||
|
css_classes = list(set(re.findall(r'\.([a-zA-Z][\w-]*)\s*[{,:]', style_content)))
|
||||||
|
css_ids = list(set(re.findall(r'#([a-zA-Z][\w-]*)\s*[{,:]', style_content)))
|
||||||
|
css_selectors = list(set(re.findall(r'^([a-zA-Z][\w-]*)\s*{', style_content, re.MULTILINE)))
|
||||||
|
|
||||||
|
# Extract CSS custom properties (variables)
|
||||||
|
css_vars = list(set(re.findall(r'--([a-zA-Z][\w-]*)', style_content)))
|
||||||
|
|
||||||
chunk_text = f"File: {filepath}\nType: style\nComponent: {component_name}\n"
|
chunk_text = f"File: {filepath}\nType: style\nComponent: {component_name}\n"
|
||||||
|
chunk_text += f"Language: {style_lang}\nScoped: {is_scoped}\n"
|
||||||
if css_classes:
|
if css_classes:
|
||||||
chunk_text += f"CSS Classes: {', '.join(css_classes)}\n"
|
chunk_text += f"CSS Classes: {', '.join(css_classes)}\n"
|
||||||
|
if css_ids:
|
||||||
|
chunk_text += f"CSS IDs: {', '.join(css_ids)}\n"
|
||||||
|
if css_vars:
|
||||||
|
chunk_text += f"CSS Variables: {', '.join(css_vars)}\n"
|
||||||
chunk_text += f"Style Content:\n{style_content}"
|
chunk_text += f"Style Content:\n{style_content}"
|
||||||
|
|
||||||
chunks.append({
|
chunks.append({
|
||||||
"text": chunk_text,
|
"text": chunk_text,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"file": str(filepath),
|
"file": str(filepath),
|
||||||
"name": f"style_block_{idx}",
|
"name": f"style_block_{style_idx}",
|
||||||
"type": "style",
|
"type": "style",
|
||||||
"line": style_start_line,
|
"line": style_start_line,
|
||||||
"language": "css",
|
"language": style_lang,
|
||||||
"block_type": "style",
|
"block_type": "style",
|
||||||
"component_name": component_name,
|
"component_name": component_name,
|
||||||
"css_classes": css_classes,
|
"css_classes": css_classes,
|
||||||
"css_selectors": css_selectors
|
"css_ids": css_ids,
|
||||||
|
"css_selectors": css_selectors,
|
||||||
|
"css_variables": css_vars,
|
||||||
|
"is_scoped": is_scoped,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
idx += 1
|
|
||||||
|
|
||||||
# Markup with Svelte-specific element analysis
|
# Parse markup (template)
|
||||||
markup = re.sub(r"<script[^>]*>.*?</script>", "", text, flags=re.DOTALL)
|
markup = text
|
||||||
markup = re.sub(r"<style[^>]*>.*?</style>", "", markup, flags=re.DOTALL).strip()
|
# Remove script blocks
|
||||||
|
markup = re.sub(r"<script[^>]*>.*?</script>", "", markup, flags=re.DOTALL)
|
||||||
|
# Remove style blocks
|
||||||
|
markup = re.sub(r"<style[^>]*>.*?</style>", "", markup, flags=re.DOTALL)
|
||||||
|
markup = markup.strip()
|
||||||
|
|
||||||
if markup:
|
if markup:
|
||||||
# Extract component usage and bindings from markup
|
# Extract Svelte-specific markup features
|
||||||
used_components = re.findall(r'<([A-Z][a-zA-Z]*)', markup)
|
used_components = list(set(re.findall(r'<([A-Z][a-zA-Z0-9]*)', markup)))
|
||||||
prop_bindings = re.findall(r'(\w+)={([^}]+)}', markup)
|
prop_bindings = re.findall(r'\b(\w+)={([^}]+)}', markup)
|
||||||
event_handlers = re.findall(r'on:(\w+)=', markup)
|
event_handlers = list(set(re.findall(r'on:(\w+)=', markup)))
|
||||||
|
|
||||||
|
# Extract slots
|
||||||
|
slots = list(set(re.findall(r'<slot\s+name="([^"]+)"', markup)))
|
||||||
|
has_default_slot = '<slot' in markup and 'name=' not in markup[:markup.find('<slot') + 100]
|
||||||
|
|
||||||
|
# Extract stores used in template
|
||||||
|
template_stores = list(set(re.findall(r'\$([a-zA-Z_]\w*)', markup)))
|
||||||
|
|
||||||
|
# Extract directives
|
||||||
|
use_directives = list(set(re.findall(r'use:(\w+)', markup)))
|
||||||
|
transition_directives = list(set(re.findall(r'(?:transition|in|out):(\w+)', markup)))
|
||||||
|
|
||||||
|
# Extract bind directives
|
||||||
|
bind_directives = list(set(re.findall(r'bind:(\w+)', markup)))
|
||||||
|
|
||||||
|
# Extract reactive statements in template
|
||||||
|
reactive_statements = re.findall(r'\{([^}]*\$:\s*[^}]*)\}', markup)
|
||||||
|
|
||||||
chunk_text = f"File: {filepath}\nType: markup\nComponent: {component_name}\n"
|
chunk_text = f"File: {filepath}\nType: markup\nComponent: {component_name}\n"
|
||||||
|
|
||||||
if used_components:
|
if used_components:
|
||||||
chunk_text += f"Uses Components: {', '.join(set(used_components))}\n"
|
chunk_text += f"Uses Components: {', '.join(used_components)}\n"
|
||||||
if prop_bindings:
|
if prop_bindings:
|
||||||
chunk_text += f"Prop Bindings: {', '.join([f'{prop}={val}' for prop, val in prop_bindings[:5]])}\n"
|
bindings_preview = [f"{prop}={{{val}}}" for prop, val in prop_bindings[:5]]
|
||||||
|
chunk_text += f"Prop Bindings: {', '.join(bindings_preview)}\n"
|
||||||
if event_handlers:
|
if event_handlers:
|
||||||
chunk_text += f"Event Handlers: {', '.join(set(event_handlers))}\n"
|
chunk_text += f"Event Handlers: {', '.join(event_handlers)}\n"
|
||||||
|
if slots or has_default_slot:
|
||||||
|
slot_info = []
|
||||||
|
if has_default_slot:
|
||||||
|
slot_info.append("default")
|
||||||
|
slot_info.extend(slots)
|
||||||
|
chunk_text += f"Slots: {', '.join(slot_info)}\n"
|
||||||
|
if template_stores:
|
||||||
|
chunk_text += f"Template Stores: {', '.join(template_stores)}\n"
|
||||||
|
if use_directives:
|
||||||
|
chunk_text += f"Use Directives: {', '.join(use_directives)}\n"
|
||||||
|
if transition_directives:
|
||||||
|
chunk_text += f"Transitions: {', '.join(transition_directives)}\n"
|
||||||
|
if bind_directives:
|
||||||
|
chunk_text += f"Bind Directives: {', '.join(bind_directives)}\n"
|
||||||
|
|
||||||
chunk_text += f"Markup:\n{markup}"
|
chunk_text += f"Markup:\n{markup}"
|
||||||
|
|
||||||
chunks.append({
|
chunks.append({
|
||||||
@@ -1945,52 +2087,77 @@ def parse_svelte_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
|||||||
"language": "svelte",
|
"language": "svelte",
|
||||||
"block_type": "markup",
|
"block_type": "markup",
|
||||||
"component_name": component_name,
|
"component_name": component_name,
|
||||||
"used_components": list(set(used_components)),
|
"used_components": used_components,
|
||||||
"prop_bindings": [prop for prop, _ in prop_bindings],
|
"prop_bindings": [prop for prop, _ in prop_bindings],
|
||||||
"event_handlers": list(set(event_handlers)),
|
"event_handlers": event_handlers,
|
||||||
"component_props": component_props, # Props discovered in scripts
|
"component_props": component_props,
|
||||||
"reactive_vars": component_context # Reactive context discovered
|
"reactive_vars": reactive_declarations,
|
||||||
|
"stores": component_stores,
|
||||||
|
"template_stores": template_stores,
|
||||||
|
"imports": component_imports,
|
||||||
|
"slots": slots,
|
||||||
|
"has_default_slot": has_default_slot,
|
||||||
|
"use_directives": use_directives,
|
||||||
|
"transitions": transition_directives,
|
||||||
|
"bind_directives": bind_directives,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"parse_svelte_file failed {filepath}: {e}")
|
logger.warning(f"Svelte parsing failed for {filepath}: {e}")
|
||||||
return tuple()
|
|
||||||
return tuple((c["text"], tuple(c["metadata"].items())) for c in chunks)
|
|
||||||
|
|
||||||
def _make_script_chunk(filepath, script_content, start_line, block_type, component_name=None):
|
# Return parsed chunks or fallback
|
||||||
"""Enhanced script chunk creation with basic analysis."""
|
if chunks:
|
||||||
# Basic heuristic analysis for when TypeScript parser fails
|
logger.info(f"✅ Successfully parsed {len(chunks)} chunks from {filepath}")
|
||||||
|
return tuple((c["text"], tuple(c["metadata"].items())) for c in chunks)
|
||||||
|
|
||||||
|
# Fallback: emit a whole-file chunk
|
||||||
|
logger.warning(f"❌ No chunks created from Svelte parsing for {filepath}. Emitting fallback whole-file chunk.")
|
||||||
|
try:
|
||||||
|
full_code = filepath.read_text(encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
full_code = ""
|
||||||
|
|
||||||
|
fallback_meta = {
|
||||||
|
"file": str(filepath),
|
||||||
|
"name": filepath.stem,
|
||||||
|
"type": "component",
|
||||||
|
"line": 1,
|
||||||
|
"language": "svelte",
|
||||||
|
"component_name": filepath.stem,
|
||||||
|
}
|
||||||
|
return ((full_code, tuple(fallback_meta.items())),)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_script_chunk(filepath: Path, script_content: str, start_line: int,
|
||||||
|
script_type: str, component_name: str, language: str = "javascript") -> dict:
|
||||||
|
"""Create a fallback script chunk when TS parser fails."""
|
||||||
|
chunk_text = f"File: {filepath}\nType: {script_type}\nComponent: {component_name}\n"
|
||||||
|
chunk_text += f"Language: {language}\nCode:\n{script_content}"
|
||||||
|
|
||||||
|
# Extract basic info with regex
|
||||||
exports = re.findall(r'export\s+(?:let|const|function|class)\s+(\w+)', script_content)
|
exports = re.findall(r'export\s+(?:let|const|function|class)\s+(\w+)', script_content)
|
||||||
functions = re.findall(r'(?:export\s+)?function\s+(\w+)', script_content)
|
imports = re.findall(r'from\s+["\']([^"\']+)["\']', script_content)
|
||||||
imports = re.findall(r'import.*from\s+[\'"]([^\'"]+)[\'"]', script_content)
|
|
||||||
|
|
||||||
chunk_text = f"File: {filepath}\nType: {block_type}\n"
|
|
||||||
if component_name:
|
|
||||||
chunk_text += f"Component: {component_name}\n"
|
|
||||||
if exports:
|
|
||||||
chunk_text += f"Exports: {', '.join(exports)}\n"
|
|
||||||
if functions:
|
|
||||||
chunk_text += f"Functions: {', '.join(functions)}\n"
|
|
||||||
chunk_text += f"Code:\n{script_content}"
|
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
"file": str(filepath),
|
"file": str(filepath),
|
||||||
"name": f"{block_type}_chunk",
|
"name": script_type,
|
||||||
"type": block_type,
|
"type": script_type,
|
||||||
"line": start_line,
|
"line": start_line,
|
||||||
"language": "typescript" if block_type == "script" else "css",
|
"language": language,
|
||||||
"block_type": block_type
|
"block_type": script_type,
|
||||||
|
"component_name": component_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if component_name:
|
|
||||||
metadata["component_name"] = component_name
|
|
||||||
if exports:
|
if exports:
|
||||||
metadata["exports"] = exports
|
metadata["exports"] = exports
|
||||||
if imports:
|
if imports:
|
||||||
metadata["imports"] = imports
|
metadata["imports"] = imports
|
||||||
|
|
||||||
return {"text": chunk_text, "metadata": metadata}
|
return {
|
||||||
|
"text": chunk_text,
|
||||||
|
"metadata": metadata
|
||||||
|
}
|
||||||
|
|
||||||
def parse_svelte_file(filepath: Path) -> List[Dict]:
|
def parse_svelte_file(filepath: Path) -> List[Dict]:
|
||||||
"""Parse Svelte file with caching."""
|
"""Parse Svelte file with caching."""
|
||||||
|
|||||||
+417
-91
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
// Enhanced TypeScript/JavaScript AST parser
|
||||||
// Usage: node parse_ts.js file.ts
|
// Usage: node parse_ts.js file.ts
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require("fs");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
const { parse } = require('@typescript-eslint/typescript-estree');
|
const { parse } = require("@typescript-eslint/typescript-estree");
|
||||||
|
|
||||||
function getLoc(node) {
|
function getLoc(node) {
|
||||||
if (node.loc) {
|
if (node.loc) {
|
||||||
@@ -14,139 +15,459 @@ function getLoc(node) {
|
|||||||
|
|
||||||
function extractLeadingComment(node, sourceCode) {
|
function extractLeadingComment(node, sourceCode) {
|
||||||
if (!node.range || !sourceCode) return "";
|
if (!node.range || !sourceCode) return "";
|
||||||
|
|
||||||
const startIdx = node.range[0];
|
const startIdx = node.range[0];
|
||||||
let commentEnd = startIdx;
|
|
||||||
// Look backward for comments
|
|
||||||
let i = startIdx - 1;
|
|
||||||
let commentLines = [];
|
|
||||||
let inBlock = false;
|
|
||||||
|
|
||||||
while (i >= 0) {
|
// Find the start of the line containing the node
|
||||||
const char = sourceCode[i];
|
let lineStart = startIdx;
|
||||||
if (char === '\n') break;
|
while (lineStart > 0 && sourceCode[lineStart - 1] !== "\n") {
|
||||||
i--;
|
lineStart--;
|
||||||
}
|
|
||||||
const lineStart = i + 1;
|
|
||||||
const lineAbove = sourceCode.slice(lineStart, startIdx).trim();
|
|
||||||
|
|
||||||
// Check for // comment on same line before node
|
|
||||||
if (lineAbove.startsWith('//')) {
|
|
||||||
return lineAbove.substring(2).trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look further up for multi-line or JSDoc
|
// Check for inline comment on the same line
|
||||||
const lines = sourceCode.substring(0, lineStart).split('\n');
|
const lineContent = sourceCode.slice(lineStart, startIdx).trim();
|
||||||
for (let j = lines.length - 1; j >= Math.max(0, lines.length - 5); j--) {
|
if (lineContent.startsWith("//")) {
|
||||||
const line = lines[j].trim();
|
return lineContent.substring(2).trim();
|
||||||
if (line.startsWith('//')) {
|
}
|
||||||
commentLines.unshift(line.substring(2).trim());
|
|
||||||
} else if (line.endsWith('*/')) {
|
// Look backward through previous lines for comments
|
||||||
inBlock = true;
|
const lines = sourceCode.substring(0, lineStart).split("\n");
|
||||||
commentLines.unshift(line.slice(0, -2).trim());
|
const commentLines = [];
|
||||||
} else if (inBlock) {
|
let inBlockComment = false;
|
||||||
if (line.startsWith('/*') || line.startsWith('/**')) {
|
|
||||||
commentLines.unshift(line.slice(2).trim());
|
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 10); i--) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
|
||||||
|
if (line.endsWith("*/")) {
|
||||||
|
// End of block comment found
|
||||||
|
inBlockComment = true;
|
||||||
|
const blockContent = line
|
||||||
|
.slice(0, -2)
|
||||||
|
.replace(/^\*+\s*/, "")
|
||||||
|
.trim();
|
||||||
|
if (blockContent) {
|
||||||
|
commentLines.unshift(blockContent);
|
||||||
|
}
|
||||||
|
} else if (inBlockComment) {
|
||||||
|
if (line.startsWith("/*") || line.startsWith("/**")) {
|
||||||
|
// Start of block comment found
|
||||||
|
const blockContent = line
|
||||||
|
.substring(line.indexOf("/*") + 2)
|
||||||
|
.replace(/^\*+\s*/, "")
|
||||||
|
.trim();
|
||||||
|
if (blockContent) {
|
||||||
|
commentLines.unshift(blockContent);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
commentLines.unshift(line);
|
// Inside block comment
|
||||||
|
const blockContent = line.replace(/^\*+\s*/, "").trim();
|
||||||
|
if (blockContent) {
|
||||||
|
commentLines.unshift(blockContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (line.startsWith("//")) {
|
||||||
|
// Single-line comment
|
||||||
|
commentLines.unshift(line.substring(2).trim());
|
||||||
|
} else if (line === "") {
|
||||||
|
// Empty line - continue if we already have comments
|
||||||
|
if (commentLines.length === 0) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (line === '') {
|
|
||||||
if (commentLines.length > 0) continue;
|
|
||||||
else break;
|
|
||||||
} else {
|
} else {
|
||||||
|
// Non-comment, non-empty line - stop looking
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return commentLines.join('\n').trim();
|
|
||||||
|
return commentLines.join("\n").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTypeInfo(node) {
|
||||||
|
const typeInfo = {};
|
||||||
|
|
||||||
|
// Extract type annotation
|
||||||
|
if (node.typeAnnotation) {
|
||||||
|
typeInfo.returnType = extractTypeString(node.typeAnnotation.typeAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract parameters for functions
|
||||||
|
if (node.params) {
|
||||||
|
typeInfo.parameters = node.params.map((param) => {
|
||||||
|
const paramInfo = {
|
||||||
|
name: extractParamName(param),
|
||||||
|
type: param.typeAnnotation
|
||||||
|
? extractTypeString(param.typeAnnotation.typeAnnotation)
|
||||||
|
: "any",
|
||||||
|
};
|
||||||
|
if (param.optional) {
|
||||||
|
paramInfo.optional = true;
|
||||||
|
}
|
||||||
|
return paramInfo;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract generics
|
||||||
|
if (node.typeParameters) {
|
||||||
|
typeInfo.generics = node.typeParameters.params.map((tp) => tp.name.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractParamName(param) {
|
||||||
|
if (param.type === "Identifier") {
|
||||||
|
return param.name;
|
||||||
|
}
|
||||||
|
if (param.type === "RestElement" && param.argument.type === "Identifier") {
|
||||||
|
return "..." + param.argument.name;
|
||||||
|
}
|
||||||
|
if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
|
||||||
|
return param.left.name;
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTypeString(typeNode) {
|
||||||
|
if (!typeNode) return "any";
|
||||||
|
|
||||||
|
switch (typeNode.type) {
|
||||||
|
case "TSStringKeyword":
|
||||||
|
return "string";
|
||||||
|
case "TSNumberKeyword":
|
||||||
|
return "number";
|
||||||
|
case "TSBooleanKeyword":
|
||||||
|
return "boolean";
|
||||||
|
case "TSAnyKeyword":
|
||||||
|
return "any";
|
||||||
|
case "TSVoidKeyword":
|
||||||
|
return "void";
|
||||||
|
case "TSUndefinedKeyword":
|
||||||
|
return "undefined";
|
||||||
|
case "TSNullKeyword":
|
||||||
|
return "null";
|
||||||
|
case "TSTypeReference":
|
||||||
|
if (typeNode.typeName.type === "Identifier") {
|
||||||
|
let typeStr = typeNode.typeName.name;
|
||||||
|
if (typeNode.typeParameters) {
|
||||||
|
const params = typeNode.typeParameters.params
|
||||||
|
.map(extractTypeString)
|
||||||
|
.join(", ");
|
||||||
|
typeStr += `<${params}>`;
|
||||||
|
}
|
||||||
|
return typeStr;
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
case "TSArrayType":
|
||||||
|
return extractTypeString(typeNode.elementType) + "[]";
|
||||||
|
case "TSUnionType":
|
||||||
|
return typeNode.types.map(extractTypeString).join(" | ");
|
||||||
|
case "TSIntersectionType":
|
||||||
|
return typeNode.types.map(extractTypeString).join(" & ");
|
||||||
|
case "TSFunctionType":
|
||||||
|
return "Function";
|
||||||
|
case "TSTypeLiteral":
|
||||||
|
return "object";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractDeclarations(ast, sourceCode) {
|
function extractDeclarations(ast, sourceCode) {
|
||||||
const decls = [];
|
const decls = [];
|
||||||
|
const parentMap = new WeakMap();
|
||||||
|
|
||||||
function visit(node) {
|
function setParents(node, parent = null) {
|
||||||
if (!node || typeof node !== 'object') return;
|
if (!node || typeof node !== "object") return;
|
||||||
|
|
||||||
if (Array.isArray(node)) {
|
if (Array.isArray(node)) {
|
||||||
node.forEach(n => visit(n));
|
node.forEach((n) => setParents(n, parent));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let docComment = extractLeadingComment(node, sourceCode);
|
parentMap.set(node, parent);
|
||||||
|
Object.values(node).forEach((child) => setParents(child, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
setParents(ast);
|
||||||
|
|
||||||
|
function visit(node) {
|
||||||
|
if (!node || typeof node !== "object") return;
|
||||||
|
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
node.forEach((n) => visit(n));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const docComment = extractLeadingComment(node, sourceCode);
|
||||||
|
const loc = getLoc(node);
|
||||||
|
|
||||||
|
// Function Declarations
|
||||||
|
if (node.type === "FunctionDeclaration" && node.id?.name) {
|
||||||
|
const typeInfo = extractTypeInfo(node);
|
||||||
|
|
||||||
if (node.type === 'FunctionDeclaration' && node.id?.name) {
|
|
||||||
const loc = getLoc(node);
|
|
||||||
decls.push({
|
decls.push({
|
||||||
name: node.id.name,
|
name: node.id.name,
|
||||||
type: 'function',
|
type: "function",
|
||||||
doc_comment: docComment,
|
doc_comment: docComment,
|
||||||
start_line: loc.start,
|
start_line: loc.start,
|
||||||
end_line: loc.end
|
end_line: loc.end,
|
||||||
|
is_async: node.async || false,
|
||||||
|
is_generator: node.generator || false,
|
||||||
|
...typeInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Arrow Functions assigned to variables
|
||||||
else if (
|
else if (
|
||||||
node.type === 'VariableDeclarator' &&
|
node.type === "VariableDeclarator" &&
|
||||||
node.id?.type === 'Identifier' &&
|
node.id?.type === "Identifier" &&
|
||||||
node.init?.type === 'ArrowFunctionExpression'
|
node.init?.type === "ArrowFunctionExpression"
|
||||||
) {
|
) {
|
||||||
const loc = getLoc(node);
|
const typeInfo = extractTypeInfo(node.init);
|
||||||
|
|
||||||
decls.push({
|
decls.push({
|
||||||
name: node.id.name,
|
name: node.id.name,
|
||||||
type: 'function',
|
type: "function",
|
||||||
doc_comment: docComment,
|
doc_comment: docComment,
|
||||||
start_line: loc.start,
|
start_line: loc.start,
|
||||||
end_line: loc.end
|
end_line: loc.end,
|
||||||
|
is_async: node.init.async || false,
|
||||||
|
is_arrow: true,
|
||||||
|
...typeInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (node.type === 'ClassDeclaration' && node.id?.name) {
|
// Class Declarations
|
||||||
const loc = getLoc(node);
|
else if (node.type === "ClassDeclaration" && node.id?.name) {
|
||||||
|
const classInfo = {
|
||||||
|
name: node.id.name,
|
||||||
|
type: "class",
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end,
|
||||||
|
is_abstract: node.abstract || false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract extends
|
||||||
|
if (node.superClass) {
|
||||||
|
if (node.superClass.type === "Identifier") {
|
||||||
|
classInfo.extends = node.superClass.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract implements
|
||||||
|
if (node.implements && node.implements.length > 0) {
|
||||||
|
classInfo.implements = node.implements.map(
|
||||||
|
(impl) => impl.expression.name || "unknown",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract generics
|
||||||
|
if (node.typeParameters) {
|
||||||
|
classInfo.generics = node.typeParameters.params.map(
|
||||||
|
(tp) => tp.name.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract methods and properties
|
||||||
|
const methods = [];
|
||||||
|
const properties = [];
|
||||||
|
|
||||||
|
if (node.body && node.body.body) {
|
||||||
|
for (const member of node.body.body) {
|
||||||
|
if (member.type === "MethodDefinition" && member.key?.name) {
|
||||||
|
methods.push(member.key.name);
|
||||||
|
} else if (member.type === "PropertyDefinition" && member.key?.name) {
|
||||||
|
properties.push(member.key.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methods.length > 0) {
|
||||||
|
classInfo.methods = methods;
|
||||||
|
}
|
||||||
|
if (properties.length > 0) {
|
||||||
|
classInfo.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
decls.push(classInfo);
|
||||||
|
}
|
||||||
|
// Interface Declarations
|
||||||
|
else if (node.type === "TSInterfaceDeclaration" && node.id?.name) {
|
||||||
|
const interfaceInfo = {
|
||||||
|
name: node.id.name,
|
||||||
|
type: "interface",
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract extends
|
||||||
|
if (node.extends && node.extends.length > 0) {
|
||||||
|
interfaceInfo.extends = node.extends.map(
|
||||||
|
(ext) => ext.expression.name || "unknown",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract generics
|
||||||
|
if (node.typeParameters) {
|
||||||
|
interfaceInfo.generics = node.typeParameters.params.map(
|
||||||
|
(tp) => tp.name.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract properties/methods
|
||||||
|
const properties = [];
|
||||||
|
const methods = [];
|
||||||
|
|
||||||
|
if (node.body && node.body.body) {
|
||||||
|
for (const member of node.body.body) {
|
||||||
|
if (member.type === "TSPropertySignature" && member.key?.name) {
|
||||||
|
properties.push(member.key.name);
|
||||||
|
} else if (member.type === "TSMethodSignature" && member.key?.name) {
|
||||||
|
methods.push(member.key.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.length > 0) {
|
||||||
|
interfaceInfo.properties = properties;
|
||||||
|
}
|
||||||
|
if (methods.length > 0) {
|
||||||
|
interfaceInfo.methods = methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
decls.push(interfaceInfo);
|
||||||
|
}
|
||||||
|
// Type Alias Declarations
|
||||||
|
else if (node.type === "TSTypeAliasDeclaration" && node.id?.name) {
|
||||||
|
const typeInfo = {
|
||||||
|
name: node.id.name,
|
||||||
|
type: "type",
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract generics
|
||||||
|
if (node.typeParameters) {
|
||||||
|
typeInfo.generics = node.typeParameters.params.map(
|
||||||
|
(tp) => tp.name.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract type definition
|
||||||
|
if (node.typeAnnotation) {
|
||||||
|
typeInfo.type_definition = extractTypeString(node.typeAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
decls.push(typeInfo);
|
||||||
|
}
|
||||||
|
// Enum Declarations
|
||||||
|
else if (node.type === "TSEnumDeclaration" && node.id?.name) {
|
||||||
|
const members = [];
|
||||||
|
|
||||||
|
if (node.members) {
|
||||||
|
for (const member of node.members) {
|
||||||
|
if (member.id?.type === "Identifier") {
|
||||||
|
members.push(member.id.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
decls.push({
|
decls.push({
|
||||||
name: node.id.name,
|
name: node.id.name,
|
||||||
type: 'class',
|
type: "enum",
|
||||||
doc_comment: docComment,
|
doc_comment: docComment,
|
||||||
start_line: loc.start,
|
start_line: loc.start,
|
||||||
end_line: loc.end
|
end_line: loc.end,
|
||||||
});
|
members: members,
|
||||||
}
|
|
||||||
else if (node.type === 'TSInterfaceDeclaration' && node.id?.name) {
|
|
||||||
const loc = getLoc(node);
|
|
||||||
decls.push({
|
|
||||||
name: node.id.name,
|
|
||||||
type: 'interface',
|
|
||||||
doc_comment: docComment,
|
|
||||||
start_line: loc.start,
|
|
||||||
end_line: loc.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (node.type === 'TSTypeAliasDeclaration' && node.id?.name) {
|
|
||||||
const loc = getLoc(node);
|
|
||||||
decls.push({
|
|
||||||
name: node.id.name,
|
|
||||||
type: 'type',
|
|
||||||
doc_comment: docComment,
|
|
||||||
start_line: loc.start,
|
|
||||||
end_line: loc.end
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Variable Declarations (const, let, var)
|
||||||
else if (
|
else if (
|
||||||
node.type === 'VariableDeclarator' &&
|
node.type === "VariableDeclarator" &&
|
||||||
node.id?.type === 'Identifier'
|
node.id?.type === "Identifier"
|
||||||
) {
|
) {
|
||||||
const parent = node.parent;
|
const parent = parentMap.get(node);
|
||||||
if (
|
|
||||||
parent?.type === 'VariableDeclaration' &&
|
if (parent?.type === "VariableDeclaration") {
|
||||||
['const', 'let'].includes(parent.kind)
|
const varInfo = {
|
||||||
) {
|
|
||||||
const loc = getLoc(node);
|
|
||||||
decls.push({
|
|
||||||
name: node.id.name,
|
name: node.id.name,
|
||||||
type: 'variable',
|
type: "variable",
|
||||||
doc_comment: docComment,
|
doc_comment: docComment,
|
||||||
start_line: loc.start,
|
start_line: loc.start,
|
||||||
end_line: loc.end
|
end_line: loc.end,
|
||||||
});
|
var_kind: parent.kind, // const, let, or var
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract type annotation
|
||||||
|
if (node.id.typeAnnotation) {
|
||||||
|
varInfo.var_type = extractTypeString(
|
||||||
|
node.id.typeAnnotation.typeAnnotation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
decls.push(varInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Import Declarations
|
||||||
|
else if (node.type === "ImportDeclaration" && node.source?.value) {
|
||||||
|
const importInfo = {
|
||||||
|
name: node.source.value,
|
||||||
|
type: "import",
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end,
|
||||||
|
import_path: node.source.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract imported names
|
||||||
|
const imports = [];
|
||||||
|
if (node.specifiers) {
|
||||||
|
for (const spec of node.specifiers) {
|
||||||
|
if (spec.type === "ImportSpecifier" && spec.imported?.name) {
|
||||||
|
imports.push(spec.imported.name);
|
||||||
|
} else if (
|
||||||
|
spec.type === "ImportDefaultSpecifier" &&
|
||||||
|
spec.local?.name
|
||||||
|
) {
|
||||||
|
imports.push(`default as ${spec.local.name}`);
|
||||||
|
} else if (
|
||||||
|
spec.type === "ImportNamespaceSpecifier" &&
|
||||||
|
spec.local?.name
|
||||||
|
) {
|
||||||
|
imports.push(`* as ${spec.local.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imports.length > 0) {
|
||||||
|
importInfo.imported_names = imports;
|
||||||
|
}
|
||||||
|
|
||||||
|
decls.push(importInfo);
|
||||||
|
}
|
||||||
|
// Export Declarations
|
||||||
|
else if (node.type === "ExportNamedDeclaration") {
|
||||||
|
if (node.declaration) {
|
||||||
|
// Export with declaration: export const x = ...
|
||||||
|
visit(node.declaration);
|
||||||
|
} else if (node.specifiers && node.specifiers.length > 0) {
|
||||||
|
// Named exports: export { x, y }
|
||||||
|
for (const spec of node.specifiers) {
|
||||||
|
if (spec.exported?.name) {
|
||||||
|
decls.push({
|
||||||
|
name: spec.exported.name,
|
||||||
|
type: "export",
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end,
|
||||||
|
export_kind: "named",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Continue traversing
|
||||||
Object.values(node).forEach(visit);
|
Object.values(node).forEach(visit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,24 +475,29 @@ function extractDeclarations(ast, sourceCode) {
|
|||||||
return decls;
|
return decls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main execution
|
||||||
if (process.argv.length < 3) {
|
if (process.argv.length < 3) {
|
||||||
console.error('Usage: node parse_ts.js <file.ts>');
|
console.error("Usage: node parse_ts.js <file.ts|file.js>");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = path.resolve(process.argv[2]);
|
const filePath = path.resolve(process.argv[2]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const code = fs.readFileSync(filePath, 'utf8');
|
const code = fs.readFileSync(filePath, "utf8");
|
||||||
const ast = parse(code, {
|
const ast = parse(code, {
|
||||||
sourceType: 'module',
|
sourceType: "module",
|
||||||
loc: true,
|
loc: true,
|
||||||
range: true,
|
range: true,
|
||||||
comment: false // we extract manually for simplicity
|
comment: false,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const decls = extractDeclarations(ast, code);
|
const decls = extractDeclarations(ast, code);
|
||||||
console.log(JSON.stringify(decls, null, 2));
|
console.log(JSON.stringify(decls, null, 2));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`ERR: ${e.message}`);
|
console.error(`ERR: ${e.message}`);
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user