Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions .github/scripts/test_check_tree_sitter_upgrade_readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ def _physical_vendor_grammars() -> set[str]:
def _render_report() -> tuple[str, int]:
"""Run main() with network mocked to mirror PRODUCTION; return (md, exit_code).

- npm grammars resolve to a permissive "Ready" peer dep, so the ONLY blocker
left is the held vendored tree-sitter-c — letting us assert the hold is
load-bearing (exit code stays non-zero because of it).
- npm grammars resolve to a permissive "Ready" peer dep, so the only blockers
left are the held vendored grammars (tree-sitter-c, tree-sitter-kotlin) plus
the intentionally-pinned tree-sitter-cpp — letting us assert holds are
load-bearing (exit code stays non-zero because of them).
- npm_view_json records its calls so we can prove vendored grammars are never
npm-queried.
- fetch_text mirrors the real workflow: upstream parser.c resolves to a real
Expand Down Expand Up @@ -363,13 +364,15 @@ def test_issue_update_summary_regex_matches_current_report(self):
# Counts are derived from _render_report()'s mock corpus (all npm peer
# deps mocked permissive): of the 10 npm-installed grammars, 9 render
# Ready and 1 — tree-sitter-cpp — is the intentional pin (#1242), so it is
# not counted ready. The 2 blockers are that same pinned tree-sitter-cpp
# plus the vendored, ABI-held tree-sitter-c (the only out-of-range
# vendored grammar). If a grammar is added/removed or a pin/hold changes,
# not counted ready. The 3 blockers are that same pinned tree-sitter-cpp
# plus two held vendored grammars: ABI-held tree-sitter-c (#1242/#858) and
# tree-sitter-kotlin (pinned to an unreleased fwcd main commit for `fun
# interface` support — ABI 14 is in range, but a hold counts as a blocker
# until it is lifted). If a grammar is added/removed or a pin/hold changes,
# update _render_report()'s mock AND these expected counts together; a
# mismatch here means the report prose drifted, not the regex.
self.assertEqual(ready.groups(), ("9", "10"))
self.assertEqual(blockers.group(1), "2")
self.assertEqual(blockers.group(1), "3")

def _matrix_row(self, name: str) -> str:
for line in self.report.splitlines():
Expand Down
3 changes: 2 additions & 1 deletion .github/vendored-grammars.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
},
"kotlin": {
"name": "tree-sitter-kotlin",
"upstream": { "npm": "tree-sitter-kotlin" }
"upstream": { "npm": "tree-sitter-kotlin" },
"hold": "pinned to unreleased fwcd main commit c8ac3d26 for `fun interface` support (fwcd/tree-sitter-kotlin#169, closes #87) — npm latest (0.3.8) lacks the fix, so the monitor must NOT auto-revert (isNewer is strict-inequality: 0.3.8 != 0.4.0). Drop this hold and bump when upstream cuts a release that includes the fix"
},
"dart": {
"name": "tree-sitter-dart",
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/build-tree-sitter-prebuilds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ name: Build tree-sitter prebuilds
# REQUIRED grammar)
# - tree-sitter-dart (vendored source; built from gitnexus/vendor/)
# - tree-sitter-proto (vendored source; built from gitnexus/vendor/)
# - tree-sitter-kotlin (vendored source; built from the published npm package —
# upstream ships source only)
# - tree-sitter-kotlin (vendored source; built from gitnexus/vendor/ — pinned to
# an unreleased main commit for `fun interface` support
# (#169) that no npm release carries yet)
# - tree-sitter-swift (vendored source; built from gitnexus/vendor/ — its
# prebuilds were originally upstream-shipped, now
# GitNexus-cross-built like the rest for uniformity)
Expand Down Expand Up @@ -74,7 +75,9 @@ on:
- 'gitnexus/vendor/tree-sitter-proto/package.json'
- 'gitnexus/vendor/tree-sitter-kotlin/package.json'
- 'gitnexus/vendor/tree-sitter-swift/package.json'
# Transition window: kotlin's pin still lives here until it is vendored.
# Self-test: re-run the guard if a future grammar pin is reintroduced in
# the main package.json (optionalDependencies fallback). No-op otherwise —
# all five grammars are now fully vendored (kotlin included).
- 'gitnexus/package.json'
# Self-test: re-run the guard (normally a no-op) when the recipe changes.
- '.github/workflows/build-tree-sitter-prebuilds.yml'
Expand Down Expand Up @@ -135,7 +138,12 @@ jobs:
c: { name: 'tree-sitter-c', kind: 'npm' },
dart: { name: 'tree-sitter-dart', kind: 'vendored' },
proto: { name: 'tree-sitter-proto', kind: 'vendored' },
kotlin: { name: 'tree-sitter-kotlin', kind: 'npm' },
// kotlin is vendored WITH its source (parser.c/scanner.c/binding.gyp),
// so it builds from gitnexus/vendor/ like dart/proto/swift. It was
// 'npm' while tracking released versions, but is now pinned to an
// unreleased main commit for `fun interface` support (#169) that no
// npm release carries yet — so it must build from the vendored source.
kotlin: { name: 'tree-sitter-kotlin', kind: 'vendored' },
// swift is vendored WITH its source (parser.c/scanner.c/binding.gyp),
// so it builds from gitnexus/vendor/ like dart/proto. Its prebuilds
// were originally upstream-shipped; rebuilding them here unifies it.
Expand Down
5 changes: 3 additions & 2 deletions gitnexus/bench/scope-capture/baselines.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@
"_rebaselined": "#1956 synth-widening: + javascript-qualified-base fixture; synthesizeJsInheritanceReferences now handles a member_expression base (class S extends ns.Base -> Base), matching the #1940 legacy leg + the TS terminalTsTypeNameNode property_identifier case, at parity. Linear (~1.05). | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged."
},
"kotlin": {
"fingerprint": "90aa832978d9744e50058e77a04748390a7e34e36b309f6c1d178eb07280b7ea",
"fingerprint": "4900431791f2b9280009deb2b82659c26ead8aa6fb8731190a7c505dec5a9041",
"scaling_budget": 1.5,
"_added": "#1951: bench coverage added (was ungated); scale source heritage-bearing (: Base()); js/kotlin O(n^2) findNodeAtRange-per-match fixed to threaded captured node, now linear.",
"_rebaselined": "#1919 review CF3 fix: extended kotlin-local-property-owner (init/accessor destructuring) + new dart-accessor-owner fixture (getter/setter ownership). Fingerprint-only corpus drift; scaling ~1.0."
"_rebaselined": "#1919 review CF3 fix: extended kotlin-local-property-owner (init/accessor destructuring) + new dart-accessor-owner fixture (getter/setter ownership). Fingerprint-only corpus drift; scaling ~1.0.",
"_rebaselined_2271": "PR #2271: re-vendored tree-sitter-kotlin 0.3.8 -> unreleased fwcd main c8ac3d26 for `fun interface` support + new kotlin-fun-interface fixture in the corpus. Drift is both corpus-additive (the fixture) and grammar-driven (the new grammar parses `fun interface` as a class_declaration, not an ERROR node). Baselined to the NEW grammar's fingerprint, so this --check passes only once the regenerated prebuilds land — until then CI loads the committed 0.3.8 binary and the bench is red, same as the kotlin fun-interface integration tests. scaling ~0.83 (linear)."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fixtures

// Functional (SAM) interfaces — the `fun interface` modifier.
// Before tree-sitter-kotlin gained `fun interface` support (fwcd #169), the
// vendored 0.3.8 grammar parsed these as an ERROR node and dropped the whole
// declaration, so neither the interface nor its abstract method was extracted.
fun interface Clicker {
fun onClick(id: Int): Boolean
}

fun interface Mapper<T> {
fun map(value: T): String
}

// A regular interface alongside, to confirm both shapes coexist.
interface Plain {
fun plain(): Int
}

class Button : Plain {
override fun plain(): Int = 0

fun bind(clicker: Clicker) {
clicker.onClick(1)
}
}
30 changes: 30 additions & 0 deletions gitnexus/test/integration/resolvers/kotlin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2900,3 +2900,33 @@ describe('F52 — Kotlin companion-object properties', () => {
expect(getNodesByLabel(result, 'Property')).not.toContain('create');
});
});

// ---------------------------------------------------------------------------
// Functional (SAM) interfaces: `fun interface` (vendored grammar bump, fwcd #169)
// ---------------------------------------------------------------------------

describe('Kotlin functional (fun) interfaces', () => {
let result: PipelineResult;

beforeAll(async () => {
result = await runPipelineFromRepo(path.join(FIXTURES, 'kotlin-fun-interface'), () => {});
}, 60000);

// Pre-fix, the 0.3.8 grammar parsed `fun interface` as an ERROR node and
// dropped the declaration, so Clicker/Mapper were never extracted.
it('extracts `fun interface` declarations as Interface nodes alongside a plain one', () => {
expect(getNodesByLabel(result, 'Interface')).toEqual(['Clicker', 'Mapper', 'Plain']);
expect(getNodesByLabel(result, 'Class')).toEqual(['Button']);
});

it('extracts the abstract methods of fun interfaces', () => {
const methods = getNodesByLabel(result, 'Method');
expect(methods).toContain('onClick'); // Clicker (fun interface)
expect(methods).toContain('map'); // Mapper<T> (generic fun interface)
});

it('still resolves heritage on a class implementing a plain interface', () => {
const implements_ = getRelationships(result, 'IMPLEMENTS');
expect(edgeSet(implements_)).toContain('Button → Plain');
});
});
2 changes: 1 addition & 1 deletion gitnexus/test/unit/cli-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe('CLI commands', () => {
// ships source only) and loaded from vendor/ by absolute path (#2111).
expect(optional['tree-sitter-kotlin']).toBeUndefined();
expect(pkg.default.scripts.postinstall).toContain('build-tree-sitter-grammars.cjs');
expect(kotlinPkg.default.version).toBe('0.3.8');
expect(kotlinPkg.default.version).toBe('0.4.0');
// No scripts.install / dependencies inside vendor/ (#836 / #1728 hygiene).
expect(kotlinPkg.default.scripts?.install).toBeUndefined();
expect(kotlinPkg.default.dependencies).toBeUndefined();
Expand Down
10 changes: 7 additions & 3 deletions gitnexus/test/unit/grammar-update-monitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,14 @@ describe('GRAMMARS registry', () => {
expect(mod.GRAMMARS.dart.github).toContain('tree-sitter-dart');
});

it('monitors c but marks it report-only (ABI-pinned hold); the rest are auto-updatable', () => {
it('marks c and kotlin report-only (holds); swift/dart/proto are auto-updatable', () => {
expect(mod.GRAMMARS.c.npm).toBe('tree-sitter-c');
expect(mod.GRAMMARS.c.hold).toBeTruthy(); // detected/reported, never auto-applied
for (const k of ['swift', 'kotlin', 'dart', 'proto']) {
expect(mod.GRAMMARS.c.hold).toBeTruthy(); // ABI-pinned: detected/reported, never auto-applied
// kotlin is pinned to an unreleased fwcd main commit for `fun interface`
// support (#169); npm latest (0.3.8) lacks it, so the strict-inequality
// isNewer would auto-revert the pin without this hold.
expect(mod.GRAMMARS.kotlin.hold).toBeTruthy();
for (const k of ['swift', 'dart', 'proto']) {
expect(mod.GRAMMARS[k].hold).toBeUndefined();
}
});
Expand Down
55 changes: 36 additions & 19 deletions gitnexus/vendor/tree-sitter-kotlin/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
## GitNexus vendor notice

This directory is a GitNexus-managed minimal **runtime** package derived from
`tree-sitter-kotlin@0.3.8` (fwcd). It carries only what the runtime needs:
`bindings/node/`, `src/node-types.json`, `LICENSE`, and the native
`prebuilds/`. The C source (`parser.c`, `scanner.c`, `binding.gyp`) is **not**
vendored — `parser.c` alone is ~23 MB, and the prebuilds are produced from the
published npm package, so committing the source would bloat git history for no
runtime benefit.
`tree-sitter-kotlin` (fwcd), pinned to the **unreleased `main` commit
[`c8ac3d26`](https://github.com/fwcd/tree-sitter-kotlin/commit/c8ac3d2627240160b999a2c100de3babbdb8f419)**
(`package.json` version `0.4.0`). It is pinned to `main` rather than
a tagged release because the latest release, `0.3.8` (tagged 2024-08-03),
predates `fun interface` (functional/SAM interface) support: it parsed
`fun interface Foo` as an `ERROR` node and dropped the declaration. That fix
landed in [PR #169](https://github.com/fwcd/tree-sitter-kotlin/pull/169)
(closing [issue #87](https://github.com/fwcd/tree-sitter-kotlin/issues/87)),
merged into `main` 2025-04-25 but **not yet in any npm release**.

It carries `bindings/node/`, `LICENSE`, the native `prebuilds/`, and the full C
source — `src/parser.c`, `src/scanner.c`, `src/node-types.json`,
`src/tree_sitter/`, and `binding.gyp`. The source IS vendored (despite the
~33 MB generated `parser.c`) for two reasons: it lets `build-tree-sitter-grammars.cjs`
source-build the binding on a toolchain host when no prebuild matches, and —
because the pinned commit is unreleased on npm — it is the source the prebuild
workflow itself compiles from (see below).

### Why this is vendored (unlike the npm grammars)

Expand All @@ -18,25 +29,31 @@ prebuilds itself and vendors them here. `node-gyp-build` selects the correct
binary at require time; `build-tree-sitter-grammars.cjs` activates the binding
(prefer prebuild, else source-build) at install time.

`tree-sitter-swift` is handled the same way now: its prebuilds were originally
**copied from upstream** (Swift ships them), but it is unified with this pipeline —
its source is vendored and its prebuilds are **GitNexus-cross-built** too, so all
of Dart/Proto/Swift/Kotlin go through one uniform build path.
`tree-sitter-swift` is handled the same way: its source is vendored and its
prebuilds are **GitNexus-cross-built** from that vendored source. Kotlin now
uses this exact path too (workflow registry `kind: 'vendored'`, switched from
`'npm'` when this pin moved to an unreleased commit), so all of
Dart/Proto/Swift/Kotlin go through one uniform `kind: 'vendored'` build.

### Updating this vendor package

1. Bump the upstream version: update `version` in `package.json` (this is the
value the `build-tree-sitter-prebuilds` workflow diffs to decide whether to
rebuild) and refresh `_vendoredBy`.
2. Refresh `bindings/node/*` and `src/node-types.json` from the new upstream
`tree-sitter-kotlin` npm release.
1. Bump the pin: update `version` in `package.json` (this is the value the
`build-tree-sitter-prebuilds` workflow diffs to decide whether to rebuild)
and refresh `_vendoredBy` with the new ref.
2. Refresh `bindings/node/*`, `src/parser.c`, `src/scanner.c`,
`src/node-types.json`, `src/tree_sitter/*`, and `binding.gyp` from the new
upstream ref (a release tag, or — as now — a pinned `main` commit). For a
pinned commit the generated `parser.c` is committed upstream, so copy it
directly; if you re-pin to a ref that does not commit `parser.c`, regenerate
it with `tree-sitter generate` first.
3. Regenerate the six native prebuilds by running the
**`build-tree-sitter-prebuilds`** GitHub Actions workflow (it builds
`{linux,darwin,win32}-{x64,arm64}` from the published package and opens a PR
committing them under `prebuilds/`).
`{linux,darwin,win32}-{x64,arm64}` from this vendored source and opens a PR
committing them under `prebuilds/`). While `kind: 'vendored'`, the workflow
does NOT touch npm for kotlin.
4. Verify the packed GitNexus tarball can `require('tree-sitter-kotlin')` and
parse a Kotlin snippet on each target platform-arch (the workflow's validate
step does this in CI).
parse a Kotlin snippet (including a `fun interface`) on each target
platform-arch (the workflow's validate step does this in CI).

> Note: `darwin-x64` prebuilds depend on GitHub's `macos-15-intel` image, whose
> x86_64 macOS runners sunset ~Aug 2027. After that, darwin-x64 needs
Expand Down
6 changes: 5 additions & 1 deletion gitnexus/vendor/tree-sitter-kotlin/bindings/node/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const root = require("path").join(__dirname, "..", "..");

module.exports = require("node-gyp-build")(root);
module.exports =
typeof process.versions.bun === "string"
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-kotlin.node`)
: require("node-gyp-build")(root);

try {
module.exports.nodeTypeInfo = require("../../src/node-types.json");
Expand Down
4 changes: 2 additions & 2 deletions gitnexus/vendor/tree-sitter-kotlin/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "tree-sitter-kotlin",
"version": "0.3.8",
"version": "0.4.0",
"description": "Kotlin grammar for tree-sitter",
"repository": "https://github.com/fwcd/tree-sitter-kotlin",
"license": "MIT",
"main": "bindings/node/index.js",
"types": "bindings/node/index.d.ts",
"_vendoredBy": "gitnexus - runtime package derived from tree-sitter-kotlin@0.3.8 (fwcd). Unlike Swift's upstream-shipped prebuilds, upstream tree-sitter-kotlin ships SOURCE ONLY (no prebuilds/); the native prebuilds/ here are GitNexus-cross-built by .github/workflows/build-tree-sitter-prebuilds.yml. The grammar source (parser.c/scanner.c/binding.gyp + src/) is ALSO vendored so build-tree-sitter-grammars.cjs can source-build the binding on a toolchain host when no prebuild matches (e.g. CI before prebuilds land). The generated parser.c is large (~23 MB on disk; it compresses heavily in git); once the prebuilds cover every platform-arch the source serves only as the fallback. Loaded from vendor/ by absolute path at runtime (vendored-grammars.ts) — NEVER copied to node_modules (#2111) (no scripts.install here — #836/#1728).",
"_vendoredBy": "gitnexus - runtime package derived from tree-sitter-kotlin (fwcd) at unreleased main commit c8ac3d2627240160b999a2c100de3babbdb8f419 (package.json version 0.4.0; latest npm/tag is still 0.3.8). Pinned to main rather than a release to pull in `fun interface` (functional/SAM interface) support — PR fwcd/tree-sitter-kotlin#169, fixing issue #87 — which 0.3.8 (tagged 2024-08-03) lacks: it parsed `fun interface Foo` as an ERROR node and dropped the declaration. Because the fix is unreleased on npm, the prebuild workflow builds kotlin from THIS vendored source (kind 'vendored', like swift), NOT from the npm package. The grammar source (parser.c/scanner.c/binding.gyp + src/) is vendored so build-tree-sitter-grammars.cjs can source-build the binding on a toolchain host when no prebuild matches; the native prebuilds/ are GitNexus-cross-built by .github/workflows/build-tree-sitter-prebuilds.yml (regenerated whenever this `version` changes). The generated parser.c is large (~33 MB on disk; it compresses heavily in git). Loaded from vendor/ by absolute path at runtime (vendored-grammars.ts) — NEVER copied to node_modules (#2111) (no scripts.install here — #836/#1728). To re-pin: bump `version`, refresh src/ + bindings/node/ from the new upstream ref, update this note, and let the prebuild workflow rebuild the binaries.",
"peerDependencies": {
"tree-sitter": "^0.21.0"
},
Expand Down
Loading
Loading