67 lines
3.1 KiB
Python
67 lines
3.1 KiB
Python
# DO NOT import from graphify.extract here — direction is extract.py → extractors/ only.
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from graphify.ids import make_id
|
|
|
|
# Language built-in globals that AST may classify as call targets when used as
|
|
# constructors or coercion functions (e.g. String(x), Number(x), Boolean(x)).
|
|
# Without this filter they become god-nodes accumulating spurious edges from
|
|
# every call site. Filter applied at same-file and cross-file resolution.
|
|
# See issue #726.
|
|
_LANGUAGE_BUILTIN_GLOBALS: frozenset[str] = frozenset({
|
|
# JavaScript / TypeScript ECMAScript built-ins
|
|
"String", "Number", "Boolean", "Object", "Array", "Symbol", "BigInt",
|
|
"Date", "RegExp", "Error", "TypeError", "RangeError", "SyntaxError",
|
|
"ReferenceError", "EvalError", "URIError",
|
|
"Promise", "Map", "Set", "WeakMap", "WeakSet", "JSON", "Math",
|
|
"Reflect", "Proxy", "Intl",
|
|
"parseInt", "parseFloat", "isNaN", "isFinite",
|
|
"encodeURIComponent", "decodeURIComponent", "encodeURI", "decodeURI",
|
|
# Browser / Node common globals
|
|
"URL", "URLSearchParams", "FormData", "Blob", "File",
|
|
"Headers", "Request", "Response", "AbortController", "AbortSignal",
|
|
"TextEncoder", "TextDecoder", "console",
|
|
# Python built-in callables
|
|
"str", "int", "float", "bool", "list", "dict", "set", "tuple", "bytes",
|
|
"len", "range", "enumerate", "zip", "map", "filter", "sum", "min", "max",
|
|
"print", "open", "isinstance", "type", "super", "sorted", "reversed",
|
|
"any", "all", "abs", "round", "next", "iter", "hash", "id", "repr",
|
|
"callable", "getattr", "setattr", "hasattr", "delattr", "vars", "dir",
|
|
})
|
|
|
|
|
|
def _make_id(*parts: str) -> str:
|
|
return make_id(*parts)
|
|
|
|
|
|
def _file_stem(path: Path) -> str:
|
|
"""Stem used as the node-ID prefix for a file and its symbols.
|
|
|
|
The full path (extension dropped) is preserved as path segments; ``make_id``
|
|
later collapses the separators to underscores. Using every segment — not just
|
|
the immediate parent dir (#1504) — means same-named files in different
|
|
directories get distinct IDs instead of colliding into one
|
|
last-writer-wins node:
|
|
|
|
docs/v1/api/README.md -> docs/v1/api/README -> docs_v1_api_readme
|
|
docs/v2/api/README.md -> docs/v2/api/README -> docs_v2_api_readme
|
|
|
|
Top-level files keep a bare stem (``setup.py`` -> ``setup``). When passed an
|
|
absolute path the whole path is encoded; the extract() id-remap post-pass
|
|
re-derives the canonical repo-relative form from ``source_file`` so the on-disk
|
|
location can't leak into the persisted IDs (#502).
|
|
|
|
Returns "" for a path with no name (``Path('.')`` — a source_file that equals
|
|
the scan root, so it has no per-file stem). Guarding here keeps
|
|
``path.with_suffix("")`` from raising ``ValueError: '.' has an empty name`` and
|
|
protects every caller, not just ``_semantic_id_remap`` (#1618)."""
|
|
if not path.name:
|
|
return ""
|
|
return path.with_suffix("").as_posix()
|
|
|
|
|
|
def _read_text(node, source: bytes) -> str:
|
|
return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace")
|