mirror of
https://github.com/Graphify-Labs/graphify.git
synced 2026-09-14 19:34:09 +08:00
b84b4a7316
A type-only import (import type { T } from './m') produced an import edge like a value
import, so type-only round-trips showed up as false import cycles. Stamp type_only on the
module and symbol edges from type-only statements (correctly not firing for a mixed
import { type A, B }, which keeps its value edge) and exclude stamped edges from
find_import_cycles, mirroring the existing deferred-import exclusion.
132 lines
4.5 KiB
Python
132 lines
4.5 KiB
Python
"""models — moved verbatim from graphify/extract.py."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable
|
|
from pathlib import Path
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
_WORKSPACE_PACKAGE_CACHE: dict[str, dict[str, Path]] = {}
|
|
|
|
_JS_CACHE_BYPASS_SUFFIXES = {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".vue", ".svelte"}
|
|
|
|
@dataclass
|
|
class LanguageConfig:
|
|
ts_module: str # e.g. "tree_sitter_python"
|
|
ts_language_fn: str = "language" # attr to call: e.g. tslang.language()
|
|
|
|
class_types: frozenset = frozenset()
|
|
function_types: frozenset = frozenset()
|
|
import_types: frozenset = frozenset()
|
|
call_types: frozenset = frozenset()
|
|
static_prop_types: frozenset = frozenset()
|
|
helper_fn_names: frozenset = frozenset()
|
|
container_bind_methods: frozenset = frozenset()
|
|
event_listener_properties: frozenset = frozenset()
|
|
|
|
# Name extraction
|
|
name_field: str = "name"
|
|
name_fallback_child_types: tuple = ()
|
|
|
|
# Body detection
|
|
body_field: str = "body"
|
|
body_fallback_child_types: tuple = () # e.g. ("declaration_list", "compound_statement")
|
|
|
|
# Call name extraction
|
|
call_function_field: str = "function" # field on call node for callee
|
|
call_accessor_node_types: frozenset = frozenset() # member/attribute nodes
|
|
call_accessor_field: str = "attribute" # field on accessor for method name
|
|
call_accessor_object_field: str = "" # field on accessor for the receiver/object
|
|
|
|
# Stop recursion at these types in walk_calls
|
|
function_boundary_types: frozenset = frozenset()
|
|
|
|
# Import handler: called for import nodes instead of generic handling
|
|
import_handler: Callable | None = None
|
|
|
|
# Optional custom name resolver for functions (C, C++ declarator unwrapping)
|
|
resolve_function_name_fn: Callable | None = None
|
|
|
|
# Optional symbol name sanitizer for node ID generation (e.g. Ruby suffixed methods)
|
|
sanitize_symbol_name_fn: Callable[[str], str] | None = None
|
|
|
|
# Extra label formatting for functions: if True, functions get "name()" label
|
|
function_label_parens: bool = True
|
|
|
|
# Extra walk hook called after generic dispatch (for JS arrow functions, C# namespaces, etc.)
|
|
extra_walk_fn: Callable | None = None
|
|
|
|
@dataclass(frozen=True)
|
|
class _SymbolDeclarationFact:
|
|
file_path: Path
|
|
name: str
|
|
line: int
|
|
|
|
@dataclass(frozen=True)
|
|
class _SymbolImportFact:
|
|
file_path: Path
|
|
local_name: str
|
|
target_path: Path
|
|
imported_name: str
|
|
line: int
|
|
|
|
@dataclass(frozen=True)
|
|
class _SymbolAliasFact:
|
|
file_path: Path
|
|
alias: str
|
|
target_name: str
|
|
line: int
|
|
|
|
@dataclass(frozen=True)
|
|
class _SymbolExportFact:
|
|
file_path: Path
|
|
exported_name: str
|
|
line: int
|
|
local_name: str | None = None
|
|
target_path: Path | None = None
|
|
target_name: str | None = None
|
|
# `export type { X } from ...`: erased at compile time, so the re-export
|
|
# edge it produces is stamped type_only and excluded from Import Cycles
|
|
# (#3123). The fact itself still participates in symbol resolution - a
|
|
# type import resolved through a type-only barrel is itself type-only.
|
|
type_only: bool = False
|
|
|
|
@dataclass(frozen=True)
|
|
class _StarExportFact:
|
|
file_path: Path
|
|
target_path: Path
|
|
line: int
|
|
type_only: bool = False
|
|
|
|
@dataclass(frozen=True)
|
|
class _NamespaceExportFact:
|
|
file_path: Path
|
|
exported_name: str
|
|
target_path: Path
|
|
line: int
|
|
type_only: bool = False
|
|
|
|
@dataclass(frozen=True)
|
|
class _SymbolUseFact:
|
|
file_path: Path
|
|
source_id: str
|
|
local_name: str
|
|
relation: str
|
|
context: str
|
|
line: int
|
|
|
|
@dataclass
|
|
class _SymbolResolutionFacts:
|
|
declarations: list[_SymbolDeclarationFact] = field(default_factory=list)
|
|
imports: list[_SymbolImportFact] = field(default_factory=list)
|
|
aliases: list[_SymbolAliasFact] = field(default_factory=list)
|
|
exports: list[_SymbolExportFact] = field(default_factory=list)
|
|
star_exports: list[_StarExportFact] = field(default_factory=list)
|
|
namespace_exports: list[_NamespaceExportFact] = field(default_factory=list)
|
|
uses: list[_SymbolUseFact] = field(default_factory=list)
|
|
# File-to-file submodule imports from `from pkg import submod` (#1146).
|
|
# Each entry is (importing_file, submodule_file, line, local_name) -- local_name
|
|
# is the binding introduced in the importing file: the alias when `from pkg
|
|
# import submod as alias` is used, otherwise the submodule's own name (#2082).
|
|
module_imports: list[tuple[Path, Path, int, str]] = field(default_factory=list)
|