Skip to content

Releases: cyberchronos00/code-graph

v0.23.12

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 19 Oct 13:47

Added

  • Kotlin exact mode maps scip-java property terms (Cart#label.) and the synthetic getLabel() / setItems() /
    isReady() at the property's spelling: a stored property is READS_PROP / WRITES_PROP, a custom accessor is
    CALLS with property: read, write or read_write (from =, +=, ++). Covered files drop the heuristic
    accessor calls. A declared fun getX() stays a call where the source spells getX (#105).
  • Kotlin: an interface or abstract val / var with no accessor becomes a property node (property: abstract) when an
    override has one, with IMPLEMENTED_BY from an interface property and OVERRIDDEN_BY from an abstract or open one
    (#105).
  • Java getX / setX / isX on a Kotlin property uses the same property nodes in heuristic and exact mode (#105).
  • Swift @AppStorage / @SceneStorage fields that share a wrapper and key are linked with SHARES_STORAGE (#235).

Changed

  • Kotlin $name inside a string template and a field read through an extension receiver
    (fun Cart.total() = items.size) record READS_PROP (#235).
  • A second branch on the same line keeps its own edge: the edge key includes branch, Kotlin / Swift construction
    branches are queued in source order, and the TypeScript extractor does the same for RENDERS (#235).

v0.23.11

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 19 Oct 12:02

Added

  • cg roundtrip --all (MCP roundtrip(prop="")): every stored property in one pass, listing only the round trips.
    About 1 s on a 5,000-field iOS app, where one call per property took minutes (#233).
  • Compose state in a local delegate (var count by remember { mutableIntStateOf(0) }, by rememberSaveable { },
    by mutableStateOf(), by vm.state.collectAsStateWithLifecycle(), by produceState) is a field:<function>.<name>
    node (property: state, hook, local) with READS_PROP / WRITES_PROP for bare uses in the function and its
    lambdas (#234).
  • Vue Options API templates: a bare {{ count }}, :title="label" or @click="count++" reads / writes the data()
    key (template: true); a v-for variable of that name does not (#234).

Changed

  • cg lint async-state: a call of a project Kotlin suspend function (val hits = repo.find(q) inside launch { })
    is an await point. incomplete-cache-key counts a Swift / Kotlin field used without self. / this. as an input,
    unless the function calls methods on it. two-writers treats a sentinel reset (= AsyncData.Loading(),
    = UiState.Idle) as a default (#233).

v0.23.10

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 19 Oct 09:31

Added

  • cg parity --structure pairs the missing members of a matched type inside the counterpart type: with an unmatched
    member or a top-level function that makes the same paired calls (duplicatePlants ↔ copySelected), or with a
    case .x: arm of a Swift receive(_:) / perform(_:) switch (handleHelpCenterClick ↔ case .helpCenterTapped,
    case in JSON). On Bitwarden: 19 more iOS → Android and 62 more Android → iOS pairs (#227).

Changed

  • A Swift receive(_:) / perform(_:) method with case .x: arms is no longer paired as a whole with one handler.

v0.23.9

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 19 Oct 09:13

Added

  • Swift platform files: Foo+iOS.swift, Foo.macOS.swift and files in an iOS/ / macOS/ / watchOS/ / tvOS/ /
    visionOS/ folder are built for that platform and unknown on the other Apple platforms, when Xcode target membership
    does not say. A guess: it never makes a dead branch, and platforms.path_conventions: false turns it off (#230).

Changed

  • Swift and Kotlin receivers are typed through call chains (shop.engine().run(), makeShop().engine()), locals
    initialised from one (let e = shop.engine()), Self return types (Sub.make() on a class func make() -> Self) and
    generic substitution (Garage<Engine>(…).get() with func get() -> T). Kotlin expression bodies (= Engine()) count
    as the return type. Fewer name-bound and candidate edges (#229, part of #109).
  • #available branches are column-precise: if #available(…) { a() } else { b() } on one line and } else { b() }
    on the closing line no longer mark b as guarded; conditions after the check (, x.isOn) are (#230, part of #225).

Fixed

  • Swift Self(…), implicit .init(…) (from the annotation or the declared return type) and nested-type initializers
    (Point.Delta(…)) are INSTANTIATES plus CALLS to the init (#231, part of #108).
  • A bare Swift class func / class var is static, so calls through a subclass resolve (#229).

v0.23.8

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 19 Oct 08:10

Added

  • cg parity --structure pairs types still missing by the fields they declare (confidence fields). Stored properties count under their wire names: Swift CodingKeys raw values, or Kotlin @SerialName / @SerializedName / @Json(name =). So a Swift Codable model meets its renamed Kotlin @Serializable port. Enum cases are compared with enum cases. On Bitwarden this adds 52 and 55 inferred pairs, mostly request / response models and their …Json counterparts. See parity.md (#226, #93).
  • Image and raw resources (Image("x"), UIImage(named:), Asset.Images.x, Image(.x), R.drawable.x, Res.drawable.x) are a structure feature (res:) (#226).
  • cg parity --both-sides lists the matched pairs and what only the target has (== ONLY IN TARGET, --json target_only): unmatched target types and top-level symbols, plus unmatched members of matched target types (#226).

Changed

  • cg parity --structure: a pair whose shared uses are all generic (found in more than three files on a side, such as l10n:cancel or l10n:yes) now needs two shared name words. Numbers are in validation-log.md (#226).

v0.23.7

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 19 Oct 07:40

Added

  • Dead branches: code under nested conditional compilation whose combined condition holds on no known target (#elseif os(macOS) inside #if os(iOS), #[cfg(windows)] inside #[cfg(unix)], #ifdef _WIN32 inside #ifdef __linux__) gets attrs.dead_branch, and its calls, reads and writes to other code are dropped, so readers / writers / impact no longer list it. cg platforms divergence --kind dead_branch (and the MCP platform_divergence tool) lists each region with its lines, the conditions that contradict and the references left out. Conditions that also cover other systems (Cygwin, MinGW, BSDs) and directory-name guesses never count. See platforms.md (#222).
  • C / C++: per-platform CMake source lists (if(WIN32), if(APPLE), if(CMAKE_SYSTEM_NAME STREQUAL "Linux"), elseif / else, list(APPEND ...), target_sources) decide which targets build a file, replacing the directory-name guess. Read without running CMake; platforms.cmake_membership: false turns it off (#111).
  • platforms.compile_commands in .cg.yaml: one compile_commands.json per target build gives exact C / C++ membership, winning over the CMake lists. Targets without a database stay unknown (#111).
  • Tauri events: emit / emit_to / listen in Rust and emit / listen / once in JS / TS are tauri-event bridge endpoints linked across the process boundary, with no_receiver / no_sender checks; tauri:// built-ins and listeners with computed names are exempt. See bridges.md.
  • Svelte components (.svelte) are indexed through the TypeScript extractor like Vue: <script> blocks plus on*={handler} attributes, with references on the component node.
  • platforms.processes in .cg.yaml declares Electron / Tauri process roles by path glob (main, preload, renderer, webview, core, worker), overriding the inferred role.

Changed

  • A Linux-named file (*_linux.c, linux/) without a build list is unknown on Android instead of absent there.
  • __PASE__ and __illumos__ count as other systems in C conditions.
  • .svelte is no longer listed as unsupported by cg completeness.

Fixed

  • .cg.yaml validation rejected platforms.xcode_membership.

v0.23.6

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 17 Oct 12:15

Added

  • Default arguments in TypeScript / JavaScript, Kotlin, PHP and Python, as already done for Swift: a call inside a parameter's default value is an edge from the declaring function with attrs.default_arg, which impact lists as via default argument without walking it. In TypeScript / JavaScript, Kotlin and PHP (8.1 new in initializers), where the default runs on every call, a resolved call that leaves the argument out (or passes undefined in TS) also gets an edge from the caller to the default's callee with attrs.omits_default and attrs.default_of, so impact on that callee lists the callers that run it. Named and trailing-lambda arguments count; a call with a spread argument gets none. Python evaluates defaults once at def time, so only the declaration edge is marked. See schema.md (#212).
  • docs/validation-log.md records the cg affected hand-check of 20 (file → test) pairs: immich server/ 20/20 after the TypeScript fix below, flask 9/20 (the misses are filed as #221), and the Swift narrowing numbers (#199, #218).

Changed

  • Swift heuristic mode narrows selector candidates: a local assigned from a project function or static factory with a declared project return type (let b = makeBridge(), let s = Store.shared(), -> Self) gets that type, and a selector with only one visible declaring type binds by name instead of as a candidate. private / fileprivate members, and members of a file-private type, count only in their own file; an overload set split across files or across access levels stays visible. Candidate edges drop on most corpora (Kingfisher 206 → 197, SwiftUIX 454 → 441) with exact-mode precision and recall on Alamofire unchanged; numbers in validation-log.md (#218).

Fixed

  • TypeScript: an instance method no longer gets OVERRIDDEN_BY (or IMPLEMENTED_BY) from a base class's static member of the same name, so a base service's static create(...) factory is not an override target of every subclass's create. Found by the cg affected hand-check (#199).

v0.23.5

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 17 Oct 11:47

Changed

  • Laravel API routes are stored and shown at their served path: a route in routes/api.php (or routes/api/) served at /api/v1/orders is route:POST /api/v1/orders in cg routes, route ids, route: specs and the viz, instead of POST /v1/orders. The prefix is read from bootstrap/app.php withRouting(api:, apiPrefix:), from route files mounted in its then: closure (Route::prefix('webhooks')->group(base_path('routes/hooks.php'))) and from a Laravel ≤ 10 RouteServiceProvider group; api when nothing configures it. web.php stays unprefixed. Routes keep attrs.declared_uri and attrs.prefix_from. A route: spec with the declared path still resolves, with note: declared path; served as POST /api/v1/orders. cg link and cg tests match on the served path; the hard-coded /api variant is used only for the default prefix and for graphs built by older versions. This affects saved plans, baselines and scripts that name Laravel API routes by id: re-index, and update ids that should show the served path (#191).
  • routes --writes drops writes a route reaches only through overrides its receivers cannot dispatch to, with the same receiver narrowing as downstream (#120): its direct tables now agree with downstream of the route. A write reached only through an override hop whose receiver type is unknown is still listed (#149).

Added

  • Laravel: routes registered in loops over literal data are unrolled: foreach over a literal list or key => value array (or a $var = [...] assigned before it, or a class constant), collect([...])->each/map, array_map / array_walk and an enum's cases(). The loop variables are substituted into the path, name, middleware, ->defaults(), ->where*() and the action, and each route carries attrs.unrolled_from (file, line, binding); cg routes marks it (unrolled: $genre = 'fiction'). A loop over data cg can't evaluate, or with more than 64 combinations, stays a laravel_loop_routes blind spot with the reason (iterable not literal), and unrolled loops are no longer counted there (#184).
  • Laravel routes keep ->defaults() as attrs.defaults and ->where() / whereNumber() / whereUuid() / whereIn() / ... as attrs.where (#184).
  • Nuxt Nitro: object-form handlers (defineEventHandler({ onRequest: [...], handler })) route to the handler function, with onRequest / onBeforeResponse hooks as USES_MIDDLEWARE targets; nitro.handlers / serverHandlers entries in nuxt.config become routes (attrs.registered_in); routeRules with proxy mark the routes they cover (attrs.proxy) or add a proxy route; server/ directories of local layers in extends are scanned; and a server/middleware handler that returns early outside a path prefix applies only to routes under it (heuristic) (#217).

Fixed

  • downstream and impact no longer follow every override after a $this->m() or parent::m() call: $this-> keeps the receiver the path entered the method with (so a queued job's $this->execute() reaches its own class's override, not every sibling's), and parent::m() runs exactly the parent method with no override hop (#148).

v0.23.4

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 17 Oct 09:49

On PyPI as cg-code-graph (pipx install cg-code-graph).

Changed

  • readers / writers of a Swift stored property show each site's platforms= when the property has per-platform declarations, and field:<Type>.<name>@<line> names one of them (#219).

Fixed

  • Swift: a stored property declared again in another #if branch (inside one type, or in a per-branch variant of the type) is its own field:<Type>.<name>@<line> node instead of being dropped, and READS_PROP / WRITES_PROP are narrowed by platform like CALLS (#201): a read made under #if os(macOS) links only the macOS declaration, and code built everywhere links every declaration (#219).

Full changelog: v0.23.3...v0.23.4

v0.23.3

Choose a tag to compare

@cyberchronos00 cyberchronos00 released this 17 Oct 08:59

On PyPI as cg-code-graph (pipx install cg-code-graph).

Added

  • Swift: a call that leaves out a defaulted argument gets its own CALLS edge to what the default calls, at the call site, with attrs.omits_default (the parameter label) and attrs.default_of. impact on the default's callee lists the callers that omit the argument (through an omitted default argument) and not those that pass it; callers labels them (runs the default of gamut: in render) and the declaration edge (parameter default gamut:) (#212, Swift part).

Changed

  • impact and callers count selector-only candidates (receiver type unknown, #83) apart from resolved callers: transitive callers: 3 (+1 candidate), direct callers: 2 (2 sites); candidate callers: 1 (1 site, ...), and callers_resolved / callers_candidate in the JSON. Candidates show their fan-out ((candidate, 1 of 3 methods named refreshStatus), candidate_of in JSON) and are left out of callers by module. A caller without a module (a free function) is grouped as <file> (top level) instead of ? (#204).
  • MCP node prints neighbours as <id> :<line>, so a per-platform variant reads refreshStatus@24 :12 instead of refreshStatus@24@12 (#201).
  • tree-sitter-swift 0.7.4 is supported (pin lifted to <0.8): !Type.method() parses with the operator around the callee and is unwrapped; members after an unknown attribute macro parse without recovery. Every Swift fixture gives the same graph on 0.7.3 and 0.7.4; corpus differences are listed in docs/validation-log.md (#199, part).

Fixed

  • Swift #if / #else variants: a reference made where only some targets build (a macOS-only caller or call) links only the per-platform definitions built on one of those targets, so a macOS-only call no longer reaches the #else stub and the stub no longer looks reached by macOS-only code. A reference whose definition is not built anywhere the site is moves to the sibling definition that is, and a #else type with no init of its own uses its implicit initializer instead of being bound to a sibling variant's init, so platform_divergence reports no false missing callee. References from code built everywhere still link every definition. callers counts a site linked to several variants once (#201).
  • Swift exact mode keeps the default_arg mark on a parameter default's call, so impact does not walk the declaring function as an unconditional caller (#208).

Full changelog: v0.23.2...v0.23.3