mirror of
https://github.com/Graphify-Labs/graphify.git
synced 2026-09-14 19:34:09 +08:00
3868434942
Review feedback on #2296: the previous revision added Go's predeclared names to _LANGUAGE_BUILTIN_GLOBALS, which ~11 languages consult through engine.py and the cross-file pass, and whose check wraps the in-file EXTRACTED branch as well as raw_calls. Two confirmed regressions: * Rust normalizes 'Widget::new(3)' to the bare token 'new', so every in-file 'Type::new()' edge disappeared. Rust keeps its own _RUST_TRAIT_METHOD_BLOCKLIST, deliberately on the cross-file branch only — this change follows that language-local pattern. * Go 'h.append(v)' is a selector_expression call to a real method and was dropped with it. On the 3x-ui corpus this cost a genuine 'systemMetrics.append(...)' -> '(*metricHistory).append' edge. The filter now lives in extractors/go.py as _GO_PREDECLARED_FUNCS and fires only when the callee node is a bare identifier, so selector calls ('h.append(v)', 'pkg.Delete(x)') and every other language are untouched. Go raw_calls now carry language="go" and the shared pass gates on it, mirroring the bash gate, as a backstop for Go raw_calls minted elsewhere. The set is the Go spec's predeclared list in full: being Go-local and bare-identifier-only makes 'len'/'max'/'min'/'print' safe to include, and a spec boundary beats a hand-picked subset. Remeasured on the same 466-file Go corpus: upstream v8 16904 edges, 334 inbound to 'append' previous rev 16571 edges, 1 inbound (332 phantom gone, but the genuine selector call too) this rev 16572 edges, 2 inbound (332 phantom gone, genuine call restored) Tests kept, plus three regressions: builtin 'append' must not bind in-file (the branch a cross-file-only gate would miss), the Go selector call must survive, and the in-file Rust 'Type::new()' edge must survive. The last two fail on the previous revision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
4.2 KiB
Python
86 lines
4.2 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",
|
|
# Swift standard library / Foundation / SwiftUI (#2147). Value-type
|
|
# initializers (Data(x), Int(x), UUID()) and protocol conformance targets
|
|
# appear from virtually every file of a Swift codebase, exactly like the
|
|
# ECMAScript constructors above. String/Date/URL/Error are already listed.
|
|
"Int", "Int8", "Int16", "Int32", "Int64",
|
|
"UInt", "UInt8", "UInt16", "UInt32", "UInt64",
|
|
"Double", "Float", "Bool", "Character",
|
|
"Sendable", "Codable", "Decodable", "Encodable", "Equatable", "Hashable",
|
|
"Identifiable", "Comparable", "CaseIterable", "RawRepresentable",
|
|
"CustomStringConvertible", "CustomDebugStringConvertible", "AnyObject",
|
|
"LocalizedError",
|
|
"Data", "UUID", "Decimal", "Calendar", "Locale", "TimeZone", "Bundle",
|
|
"IndexPath", "IndexSet", "NotificationCenter", "UserDefaults",
|
|
"FileManager", "URLSession", "URLRequest", "URLComponents",
|
|
"JSONDecoder", "JSONEncoder", "DateFormatter", "NumberFormatter",
|
|
"ISO8601DateFormatter",
|
|
"NSObject", "NSString", "NSError", "NSLock", "NSAttributedString",
|
|
"DispatchQueue", "DispatchGroup", "OperationQueue", "RunLoop",
|
|
"View", "Color", "Font",
|
|
})
|
|
|
|
|
|
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")
|