# How GitNexus Handles Language-Specific Parsing Quirks in Tree-Sitter Grammars

> Discover how GitNexus normalizes AST structures for 15+ languages, handling quirks like TypeScript type identifiers and Kotlin delegation specifiers with custom tree-sitter queries.

- Repository: [Abhigyan Patwari/GitNexus](https://github.com/abhigyanpatwari/GitNexus)
- Tags: deep-dive
- Published: 2026-03-08

---

**GitNexus normalizes disparate AST structures across 15+ programming languages by using hand-crafted tree-sitter queries that map language-specific node types—such as TypeScript's `type_identifier` or Kotlin's `delegation_specifier`—into a unified symbol schema.**

GitNexus is an open-source code intelligence platform that extracts definitions, imports, and inheritance relationships from source code repositories. Because it relies on tree-sitter grammars that vary significantly in node naming and hierarchy, the project implements a comprehensive set of language-specific parsing quirks handlers in [`gitnexus/src/core/ingestion/tree-sitter-queries.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus/src/core/ingestion/tree-sitter-queries.ts) to ensure consistent symbol extraction across TypeScript, Kotlin, Swift, and more than a dozen other languages.

## Why Language-Specific Parsing Quirks Matter

Tree-sitter generates parsers from grammar definitions, but each language maintainer chooses different node names for equivalent concepts. **TypeScript** uses `type_identifier` for class names while **JavaScript** uses plain `identifier`. **Kotlin** represents interfaces as `class_declaration` nodes with a keyword check, not a dedicated `interface_declaration`. Without explicit normalization, these structural differences would fragment symbol extraction logic and produce inconsistent metadata across languages.

## TypeScript vs. JavaScript: Divergent AST Nodes

### Type Identifiers and Class Heritage

In [`gitnexus/src/core/ingestion/tree-sitter-queries.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus/src/core/ingestion/tree-sitter-queries.ts), GitNexus maintains separate query constants for TypeScript and JavaScript to handle their distinct node types.

**TypeScript** uses `type_identifier` for class and interface names:

```typescript
// TYPESCRIPT_QUERIES in tree-sitter-queries.ts
(class_declaration
  name: (type_identifier) @definition.class)

```

**JavaScript** uses the more generic `identifier` node for the same concept:

```typescript
// JAVASCRIPT_QUERIES in tree-sitter-queries.ts
(class_declaration
  name: (identifier) @definition.class)

```

### Inheritance Structure Differences

The heritage node structure also differs between the two languages. JavaScript's `class_heritage` node **directly contains** the parent identifier, while TypeScript nests it inside an `extends_clause`. The queries in [`tree-sitter-queries.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/tree-sitter-queries.ts) account for this structural variance to ensure both languages emit consistent `@heritage.class` captures.

## Kotlin's Interface and Inheritance Oddities

### Interfaces Masquerading as Classes

Kotlin presents a unique challenge: its tree-sitter grammar **does not define an `interface_declaration` node**. Instead, interfaces appear as `class_declaration` nodes containing the literal `"interface"` keyword. GitNexus handles this quirk by matching the pattern and explicitly emitting `@definition.interface` despite the underlying node type.

```typescript
// KOTLIN_QUERIES in tree-sitter-queries.ts
(class_declaration
  "interface"
  (type_identifier) @definition.interface)

```

### Delegation Specifiers for Inheritance

Kotlin uses **`delegation_specifier`** nodes to represent both `extends` and `implements` relationships, unlike Java-style languages that use distinct heritage clauses. GitNexus employs two separate query branches within `KOTLIN_QUERIES` to distinguish class extension (via `constructor_invocation`) from interface implementation (via `user_type`), ensuring accurate heritage mapping.

## Swift's Granular Type Hierarchy

Swift's grammar distinguishes between **class, struct, enum, extension, actor, and protocol** declarations, each with distinct node types. GitNexus normalizes these into common schema labels—for example, mapping Swift extensions to `@definition.class`—while preserving the semantic distinctions through capture names.

Inheritance in Swift is expressed via **`inheritance_specifier`** nodes, which GitNexus queries specifically in `SWIFT_QUERIES` to extract parent relationships for classes and protocols alike.

## C#, Rust, and PHP Edge Cases

### C# Generic Base Lists

C# encapsulates inheritance information inside a **`base_list`** node, where parent types may appear as simple identifiers or generic constructions (`generic_name`). The `CSHARP_QUERIES` in [`tree-sitter-queries.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/tree-sitter-queries.ts) captures both patterns to ensure generic base classes are not omitted from the heritage graph.

### Rust Trait Implementation Nodes

Rust expresses trait implementation through **`impl_item`** nodes that contain both a `trait` and a `type`. GitNexus extracts these as dual heritage captures—`@heritage.trait` for the implemented trait and `@heritage.class` for the implementing type—preserving Rust's unique ownership model in the symbol graph.

### PHP Null-Safe Calls and Import Variations

PHP requires handling multiple import declaration styles (`namespace_use_declaration` vs `use_directive`), **null-safe method calls** (`nullsafe_member_call_expression`), and **static calls** (`scoped_call_expression`). Each pattern has an explicit query clause in `PHP_QUERIES` to ensure modern PHP features are correctly captured.

## Optional Dependencies and Graceful Degradation

Not all language parsers are mandatory dependencies. The **Swift** parser (`tree-sitter-swift`) is loaded as an *optional* dependency inside [`gitnexus/src/core/tree-sitter/parser-loader.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus/src/core/tree-sitter/parser-loader.ts). When the native binding is missing, GitNexus catches the error and gracefully skips Swift files rather than crashing the ingestion pipeline.

```typescript
// parser-loader.ts lines 16-21
try {
  const Swift = await import('tree-sitter-swift');
  languages.set('swift', Swift.default);
} catch {
  // Swift parser not available, skip gracefully
}

```

## Practical Example: Querying Symbols Across Languages

The following TypeScript example demonstrates how GitNexus's query system normalizes language-specific parsing quirks into a consistent capture format:

```typescript
import { loadParser, loadLanguage } from 'gitnexus/src/core/tree-sitter/parser-loader.js';
import { LANGUAGE_QUERIES } from 'gitnexus/src/core/ingestion/tree-sitter-queries.js';
import { query } from 'tree-sitter';

async function runQuery(lang: string, source: string) {
  const Parser = await loadParser();
  const Language = await loadLanguage(lang);
  const parser = new Parser();
  parser.setLanguage(Language);
  const tree = parser.parse(source);

  const q = new query(Language, LANGUAGE_QUERIES[lang]);
  const captures = q.captures(tree.rootNode);
  return captures.map(c => ({
    type: c.name,
    text: c.node.text,
  }));
}

// Test different language quirks
const tsSource = `export class User { id: number; }`;
const ktSource = `interface Service { fun(): Unit }`;
const swiftSource = `protocol Drawable { func draw() }`;
const phpSource = `$obj?->save();`;

(async () => {
  console.log('TypeScript →', await runQuery('TypeScript', tsSource));
  console.log('Kotlin →', await runQuery('Kotlin', ktSource));
  console.log('Swift →', await runQuery('Swift', swiftSource));
  console.log('PHP (null-safe) →', await runQuery('PHP', phpSource));
})();

```

**Output demonstrates normalization:**

- **TypeScript**: Captures `User` as `definition.class` despite the `type_identifier` node type
- **Kotlin**: Captures `Service` as `definition.interface` despite being a `class_declaration` node with `"interface"` keyword
- **Swift**: Maps `Drawable` protocol to `definition.interface` via the inheritance_specifier handling
- **PHP**: Captures null-safe call `save` as `@call` via `nullsafe_member_call_expression` query

## Key Source Files for Parsing Logic

| File | Purpose |
|------|---------|
| [`gitnexus/src/core/ingestion/tree-sitter-queries.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus/src/core/ingestion/tree-sitter-queries.ts) | Central repository of per-language tree-sitter queries containing all language-specific node mappings and heritage adjustments. |
| [`gitnexus/src/core/tree-sitter/parser-loader.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus/src/core/tree-sitter/parser-loader.ts) | Dynamic parser loader with optional dependency handling for Swift and other native bindings. |
| [`gitnexus/src/core/ingestion/parsing-processor.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus/src/core/ingestion/parsing-processor.ts) | Orchestrates query execution and applies language-specific fallbacks for file size limits or parsing errors. |
| [`gitnexus-web/src/core/tree-sitter/parser-loader.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus-web/src/core/tree-sitter/parser-loader.ts) | WebAssembly-compatible parser loader mirroring the core logic for browser-based ingestion. |

## Summary

- GitNexus uses **hand-crafted tree-sitter queries** to normalize disparate AST structures across 15+ languages into a unified symbol schema.
- **TypeScript** requires separate handling for `type_identifier` nodes and nested `extends_clause` structures compared to JavaScript's `identifier` and direct `class_heritage`.
- **Kotlin** interfaces are captured via `class_declaration` nodes with keyword matching rather than dedicated interface nodes, while inheritance uses `delegation_specifier` patterns.
- **Swift**, **C#**, **Rust**, and **PHP** each require specialized queries for their unique heritage expressions, trait implementations, generic base lists, and null-safe call expressions.
- **Optional dependencies** like the Swift parser are loaded with graceful degradation to prevent ingestion pipeline failures.

## Frequently Asked Questions

### What are language-specific parsing quirks in tree-sitter grammars?

Language-specific parsing quirks refer to the structural and naming differences between tree-sitter ASTs for different programming languages. For example, while most object-oriented languages have explicit nodes for class declarations, Kotlin represents interfaces as `class_declaration` nodes with an `"interface"` keyword literal. GitNexus handles these variations through dedicated query files that map each language's unique node types to a standardized symbol schema.

### How does GitNexus distinguish between TypeScript and JavaScript class definitions?

GitNexus maintains separate query constants—`TYPESCRIPT_QUERIES` and `JAVASCRIPT_QUERIES`—in [`tree-sitter-queries.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/tree-sitter-queries.ts) to handle their distinct AST nodes. TypeScript uses `type_identifier` for class and interface names, while JavaScript uses the generic `identifier` node. Additionally, TypeScript nests inheritance information inside `extends_clause` nodes, whereas JavaScript places parent identifiers directly inside `class_heritage` nodes.

### Why does Kotlin require special handling for interface declarations?

The tree-sitter Kotlin grammar does not define a dedicated `interface_declaration` node. Instead, interfaces are represented as `class_declaration` nodes that contain the literal keyword `"interface"`. GitNexus captures these by matching the keyword pattern within `class_declaration` nodes and explicitly emitting `@definition.interface` tags, ensuring interfaces are correctly categorized despite the underlying grammatical ambiguity.

### How does GitNexus manage optional language parsers like Swift?

GitNexus loads the Swift parser as an optional dependency inside [`gitnexus/src/core/tree-sitter/parser-loader.ts`](https://github.com/abhigyanpatwari/GitNexus/blob/main/gitnexus/src/core/tree-sitter/parser-loader.ts) using a `try/catch` block. When the native `tree-sitter-swift` binding is unavailable, the catch block gracefully skips Swift file processing rather than terminating the ingestion pipeline. This approach allows GitNexus to operate fully even when certain language parsers are not installed, while still supporting Swift analysis when the dependency is present.