Skip to content

Comments

feat(domain): structured container semantics for multi-language parser#43

Closed
phodal wants to merge 6 commits intomasterfrom
feature/structured-container-semantics
Closed

feat(domain): structured container semantics for multi-language parser#43
phodal wants to merge 6 commits intomasterfrom
feature/structured-container-semantics

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Jan 18, 2026

Summary

Implements Issue #41 - structured improvements to chapi-domain for multi-language parser support.

Changes Included

1. Container Semantics (Part A)

  • Added ContainerKind enum for classifying containers (SourceFile, Module, Config, etc.)
  • Added Language field to identify the source language
  • Added NamespacePath for namespace/module path representation
  • Added DeclaredPackage vs ResolvedModulePath distinction

2. Top-Level Support (Part B)

  • Added TopLevelScope structure for file-scope declarations
  • Provides alternative to the default node hack

3. Import/Export Semantics (Part C)

  • Added CodeImportSpec with structured import representation
  • Added CodeExportSpec with structured export representation
  • Supports: default, named, namespace, star, re-export patterns
  • Added typeOnly flag for TypeScript type-only imports

4. Type System (Part D) ⬅️ New in this update

  • Added CodeTypeRef for structured type representation
  • Supports cross-language type constructs:
    • Generic/template types with recursive arguments
    • Union/intersection types (TypeScript)
    • Pointer/reference types (C/C++/Go/Rust)
    • Go-specific: slice, map, channel
    • Rust-specific: trait bounds, lifetimes
    • Function types, tuples, nullable types
  • Added TypeRef field to CodeField, CodeProperty
  • Added ReturnTypeRef field to CodeFunction
  • All changes are backward compatible (existing string fields preserved)

Files Changed

chapi-domain/src/main/kotlin/chapi/domain/core/
├── CodeContainer.kt       (Part A, B)
├── CodeImport.kt          (Part C)
├── CodeExport.kt          (Part C)
├── CodeTypeRef.kt         (Part D - NEW)
├── CodeField.kt           (Part D - TypeRef added)
├── CodeProperty.kt        (Part D - TypeRef added)
└── CodeFunction.kt        (Part D - ReturnTypeRef added)

Test Plan

  • All existing tests pass (no breaking changes)
  • New tests for CodeTypeRef (47 tests)
  • Tests cover all type kinds and cross-language scenarios
  • Full build passes

Next Steps

Parser implementations need to be updated to populate the new structured fields:

Parser Priority Status
TypeScript P0 Pending
Rust P0 Pending
Go P0 Pending
Java P1 Pending
Python P1 Pending
C/C++ P1 Pending
C# P1 Pending
Kotlin P1 Pending

Closes #41

Summary by CodeRabbit

Release Notes

  • Chores

    • Updated artifact publishing to Sonatype Central Portal with enhanced GPG signing support.
  • New Features

    • Enhanced language parsers across all supported languages (Java, Kotlin, Python, Go, Rust, TypeScript, Scala, C/C++, C#, Protobuf, Thrift) with structured import and export metadata tracking.
    • Introduced a structured type reference system enabling improved cross-language type analysis and comparison capabilities.

✏️ Tip: You can customize this high-level summary in your review settings.

Add helper to read Maven settings.xml for Sonatype credentials and configure Nexus publishing with new Central Portal endpoints. Also ensure kotlinSourcesJar depends on ANTLR tasks.
Replace direct key/password configuration with GPG agent for more secure signing. Fallback to traditional method if GPG agent is not configured.
…larity

This commit addresses the semantic mixing issue where `PackageName` had
different meanings across languages (package/namespace/module/config table).

Changes:
- Add `ContainerKind` enum to distinguish container types:
  - SOURCE_FILE, PACKAGE, NAMESPACE, MODULE, CRATE, CONFIG, BUILD_SCRIPT, IDL
- Add new structured fields to `CodeContainer`:
  - `Language`: the programming language (java, typescript, rust, etc.)
  - `Kind`: semantic container type
  - `DeclaredPackage`: explicitly declared package/namespace in source
  - `ResolvedModulePath`: path-derived module name (for Rust, Python, TS)
  - `NamespacePath`: namespace hierarchy as list (for C#, C++, TS)
- Update all language parsers to fill the new fields:
  - Java/Kotlin/Go/Scala: DeclaredPackage from package declaration
  - Rust/Python/TypeScript: ResolvedModulePath from file path
  - C#/C++/TypeScript: NamespacePath for namespace hierarchies
  - Toml: Kind=CONFIG
  - CMake: Kind=BUILD_SCRIPT
  - Protobuf/Thrift: Kind=IDL

Backward compatibility:
- Legacy `PackageName` field is preserved and still populated
- New fields have sensible defaults (empty strings, UNKNOWN kind)

Closes #41
This commit addresses Issue #41 problem 3: CodeImport/CodeExport being
too "string-based" to accurately represent different language import styles.

Changes to CodeImport:
- Add `ImportKind` enum: UNKNOWN, DEFAULT, NAMED, NAMESPACE, SIDE_EFFECT,
  STATIC, WILDCARD, RELATIVE, TYPE_ONLY, DOT
- Add `ImportSpecifier` data class with OriginalName, LocalName, IsTypeOnly
- New fields: Kind, Specifiers, DefaultName, NamespaceName, IsTypeOnly, PathSegments

Changes to CodeExport:
- Add `ExportKind` enum: UNKNOWN, DEFAULT, NAMED, RE_EXPORT_ALL,
  RE_EXPORT_NAMED, TYPE_ONLY
- Add `ExportSpecifier` data class with LocalName, ExportedName, IsTypeOnly
- New fields: Kind, Specifiers, FromSource, IsTypeOnly

Parser updates:
- TypeScript: full support for default/named/namespace/side-effect imports,
  default/named/re-export exports with specifiers
- Rust: PathSegments for use paths, WILDCARD for glob imports
- Python: NAMED/RELATIVE/WILDCARD imports with specifiers
- Java: STATIC/WILDCARD/NAMED imports with specifiers
- Scala: NAMED/WILDCARD imports with specifiers
- Go: DOT/SIDE_EFFECT/NAMED imports with specifiers

Backward compatibility:
- All legacy fields (Source, AsName, UsageName, Name, SourceFile) preserved
- New fields have sensible defaults

Partially addresses #41 (problem 3 of 5)
- Improve import classification for Java static imports, Rust relative imports, and Scala import specifiers
- Fix Python module path resolution to handle file extensions correctly
- Add namespace tracking for C++ AST
- Update Sonatype snapshot repository URL and server ID filtering
Implements Issue #41 Part 4 - structured type system to replace
string-based TypeType/ReturnType fields.

New CodeTypeRef class supports:
- Cross-language type representation (TypeScript, Rust, Go, C/C++, Java, etc.)
- Generic/template types with recursive type arguments
- Union/intersection types (TypeScript)
- Pointer/reference types (C/C++/Go/Rust)
- Go-specific: slice, map, channel types
- Rust-specific: trait bounds, lifetimes
- Function types, tuple types, nullable types
- Factory methods for common type patterns

Updated domain classes (backward compatible):
- CodeField: added optional TypeRef field
- CodeProperty: added optional TypeRef field
- CodeFunction: added optional ReturnTypeRef field

Existing string fields (TypeType, ReturnType) are preserved for
backward compatibility. Parser implementations can incrementally
adopt the new structured fields.

Includes 47 comprehensive tests covering all type kinds and
cross-language scenarios.
Copilot AI review requested due to automatic review settings January 18, 2026 06:56
@coderabbitai
Copy link

coderabbitai bot commented Jan 18, 2026

📝 Walkthrough

Walkthrough

This PR introduces comprehensive structural improvements to the chapi-domain core models and all language-specific AST parsers to enable true multi-language code analysis. It adds semantic enums (ContainerKind, ImportKind, ExportKind), introduces CodeTypeRef for cross-language type representation, and expands all domain models with structured metadata fields while preserving backward compatibility. All 14+ language listeners are updated to populate the new fields (Language, Kind, DeclaredPackage, import/export specifiers, type references). Build configuration is modernized for Sonatype Central Portal publishing.

Changes

Cohort / File(s) Summary
Build Configuration
build.gradle.kts
Replaced legacy Maven Central endpoints with Sonatype Central Portal; introduced GPG signing flow with fallback to keyId/password; added Maven settings.xml credential reader; configured Nexus publishing with 3-minute timeouts; extended ANTLR task dependencies for source JAR generation
Domain Models — Core Types
chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt
Added ContainerKind enum (SOURCE_FILE, MODULE, NAMESPACE, PACKAGE, CRATE, CONFIG, BUILD_SCRIPT, IDL); introduced Language, Kind, DeclaredPackage, ResolvedModulePath, NamespacePath fields; retained PackageName for backward compatibility
Domain Models — Imports
chapi-domain/src/main/kotlin/chapi/domain/core/CodeImport.kt
Added ImportKind enum (Named, Default, Namespace, SideEffect, Static, Wildcard, Relative, TypeOnly, Dot); introduced ImportSpecifier data class for (OriginalName, LocalName) mapping; enriched CodeImport with Kind, Specifiers, DefaultName, NamespaceName, IsTypeOnly, PathSegments fields
Domain Models — Exports
chapi-domain/src/main/kotlin/chapi/domain/core/CodeExport.kt
Added ExportKind enum (Named, Default, RE_EXPORT_ALL, RE_EXPORT_NAMED, TypeOnly); introduced ExportSpecifier for (LocalName, ExportedName) mapping; expanded CodeExport with Kind, Specifiers, FromSource, IsTypeOnly fields
Domain Models — Type System
chapi-domain/src/main/kotlin/chapi/domain/core/CodeTypeRef.kt
New comprehensive type model with TypeRefKind enum (18 variants: Simple, Generic, Array, Pointer, Union, Intersection, Tuple, Function, etc.); CodeTypeRef with 30+ properties supporting generics, pointers, lifetimes, trait bounds, channels; factory methods and helper utilities (isPrimitive, isGeneric, isCollection, flattenUnion)
Domain Models — Field & Function Types
chapi-domain/src/main/kotlin/chapi/domain/core/CodeField.kt, CodeFunction.kt, CodeProperty.kt
Added optional TypeRef field to CodeField and CodeProperty; added optional ReturnTypeRef to CodeFunction; provides structured type references alongside existing string-based types
Domain Tests
chapi-domain/src/test/kotlin/chapi/domain/core/CodeContainerTest.kt, CodeImportTest.kt, CodeTypeRefTest.kt
New comprehensive test suites covering container semantics across Java/Rust/C#/Protobuf; import forms across TypeScript/Rust/Python/Java/Go; CodeTypeRef with 687+ lines covering generics, unions, pointers, lifetimes, serialization, and language-specific scenarios
AST Listeners — Java
chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaBasicIdentListener.kt, JavaFullIdentListener.kt
Extended CodeContainer initialization with Language="java", Kind=SOURCE_FILE; enriched import handling to detect static/wildcard forms, populate Kind, Scope, UsageName, Specifiers, PathSegments; set both PackageName and DeclaredPackage on package declaration
AST Listeners — TypeScript
chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptFullIdentListener.kt
Overhauled import/export handling with structured specifiers; added NamespacePath tracking for nested namespaces; introduced ImportSpecifier and ExportSpecifier for bidirectional mappings; classified imports as NAMED/DEFAULT/NAMESPACE/SIDE_EFFECT; classified exports as NAMED/DEFAULT/RE_EXPORT_NAMED/RE_EXPORT_ALL; expanded CodeContainer with Language, Kind, ResolvedModulePath
AST Listeners — Rust
chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustAstBaseListener.kt
Added Language="rust", Kind=MODULE, ResolvedModulePath to CodeContainer; enhanced import classification (WILDCARD for glob, RELATIVE for crate/self/super paths, NAMED otherwise); populated Kind, PathSegments, Specifiers on CodeImport; structured path analysis for semantic classification
AST Listeners — Python
chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonFullIdentListener.kt
Extended CodeContainer with Language="python", Kind=MODULE, ResolvedModulePath; enriched import handling with Kind (NAMED/WILDCARD/RELATIVE), specifiers for aliased imports, PathSegments; populated ImportSpecifier with OriginalName/LocalName for both import and from-import statements
AST Listeners — Go
chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoFullIdentListener.kt
Added Language="go", Kind=SOURCE_FILE to CodeContainer; populated PackageName and DeclaredPackage from package clause; enhanced import handling to detect kind (NAMED/NAMED with dot/blank), populate AsName, UsageName, PathSegments, Specifiers with OriginalName/LocalName mapping
AST Listeners — Scala
chapi-ast-scala/src/main/kotlin/chapi/ast/scalaast/ScalaFullIdentListener.kt
Added Language="scala", Kind=SOURCE_FILE to CodeContainer; populated DeclaredPackage alongside PackageName; structured import handling to classify (NAMED, WILDCARD), populate Specifiers for named/selector imports, PathSegments; maintained backward compatibility with UsageName/AsName
AST Listeners — Other C-family
chapi-ast-c/src/main/kotlin/chapi/ast/cast/CFullIdentListener.kt, chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt, chapi-ast-csharp/src/main/kotlin/chapi/ast/csharpast/CSharpAstListener.kt
C: Added Language="c", Kind=SOURCE_FILE; C++: Added Language="cpp", Kind=SOURCE_FILE, namespace tracking via NamespacePath; C#: Added Language="csharp", Kind=SOURCE_FILE, NamespacePath with namespace stack management for nested tracking
AST Listeners — Other Specialized
chapi-ast-kotlin/src/main/kotlin/chapi/ast/kotlinast/KotlinBasicIdentListener.kt, chapi-ast-protobuf/src/main/kotlin/chapi/ast/protobuf/Protobuf*.kt, chapi-ast-thrift/src/main/kotlin/chapi/ast/thrift/ThriftFullIdentListener.kt
Kotlin: Added Language="kotlin", Kind=SOURCE_FILE, set DeclaredPackage; Protobuf (2 listeners): Added Language="protobuf", Kind=IDL, set DeclaredPackage on package statement; Thrift: Added Language="thrift", Kind=IDL, set DeclaredPackage on namespace
Other Parsers
chapi-parser-cmake/src/main/kotlin/chapi/parser/cmake/CMakeBasicListener.kt, chapi-parser-toml/src/main/kotlin/chapi/parser/toml/TomlListener.kt
CMake: Added Language="cmake", Kind=BUILD_SCRIPT to container initialization; Toml: Added Language="toml", Kind=CONFIG to root and table containers for consistent metadata tagging

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes

Possibly related PRs

Poem

🐰 Hops through the AST, left and right,
New types and fields now shining bright,
From Java's jars to Rust's safe lands,
Each parser stands with structured hands,
Import specs, exports clear, CodeTypeRef true—
Multi-language dreams now break on through! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes build.gradle.kts changes (Sonatype/Nexus publishing, GPG signing) which are unrelated to the core domain model improvements defined in Issue #41, representing out-of-scope infrastructure/CI changes. Move build system changes (Maven Central endpoints, GPG signing configuration, Nexus publishing) to a separate PR to keep this PR focused on Issue #41 domain model implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 18.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(domain): structured container semantics for multi-language parser' directly matches the main change: introducing structured container fields and enriched domain semantics across multiple language parsers.
Linked Issues check ✅ Passed The PR successfully implements Issue #41's core objectives: adds ContainerKind, Language, NamespacePath, DeclaredPackage, ResolvedModulePath to CodeContainer; introduces CodeTypeRef with TypeRef/ReturnTypeRef fields; structures CodeImport/CodeExport with Kind, Specifiers, and ImportKind/ExportKind enums; updates all parsers (TS, Rust, Go, Java, Kotlin, Python, C#, C, C++, Scala, Protobuf, Thrift, Toml, CMake) to populate new fields while maintaining backward compatibility.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements Part D (Type System) of Issue #41, adding structured container semantics and a comprehensive type system for multi-language parser support. The changes introduce CodeTypeRef for cross-language type representation and enhance CodeContainer, CodeImport, and CodeExport with structured fields while maintaining backward compatibility.

Changes:

  • Added CodeTypeRef with support for generics, unions, pointers, slices, channels, tuples, and language-specific constructs (Rust lifetimes/traits, TypeScript mapped types, Go channels, etc.)
  • Enhanced CodeContainer with Language, ContainerKind, DeclaredPackage, ResolvedModulePath, and NamespacePath fields
  • Updated CodeImport and CodeExport with structured ImportKind/ExportKind, Specifiers, and language-specific fields
  • Updated 14 parsers (TypeScript, Rust, Python, Java, Go, C#, C++, C, Kotlin, Scala, Protobuf, Thrift, TOML, CMake) to populate new structured fields
  • Added comprehensive test coverage with 47 tests for CodeTypeRef and tests for import/export/container semantics
  • Updated build configuration with Sonatype Central Portal migration and GPG signing improvements

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
chapi-domain/src/main/kotlin/chapi/domain/core/CodeTypeRef.kt New 563-line structured type system supporting cross-language type constructs
chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt Added container semantics fields (Language, Kind, namespace paths)
chapi-domain/src/main/kotlin/chapi/domain/core/CodeImport.kt Added structured import representation with ImportKind and Specifiers
chapi-domain/src/main/kotlin/chapi/domain/core/CodeExport.kt Added structured export representation with ExportKind and Specifiers
chapi-domain/src/main/kotlin/chapi/domain/core/CodeField.kt Added TypeRef field for structured type information
chapi-domain/src/main/kotlin/chapi/domain/core/CodeProperty.kt Added TypeRef field for structured type information
chapi-domain/src/main/kotlin/chapi/domain/core/CodeFunction.kt Added ReturnTypeRef field for structured return types
chapi-domain/src/test/kotlin/chapi/domain/core/CodeTypeRefTest.kt Comprehensive 687-line test suite covering all type scenarios
chapi-domain/src/test/kotlin/chapi/domain/core/CodeImportTest.kt Tests for structured import semantics across languages
chapi-domain/src/test/kotlin/chapi/domain/core/CodeContainerTest.kt Tests for container kind and namespace handling
chapi-ast-typescript/* Updated to populate structured import/export/namespace fields
chapi-ast-rust/* Updated for structured imports and container semantics
chapi-ast-python/* Updated for structured imports and module path resolution
chapi-ast-java/* Updated for static/wildcard imports and package declaration
chapi-ast-go/* Updated for dot/blank imports and package handling
chapi-ast-csharp/* Updated for namespace stack tracking
chapi-ast-cpp/* Updated for namespace path tracking
chapi-ast-c/* Updated for language and container kind
chapi-ast-kotlin/* Updated for package declaration tracking
chapi-ast-scala/* Updated for structured import selectors
chapi-ast-protobuf/* Updated for IDL container kind and package declaration
chapi-ast-thrift/* Updated for IDL container kind
chapi-parser-toml/* Updated for config container kind
chapi-parser-cmake/* Updated for build script container kind
build.gradle.kts Sonatype Central Portal migration and GPG signing updates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +76 to +97
// Check for glob import on this specific path
val isGlobPath = path.lastOrNull() == "*"
val head = path.firstOrNull()
val kind = when {
isGlobPath -> ImportKind.WILDCARD
head == "crate" || head == "self" || head == "super" -> ImportKind.RELATIVE
else -> ImportKind.NAMED
}

imports.add(
CodeImport(
Source = path.joinToString("::"),
UsageName = path,
Scope = if (path.first() == "crate") "crate" else "cargo",
AsName = path.last()
Scope = if (head == "crate" || head == "self" || head == "super") "crate" else "cargo",
AsName = path.last(),
// New structured fields
Kind = kind,
PathSegments = path,
Specifiers = listOf(ImportSpecifier(
OriginalName = path.last(),
LocalName = path.last()
))
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wildcard imports should not have Specifiers. When the import path ends with "*" (isGlobPath is true), the code should not create an ImportSpecifier. Wildcard imports represent importing all items from a module, not a specific named import.

Remove the Specifiers assignment for wildcard imports or conditionally create specifiers only when it's not a wildcard import.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +40
codeContainer.PackageName = nsName
codeContainer.DeclaredPackage = nsName
codeContainer.NamespacePath = codeContainer.NamespacePath + nsName
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The namespace handling may not correctly support nested namespaces when multiple namespace definitions exist in the same file. The codeContainer.PackageName is being overwritten each time a new namespace is entered, so only the last namespace name is preserved.

For proper nested namespace support, consider concatenating namespace names (e.g., "A::B" for nested "namespace A { namespace B { } }") or only updating PackageName when the path is empty. The NamespacePath field correctly tracks the stack, but PackageName and DeclaredPackage should represent the full qualified namespace path.

Suggested change
codeContainer.PackageName = nsName
codeContainer.DeclaredPackage = nsName
codeContainer.NamespacePath = codeContainer.NamespacePath + nsName
val newNamespacePath = codeContainer.NamespacePath + nsName
val fullNamespaceName = newNamespacePath.joinToString("::")
codeContainer.PackageName = fullNamespaceName
codeContainer.DeclaredPackage = fullNamespaceName
codeContainer.NamespacePath = newNamespacePath

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +13
private var codeContainer: CodeContainer = CodeContainer(
FullName = fileName,
Language = "python",
Kind = ContainerKind.MODULE,
ResolvedModulePath = fileName.substringBeforeLast('/').substringBeforeLast('\\').replace('/', '.').replace('\\', '.')
)
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ResolvedModulePath calculation may produce incorrect results for files in the current directory. The expression fileName.substringBeforeLast('/').substringBeforeLast('\\') will return empty string for files without path separators, and the subsequent .replace('/', '.').replace('\\', '.') will just convert that empty string.

Consider handling the case where there's no directory component, and also be aware that this assumes a specific directory structure maps to Python package names, which may not always be accurate (Python uses init.py to define packages).

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Jan 18, 2026

Codecov Report

❌ Patch coverage is 82.49097% with 97 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.18%. Comparing base (53139b7) to head (13f764b).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
...i/ast/typescriptast/TypeScriptFullIdentListener.kt 33.33% 54 Missing and 2 partials ⚠️
...kotlin/chapi/ast/javaast/JavaBasicIdentListener.kt 72.72% 5 Missing and 4 partials ⚠️
...in/src/main/kotlin/chapi/domain/core/CodeExport.kt 76.00% 6 Missing ⚠️
...lin/chapi/ast/pythonast/PythonFullIdentListener.kt 89.13% 1 Missing and 4 partials ⚠️
...otlin/chapi/ast/scalaast/ScalaFullIdentListener.kt 80.00% 3 Missing and 2 partials ⚠️
...n/src/main/kotlin/chapi/domain/core/CodeTypeRef.kt 96.89% 2 Missing and 3 partials ⚠️
.../kotlin/chapi/ast/javaast/JavaFullIdentListener.kt 87.87% 3 Missing and 1 partial ⚠️
...main/kotlin/chapi/ast/goast/GoFullIdentListener.kt 87.50% 0 Missing and 3 partials ⚠️
...in/kotlin/chapi/ast/csharpast/CSharpAstListener.kt 86.66% 1 Missing and 1 partial ⚠️
...n/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt 91.66% 0 Missing and 1 partial ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff              @@
##             master      #43      +/-   ##
============================================
+ Coverage     73.20%   74.18%   +0.97%     
- Complexity     1226     1264      +38     
============================================
  Files            69       70       +1     
  Lines          4800     5287     +487     
  Branches        941      984      +43     
============================================
+ Hits           3514     3922     +408     
- Misses          714      776      +62     
- Partials        572      589      +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
chapi-ast-csharp/src/main/kotlin/chapi/ast/csharpast/CSharpAstListener.kt (1)

13-105: Classes in nested namespaces have incomplete package information.

The Package field of classes reflects only the immediate namespace name (e.g., "B"), not the full path (e.g., "A.B"). This occurs because currentNamespace is set to nsName instead of the full namespace stack. Build the full namespace from the stack consistently.

🐛 Proposed fix
-                val nsName = ctx.namespace_declaration().qualified_identifier().text
-                currentNamespace = nsName
-                // Update namespace stack for nested namespaces
-                namespaceStack.add(nsName)
+                val nsName = ctx.namespace_declaration().qualified_identifier().text
+                // Update namespace stack for nested namespaces
+                namespaceStack.add(nsName)
+                val fullNamespace = namespaceStack.joinToString(".")
+                currentNamespace = fullNamespace
                 
                 val container = CodeContainer(
                     FullName = fileName,
-                    PackageName = nsName,
+                    PackageName = fullNamespace,
                     Language = "csharp",
                     Kind = ContainerKind.NAMESPACE,
-                    DeclaredPackage = nsName,
-                    NamespacePath = nsName.split(".")
+                    DeclaredPackage = fullNamespace,
+                    NamespacePath = fullNamespace.split(".")
                 )
-        if (namespaceStack.isNotEmpty()) {
-            namespaceStack.removeAt(namespaceStack.size - 1)
-        }
-        currentNamespace = namespaceStack.lastOrNull() ?: ""
+        if (namespaceStack.isNotEmpty()) {
+            namespaceStack.removeAt(namespaceStack.size - 1)
+        }
+        currentNamespace = namespaceStack.joinToString(".")
chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustAstBaseListener.kt (1)

72-101: Issue: Glob imports create specifiers with "*" as OriginalName/LocalName.

For wildcard imports (use x::*), the specifier is created with OriginalName = "*" and LocalName = "*". This is inconsistent with the typical specifier semantics (mapping a specific name to a local binding). Consider omitting specifiers for glob imports or using an empty list.

🔧 Proposed fix: Skip specifiers for glob imports
                     // New structured fields
                     Kind = kind,
                     PathSegments = path,
-                    Specifiers = listOf(ImportSpecifier(
-                        OriginalName = path.last(),
-                        LocalName = path.last()
-                    ))
+                    Specifiers = if (isGlobPath) {
+                        listOf()  // Glob imports don't have specific specifiers
+                    } else {
+                        listOf(ImportSpecifier(
+                            OriginalName = path.last(),
+                            LocalName = path.last()
+                        ))
+                    }
🤖 Fix all issues with AI agents
In `@build.gradle.kts`:
- Around line 1-3: The XML parser used to read settings.xml must be hardened
against XXE: before calling DocumentBuilderFactory.newDocumentBuilder() (i.e.,
immediately after DocumentBuilderFactory.newInstance()), set the secure features
and properties — enable XMLConstants.FEATURE_SECURE_PROCESSING, disable DOCTYPE
by setting "http://apache.org/xml/features/disallow-doctype-decl" to true,
disable external entities by setting
"http://xml.org/sax/features/external-general-entities" and
"http://xml.org/sax/features/external-parameter-entities" to false, set
XIncludeAware to false and expandEntityReferences to false on the
factory/DocumentBuilder if available, and set the
accessExternalDTD/accessExternalSchema properties to an empty string; do all of
this on the DocumentBuilderFactory instance before invoking newDocumentBuilder()
or parsing settings.xml.
- Around line 113-128: The signing block currently reads env vars into
signingKeyId, signingPassword and signingKey but never wires them into the
Gradle signing plugin, so CI-provided env creds are ignored; update the else
fallback in the signing block to call useInMemoryPgpKeys(signingKey,
signingPassword) when signingKey and signingPassword are present and set
isRequired = true, and if signingKeyId is present ensure the signing.keyId
project property is exposed (so findProperty() can read it) before calling
useInMemoryPgpKeys; keep using useGpgCmd() when signingKeyName is set.

In `@chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt`:
- Around line 35-41: The code currently sets codeContainer.PackageName and
codeContainer.DeclaredPackage directly to the inner namespace in
enterNamespaceDefinition but does not update them when exiting, leaving stale
values; update both enterNamespaceDefinition and exitNamespaceDefinition so that
after modifying codeContainer.NamespacePath you recompute
codeContainer.PackageName and codeContainer.DeclaredPackage from the current
NamespacePath (e.g., set them to the last segment of NamespacePath if present,
or to empty/null when NamespacePath is empty) rather than relying on the
previously captured nsName; ensure the recomputation happens immediately after
pushing in enterNamespaceDefinition and immediately after popping in
exitNamespaceDefinition so scope metadata stays correct.

In
`@chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptFullIdentListener.kt`:
- Around line 43-53: The current enterNamespaceDeclaration sets namespaceName
and appends the whole dotted name as one element to codeContainer.NamespacePath;
change it to split ctx.namespaceName().text on '.' into segments, append each
segment to codeContainer.NamespacePath, and store the segments count (or infer
it) so that exitNamespaceDeclaration drops that many elements (not just one);
update logic around enterNamespaceDeclaration, exitNamespaceDeclaration,
namespaceName and codeContainer.NamespacePath to push segments on enter and pop
the same number of segments on exit.
🧹 Nitpick comments (4)
chapi-parser-toml/src/main/kotlin/chapi/parser/toml/TomlListener.kt (1)

24-29: Consider populating structured package/namespace fields for table containers.

Table names are still only mapped to PackageName. If consumers rely on DeclaredPackage/NamespacePath, consider filling them alongside PackageName (especially for dotted table names).

chapi-domain/src/main/kotlin/chapi/domain/core/CodeTypeRef.kt (1)

352-563: Factory helpers look solid; consider caching primitive/collection sets.

isPrimitive() and isCollection() rebuild sets on every call. Hoisting them to companion-level constants avoids repeated allocations in hot paths.

♻️ Suggested refactor
 companion object {
+    private val PRIMITIVE_NAMES = setOf(
+        // Java/Kotlin
+        "int", "long", "short", "byte", "float", "double", "boolean", "char", "void",
+        "Int", "Long", "Short", "Byte", "Float", "Double", "Boolean", "Char", "Unit",
+        // TypeScript/JavaScript
+        "number", "string", "boolean", "undefined", "null", "void", "never", "any", "unknown",
+        // Rust
+        "i8", "i16", "i32", "i64", "i128", "isize",
+        "u8", "u16", "u32", "u64", "u128", "usize",
+        "f32", "f64", "bool", "str", "String",
+        // Go
+        "int8", "int16", "int32", "int64",
+        "uint8", "uint16", "uint32", "uint64", "uintptr",
+        "float32", "float64", "complex64", "complex128",
+        "rune", "error",
+        // C/C++
+        "size_t", "ssize_t", "ptrdiff_t"
+    ).map { it.lowercase() }.toSet()
+
+    private val COLLECTION_NAMES = setOf(
+        "list", "set", "map", "hashmap", "hashset", "treemap", "treeset",
+        "arraylist", "linkedlist", "vector", "deque", "queue", "stack",
+        "collection", "iterable", "iterator", "sequence",
+        "vec", "btreemap", "btreeset", // Rust
+        "slice" // Go
+    ).map { it.lowercase() }.toSet()
+
     /**
      * Creates a simple type reference with just a name.
      */
     fun simple(name: String): CodeTypeRef = CodeTypeRef(
         raw = name,
         name = name,
         kind = TypeRefKind.SIMPLE
     )
 }
@@
-    fun isPrimitive(): Boolean {
-        val primitives = setOf(
-            // Java/Kotlin
-            "int", "long", "short", "byte", "float", "double", "boolean", "char", "void",
-            "Int", "Long", "Short", "Byte", "Float", "Double", "Boolean", "Char", "Unit",
-            // TypeScript/JavaScript
-            "number", "string", "boolean", "undefined", "null", "void", "never", "any", "unknown",
-            // Rust
-            "i8", "i16", "i32", "i64", "i128", "isize",
-            "u8", "u16", "u32", "u64", "u128", "usize",
-            "f32", "f64", "bool", "str", "String",
-            // Go
-            "int8", "int16", "int32", "int64",
-            "uint8", "uint16", "uint32", "uint64", "uintptr",
-            "float32", "float64", "complex64", "complex128",
-            "rune", "error",
-            // C/C++
-            "size_t", "ssize_t", "ptrdiff_t"
-        )
-        return name.lowercase() in primitives.map { it.lowercase() }
-    }
+    fun isPrimitive(): Boolean {
+        return name.lowercase() in PRIMITIVE_NAMES
+    }
@@
-    fun isCollection(): Boolean {
-        val collectionNames = setOf(
-            "list", "set", "map", "hashmap", "hashset", "treemap", "treeset",
-            "arraylist", "linkedlist", "vector", "deque", "queue", "stack",
-            "collection", "iterable", "iterator", "sequence",
-            "vec", "hashmap", "btreemap", "btreeset", // Rust
-            "slice" // Go
-        )
-        return name.lowercase() in collectionNames || isSlice || kind == TypeRefKind.MAP
-    }
+    fun isCollection(): Boolean {
+        return name.lowercase() in COLLECTION_NAMES || isSlice || kind == TypeRefKind.MAP
+    }
chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonFullIdentListener.kt (1)

58-105: Consider: Relative imports with named specifiers should use NAMED kind.

The current logic assigns ImportKind.RELATIVE for any relative import (from .x import a, b), but these are semantically named imports from a relative path. The RELATIVE kind might be better reserved for imports where the path itself is the primary concern, or you could use both RELATIVE and NAMED information.

As-is, this matches the PR's documented intent for Python, so this is a design consideration rather than a bug.

Python relative import semantics PEP 328
chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoFullIdentListener.kt (1)

43-77: Consider adding specifiers for DOT and SIDE_EFFECT imports.

For consistency, DOT imports (. "pkg") could also have specifiers since they import all exported names. Similarly, SIDE_EFFECT imports could have an empty specifier list explicitly set. Currently, only NAMED imports get specifiers.

This is a minor consistency improvement and not blocking.

♻️ Optional: Add specifiers for all import kinds
         // Add specifier for the imported package
-        if (kind == ImportKind.NAMED) {
+        when (kind) {
+            ImportKind.NAMED -> {
             val originalName = sourceName.substringAfterLast('/')
             val localName = packageAlias ?: originalName
             codeImport.Specifiers = listOf(ImportSpecifier(
                 OriginalName = originalName,
                 LocalName = localName
             ))
+            }
+            ImportKind.DOT -> {
+                // DOT import brings all exports into current namespace
+                codeImport.Specifiers = listOf(ImportSpecifier(
+                    OriginalName = "*",
+                    LocalName = "*"
+                ))
+            }
+            else -> {
+                // SIDE_EFFECT imports have no bindings
+                codeImport.Specifiers = listOf()
+            }
         }

Comment on lines +1 to +3
import java.time.Duration
import org.w3c.dom.Element
import javax.xml.parsers.DocumentBuilderFactory
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate the build.gradle.kts file and examine the imports and DocumentBuilderFactory usage
fd -t f "build.gradle.kts" | head -20

Repository: phodal/chapi

Length of output: 626


🏁 Script executed:

# Once we find it, let's read the full file to understand the context
git ls-files | grep -E "build\.gradle\.kts$"

Repository: phodal/chapi

Length of output: 626


🏁 Script executed:

# Count lines in build.gradle.kts to understand its size
wc -l build.gradle.kts 2>/dev/null || echo "File not found in current directory, searching..."

Repository: phodal/chapi

Length of output: 76


🏁 Script executed:

cat -n build.gradle.kts

Repository: phodal/chapi

Length of output: 11340


Harden DocumentBuilderFactory against XXE in settings.xml parsing.

The DocumentBuilderFactory at lines 212-214 parses ~/.m2/settings.xml without disabling external entity processing or DOCTYPE declarations. Apply XXE hardening before calling newDocumentBuilder():

Proposed fix
-        val factory = DocumentBuilderFactory.newInstance()
+        val factory = DocumentBuilderFactory.newInstance().apply {
+            setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
+            setFeature("http://xml.org/sax/features/external-general-entities", false)
+            setFeature("http://xml.org/sax/features/external-parameter-entities", false)
+            setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
+            isXIncludeAware = false
+            isExpandEntityReferences = false
+        }
🤖 Prompt for AI Agents
In `@build.gradle.kts` around lines 1 - 3, The XML parser used to read
settings.xml must be hardened against XXE: before calling
DocumentBuilderFactory.newDocumentBuilder() (i.e., immediately after
DocumentBuilderFactory.newInstance()), set the secure features and properties —
enable XMLConstants.FEATURE_SECURE_PROCESSING, disable DOCTYPE by setting
"http://apache.org/xml/features/disallow-doctype-decl" to true, disable external
entities by setting "http://xml.org/sax/features/external-general-entities" and
"http://xml.org/sax/features/external-parameter-entities" to false, set
XIncludeAware to false and expandEntityReferences to false on the
factory/DocumentBuilder if available, and set the
accessExternalDTD/accessExternalSchema properties to an empty string; do all of
this on the DocumentBuilderFactory instance before invoking newDocumentBuilder()
or parsing settings.xml.

Comment on lines 113 to +128
signing {
// Only sign if signing credentials are available
val signingKeyId = project.findProperty("signing.keyId") as String? ?: System.getenv("GPG_KEY_ID")
val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("GPG_PASSPHRASE")
val signingKey = project.findProperty("signing.secretKeyRingFile") as String? ?: System.getenv("GPG_SECRET_KEY_RING_FILE")
// Use GPG agent for signing (more secure, no password in config files)
val signingKeyName = project.findProperty("signing.gnupg.keyName") as String? ?: System.getenv("GPG_KEY_NAME")

if (signingKeyName != null) {
// Use GPG agent
useGpgCmd()
isRequired = true
} else {
// Fallback to traditional signing if GPG agent is not configured
val signingKeyId = project.findProperty("signing.keyId") as String? ?: System.getenv("GPG_KEY_ID")
val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("GPG_PASSPHRASE")
val signingKey = project.findProperty("signing.secretKeyRingFile") as String? ?: System.getenv("GPG_SECRET_KEY_RING_FILE")

isRequired = signingKeyId != null && signingPassword != null && signingKey != null
isRequired = signingKeyId != null && signingPassword != null && signingKey != null
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "build.gradle.kts" -type f

Repository: phodal/chapi

Length of output: 662


🏁 Script executed:

# Get file size first
wc -l build.gradle.kts

Repository: phodal/chapi

Length of output: 76


🏁 Script executed:

# Read the signing configuration section and surrounding context
sed -n '100,140p' build.gradle.kts

Repository: phodal/chapi

Length of output: 2111


🌐 Web query:

Gradle maven-publish signing plugin environment variables configuration

💡 Result:

Summary + example — how to pass signing credentials via environment variables for Gradle's maven-publish + signing:

Key points

  • Gradle supports in-memory ascii-armored PGP keys so you can set the secret key and passphrase via environment variables instead of a keyring file. Use signing.useInMemoryPgpKeys (or useInMemoryPgpKeys in Groovy). [1]
  • Environment variable names map to Gradle project properties when prefixed with ORG_GRADLE_PROJECT_. Common names:
    • ORG_GRADLE_PROJECT_signingKey — ascii-armored secret key (or ORG_GRADLE_PROJECT_signingKey when providing key with id variant).
    • ORG_GRADLE_PROJECT_signingPassword — passphrase for that key.
    • ORG_GRADLE_PROJECT_signingKeyId — key id (when using subkey variant and you need to specify id).
      These are read in the build with findProperty / project properties. [1][2]
  • Alternatively use the gpg command (useGpgCmd()) if you prefer gpg-agent and a local gpg keyring; then you need GnuPG installed and configured. [1]

Minimal Groovy example (build.gradle)

  • publish + signing block using env-backed properties:

signing {
def signingKey = findProperty("signingKey")
def signingPassword = findProperty("signingPassword")
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.mavenJava
}

Kotlin DSL example (build.gradle.kts)

signing {
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKey, signingPassword)
sign(publishing.publications["mavenJava"])
}

How to supply env vars (example bash / CI)

  • Export the ascii-armored secret key and passphrase as CI secrets and expose them as:
    • ORG_GRADLE_PROJECT_signingKey="$(cat seckey.asc)"
    • ORG_GRADLE_PROJECT_signingPassword="your-passphrase"
  • For subkey usage add ORG_GRADLE_PROJECT_signingKeyId="ABCDEF12". [1][3]

References

  • Gradle Signing Plugin — in-memory keys and env var examples. [1]
  • Gradle Publishing + signing integration (sign publications). [2]
  • Example CI instructions / switching to in-memory keys. [3]

Would you like a ready-to-copy GitHub Actions workflow or a CI-specific snippet (GitHub Actions / GitLab / CircleCI)?


Wire env-based signing credentials into Gradle signing config.

Environment variables are read into local variables but not passed to the signing plugin, causing publish failures when CI provides only env vars. The fallback path needs to configure the signing plugin with these credentials.

The suggested approach using project.extra is incorrect—the Gradle signing plugin reads from project properties via findProperty(). The proper fix is to use useInMemoryPgpKeys() to provide the in-memory key and passphrase:

🛠️ Corrected wiring
             val signingKeyId = project.findProperty("signing.keyId") as String? ?: System.getenv("GPG_KEY_ID")
             val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("GPG_PASSPHRASE")
             val signingKey = project.findProperty("signing.secretKeyRingFile") as String? ?: System.getenv("GPG_SECRET_KEY_RING_FILE")
 
-            isRequired = signingKeyId != null && signingPassword != null && signingKey != null
+            if (signingKeyId != null && signingPassword != null && signingKey != null) {
+                useInMemoryPgpKeys(signingKeyId, signingPassword)
+                isRequired = true
+            } else {
+                isRequired = false
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
signing {
// Only sign if signing credentials are available
val signingKeyId = project.findProperty("signing.keyId") as String? ?: System.getenv("GPG_KEY_ID")
val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("GPG_PASSPHRASE")
val signingKey = project.findProperty("signing.secretKeyRingFile") as String? ?: System.getenv("GPG_SECRET_KEY_RING_FILE")
// Use GPG agent for signing (more secure, no password in config files)
val signingKeyName = project.findProperty("signing.gnupg.keyName") as String? ?: System.getenv("GPG_KEY_NAME")
if (signingKeyName != null) {
// Use GPG agent
useGpgCmd()
isRequired = true
} else {
// Fallback to traditional signing if GPG agent is not configured
val signingKeyId = project.findProperty("signing.keyId") as String? ?: System.getenv("GPG_KEY_ID")
val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("GPG_PASSPHRASE")
val signingKey = project.findProperty("signing.secretKeyRingFile") as String? ?: System.getenv("GPG_SECRET_KEY_RING_FILE")
isRequired = signingKeyId != null && signingPassword != null && signingKey != null
isRequired = signingKeyId != null && signingPassword != null && signingKey != null
}
signing {
// Use GPG agent for signing (more secure, no password in config files)
val signingKeyName = project.findProperty("signing.gnupg.keyName") as String? ?: System.getenv("GPG_KEY_NAME")
if (signingKeyName != null) {
// Use GPG agent
useGpgCmd()
isRequired = true
} else {
// Fallback to traditional signing if GPG agent is not configured
val signingKeyId = project.findProperty("signing.keyId") as String? ?: System.getenv("GPG_KEY_ID")
val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("GPG_PASSPHRASE")
val signingKey = project.findProperty("signing.secretKeyRingFile") as String? ?: System.getenv("GPG_SECRET_KEY_RING_FILE")
if (signingKeyId != null && signingPassword != null && signingKey != null) {
useInMemoryPgpKeys(signingKeyId, signingPassword)
isRequired = true
} else {
isRequired = false
}
}
🤖 Prompt for AI Agents
In `@build.gradle.kts` around lines 113 - 128, The signing block currently reads
env vars into signingKeyId, signingPassword and signingKey but never wires them
into the Gradle signing plugin, so CI-provided env creds are ignored; update the
else fallback in the signing block to call useInMemoryPgpKeys(signingKey,
signingPassword) when signingKey and signingPassword are present and set
isRequired = true, and if signingKeyId is present ensure the signing.keyId
project property is exposed (so findProperty() can read it) before calling
useInMemoryPgpKeys; keep using useGpgCmd() when signingKeyName is set.

Comment on lines 35 to +41
override fun enterNamespaceDefinition(ctx: CPP14Parser.NamespaceDefinitionContext?) {
ctx?.Identifier()?.let {
codeContainer.PackageName = it.text
val nsName = it.text
codeContainer.PackageName = nsName
codeContainer.DeclaredPackage = nsName
codeContainer.NamespacePath = codeContainer.NamespacePath + nsName
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "CPPBasicIdentListener.kt" -type f

Repository: phodal/chapi

Length of output: 129


🏁 Script executed:

head -80 ./chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt | tail -50

Repository: phodal/chapi

Length of output: 1851


🏁 Script executed:

rg -A 5 "class CodeContainer" --type kotlin

Repository: phodal/chapi

Length of output: 1211


🏁 Script executed:

cat -n ./chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | head -60

Repository: phodal/chapi

Length of output: 2589


🏁 Script executed:

cat -n ./chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | head -120 | tail -70

Repository: phodal/chapi

Length of output: 3110


🏁 Script executed:

cat -n ./chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | head -150 | tail -40

Repository: phodal/chapi

Length of output: 1919


🏁 Script executed:

rg "PackageName|DeclaredPackage" ./chapi-ast-cpp/src --type kotlin -B 2 -A 2 | head -100

Repository: phodal/chapi

Length of output: 1386


🏁 Script executed:

cat -n ./chapi-ast-cpp/src/test/kotlin/chapi/ast/cppast/CPPBasicIdentListenerTest.kt | head -80

Repository: phodal/chapi

Length of output: 3064


🏁 Script executed:

cat -n ./chapi-ast-cpp/src/test/kotlin/chapi/ast/cppast/CPPBasicIdentListenerTest.kt | grep -A 20 "namespace"

Repository: phodal/chapi

Length of output: 1466


🏁 Script executed:

cat -n ./chapi-ast-cpp/src/test/kotlin/chapi/ast/cppast/CPPBasicIdentListenerTest.kt | wc -l

Repository: phodal/chapi

Length of output: 59


🏁 Script executed:

cat -n ./chapi-ast-cpp/src/test/kotlin/chapi/ast/cppast/CPPBasicIdentListenerTest.kt

Repository: phodal/chapi

Length of output: 6637


🏁 Script executed:

rg "CodeDataStruct\|PackageName" ./chapi-ast-cpp/src --type kotlin -B 3 -A 3 | head -80

Repository: phodal/chapi

Length of output: 38


🏁 Script executed:

rg "class CodeDataStruct" --type kotlin -A 20

Repository: phodal/chapi

Length of output: 4410


🏁 Script executed:

rg "enterClassSpecifier|enterStructSpecifier" ./chapi-ast-cpp/src --type kotlin -A 10 | head -80

Repository: phodal/chapi

Length of output: 1358


🏁 Script executed:

rg "exitClassSpecifier|Package =" ./chapi-ast-cpp/src --type kotlin -B 3 -A 8 | head -120

Repository: phodal/chapi

Length of output: 2567


🏁 Script executed:

rg "\.Package\s*=" ./chapi-ast-cpp/src --type kotlin -B 3 -A 3

Repository: phodal/chapi

Length of output: 38


Update PackageName/DeclaredPackage when exiting namespaces.

After popping NamespacePath in exitNamespaceDefinition, PackageName and DeclaredPackage remain set to the inner namespace. Any declarations in outer scope will inherit stale namespace metadata. Recompute these fields from the current NamespacePath on both enter and exit to maintain accurate scope tracking.

🛠️ Suggested fix
 override fun enterNamespaceDefinition(ctx: CPP14Parser.NamespaceDefinitionContext?) {
     ctx?.Identifier()?.let {
         val nsName = it.text
         codeContainer.NamespacePath = codeContainer.NamespacePath + nsName
+        val currentNs = codeContainer.NamespacePath.joinToString("::")
+        codeContainer.PackageName = currentNs
+        codeContainer.DeclaredPackage = currentNs
     }
 }
 
 override fun exitNamespaceDefinition(ctx: CPP14Parser.NamespaceDefinitionContext?) {
     if (codeContainer.NamespacePath.isNotEmpty()) {
         codeContainer.NamespacePath = codeContainer.NamespacePath.dropLast(1)
+        val currentNs = codeContainer.NamespacePath.joinToString("::")
+        codeContainer.PackageName = currentNs
+        codeContainer.DeclaredPackage = currentNs
+    } else {
+        codeContainer.PackageName = ""
+        codeContainer.DeclaredPackage = ""
     }
 }
🤖 Prompt for AI Agents
In `@chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt`
around lines 35 - 41, The code currently sets codeContainer.PackageName and
codeContainer.DeclaredPackage directly to the inner namespace in
enterNamespaceDefinition but does not update them when exiting, leaving stale
values; update both enterNamespaceDefinition and exitNamespaceDefinition so that
after modifying codeContainer.NamespacePath you recompute
codeContainer.PackageName and codeContainer.DeclaredPackage from the current
NamespacePath (e.g., set them to the last segment of NamespacePath if present,
or to empty/null when NamespacePath is empty) rather than relying on the
previously captured nsName; ensure the recomputation happens immediately after
pushing in enterNamespaceDefinition and immediately after popping in
exitNamespaceDefinition so scope metadata stays correct.

Comment on lines 43 to +53
override fun enterNamespaceDeclaration(ctx: TypeScriptParser.NamespaceDeclarationContext?) {
this.namespaceName = ctx!!.namespaceName().text
// Update namespace path for structured semantics
codeContainer.NamespacePath = codeContainer.NamespacePath + this.namespaceName
}

override fun exitNamespaceDeclaration(ctx: TypeScriptParser.NamespaceDeclarationContext?) {
// Pop from namespace path when exiting
if (codeContainer.NamespacePath.isNotEmpty()) {
codeContainer.NamespacePath = codeContainer.NamespacePath.dropLast(1)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the file
fd "TypeScriptFullIdentListener.kt" --type f

Repository: phodal/chapi

Length of output: 147


🏁 Script executed:

# Check the CodeContainer class to understand NamespacePath type
fd "CodeContainer" --type f -e kt -e java

Repository: phodal/chapi

Length of output: 187


🏁 Script executed:

# Search for NamespacePath definition
rg "NamespacePath" --type kt -B 2 -A 2

Repository: phodal/chapi

Length of output: 82


🏁 Script executed:

# Read CodeContainer to understand NamespacePath type
cat -n chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | head -100

Repository: phodal/chapi

Length of output: 4204


🏁 Script executed:

# Read the TypeScriptFullIdentListener file to see full context
cat -n chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptFullIdentListener.kt

Repository: phodal/chapi

Length of output: 50368


🏁 Script executed:

# Find the NamespacePath definition in CodeContainer
cat -n chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | sed -n '100,200p'

Repository: phodal/chapi

Length of output: 2461


🏁 Script executed:

# Search for NamespacePath definition to understand its type
rg "NamespacePath\s*:" chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt -A 2

Repository: phodal/chapi

Length of output: 158


🏁 Script executed:

# Now I understand the type issue. Let me verify how TypeScript namespaces are actually used
# Check if there are any tests for namespace handling
fd ".*Test.*TypeScript" --type f | head -5

Repository: phodal/chapi

Length of output: 38


🏁 Script executed:

# Search for namespace tests or examples
rg "namespace.*\." chapi-ast-typescript --type kotlin -B 2 -A 2 | head -40

Repository: phodal/chapi

Length of output: 1880


🏁 Script executed:

# Check TypeScript grammar to see how namespaceName is parsed
fd "TypeScript.*\.g4" --type f

Repository: phodal/chapi

Length of output: 166


🏁 Script executed:

# Check the TypeScript grammar for namespace and namespaceName definitions
cat -n chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4 | grep -A 5 "namespaceName\|namespaceDeclaration"

Repository: phodal/chapi

Length of output: 990


🏁 Script executed:

# Let's also check if there are any tests that use namespaces with dots
fd ".*Test" --type f chapi-ast-typescript | xargs rg "namespace.*\." 2>/dev/null | head -20

Repository: phodal/chapi

Length of output: 38


🏁 Script executed:

# Let's look for actual TypeScript namespace test files to see if dotted namespaces are tested
fd "test.*typescript" -i --type d

Repository: phodal/chapi

Length of output: 38


🏁 Script executed:

# Check if there are example TypeScript files with namespace declarations
find . -name "*.ts" -type f 2>/dev/null | head -10

Repository: phodal/chapi

Length of output: 680


🏁 Script executed:

# Look for test files that include namespace examples
fd ".*Test.*" chapi-ast-typescript --type f | xargs grep -l "namespace" 2>/dev/null

Repository: phodal/chapi

Length of output: 151


🏁 Script executed:

# Check the test file for namespace-related tests
rg "namespace" chapi-ast-typescript/src/test/kotlin/chapi/ast/typescriptast/TypeScriptFullIdentListenerTest.kt -B 3 -A 10

Repository: phodal/chapi

Length of output: 994


🏁 Script executed:

# Look for actual test files with namespace declarations
find chapi-ast-typescript/src/test/resources -name "*amespace*" -o -name "*.ts" | xargs grep -l "namespace" 2>/dev/null

Repository: phodal/chapi

Length of output: 168


🏁 Script executed:

# Check if there's a namespace example file
cat chapi-ast-typescript/src/test/resources/grammar/*.ts | grep -B 2 -A 3 "namespace"

Repository: phodal/chapi

Length of output: 163


🏁 Script executed:

# Look for dotted namespace examples in test files
grep -r "namespace.*\." chapi-ast-typescript/src/test/resources --include="*.ts" -B 2 -A 5

Repository: phodal/chapi

Length of output: 38


🏁 Script executed:

# Check the Export.ts file for namespace examples
cat chapi-ast-typescript/src/test/resources/grammar/Export.ts | grep -B 2 -A 5 "namespace"

Repository: phodal/chapi

Length of output: 170


🏁 Script executed:

# Let's verify the actual Kotlin behavior of List<String> + String
cat > /tmp/test_kotlin.kt << 'EOF'
fun main() {
    val list: List<String> = listOf("A")
    val combined = list + "B.C"
    println("Result: $combined")
    println("Type: ${combined.javaClass}")
    println("First element: ${combined[0]}")
    println("Second element: ${combined[1]}")
    println("Size: ${combined.size}")
}
EOF

# Check if we can compile and run it
cd /tmp && kotlinc test_kotlin.kt -include-runtime -d test_kotlin.jar && java -jar test_kotlin.jar

Repository: phodal/chapi

Length of output: 104


🏁 Script executed:

# Also check how the code actually compiles - look for actual usage and imports in the listener
head -50 chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptFullIdentListener.kt | tail -30

Repository: phodal/chapi

Length of output: 1314


🏁 Script executed:

# Let's verify the actual behavior by checking what happens with the code
# Look for any actual usage patterns with dotted namespaces in tests
rg "namespace.*\." chapi-ast-typescript -B 2 -A 2

Repository: phodal/chapi

Length of output: 1880


🏁 Script executed:

# Check if there are any integration tests that test the NamespacePath field
rg "NamespacePath" chapi-ast-typescript --type kotlin -B 3 -A 3

Repository: phodal/chapi

Length of output: 1886


🏁 Script executed:

# Verify Kotlin list concatenation semantics by checking actual project usage
rg "List.*\+" chapi-ast-typescript/src/main/kotlin --type kotlin | head -10

Repository: phodal/chapi

Length of output: 38


🏁 Script executed:

# Let's create a simple test to verify the actual Kotlin behavior
python3 << 'EOF'
# Simulate Kotlin's List<String> + String behavior
list_path = []  # represents List<String>
namespace_name = "A.B"

# Current behavior: list + string adds the string as a single element
result = list_path + [namespace_name]
print(f"Current behavior: {result}")

# Expected behavior from suggested fix: split and add parts
parts = namespace_name.split(".")
result_fixed = list_path + parts
print(f"Fixed behavior: {result_fixed}")

# Exit behavior
print(f"\nExit - current code would dropLast(1): {result[:-1]}")
print(f"Exit - fixed code would dropLast({len(parts)}): {result_fixed[:-len(parts)]}")
EOF

Repository: phodal/chapi

Length of output: 142


🏁 Script executed:

# Check if there are any C# or C++ namespace tests to see expected behavior
rg "NamespacePath" chapi-domain -B 5 -A 5 --type kotlin | head -40

Repository: phodal/chapi

Length of output: 4039


Split dotted namespace names into path segments.

namespace A.B currently records ["A.B"] as a single element and drops only one segment on exit. According to the NamespacePath semantics (documented in CodeContainer.kt and confirmed in tests), dotted namespaces should be split: namespace MyApp.Services.Users["MyApp", "Services", "Users"]. On exit, you must drop the correct number of elements.

🧩 Suggested fix
     override fun enterNamespaceDeclaration(ctx: TypeScriptParser.NamespaceDeclarationContext?) {
         this.namespaceName = ctx!!.namespaceName().text
-        // Update namespace path for structured semantics
-        codeContainer.NamespacePath = codeContainer.NamespacePath + this.namespaceName
+        // Update namespace path for structured semantics
+        val parts = this.namespaceName.split(".")
+        codeContainer.NamespacePath = codeContainer.NamespacePath + parts
     }
 
     override fun exitNamespaceDeclaration(ctx: TypeScriptParser.NamespaceDeclarationContext?) {
-        // Pop from namespace path when exiting
-        if (codeContainer.NamespacePath.isNotEmpty()) {
-            codeContainer.NamespacePath = codeContainer.NamespacePath.dropLast(1)
-        }
+        // Pop from namespace path when exiting
+        val parts = ctx?.namespaceName()?.text?.split(".") ?: emptyList()
+        if (parts.isNotEmpty() && codeContainer.NamespacePath.size >= parts.size) {
+            codeContainer.NamespacePath = codeContainer.NamespacePath.dropLast(parts.size)
+        }
         this.namespaceName = ""
     }
🤖 Prompt for AI Agents
In
`@chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptFullIdentListener.kt`
around lines 43 - 53, The current enterNamespaceDeclaration sets namespaceName
and appends the whole dotted name as one element to codeContainer.NamespacePath;
change it to split ctx.namespaceName().text on '.' into segments, append each
segment to codeContainer.NamespacePath, and store the segments count (or infer
it) so that exitNamespaceDeclaration drops that many elements (not just one);
update logic around enterNamespaceDeclaration, exitNamespaceDeclaration,
namespaceName and codeContainer.NamespacePath to push segments on enter and pop
the same number of segments on exit.

@phodal phodal closed this Jan 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chapi-domain 核心模型:面向多语言 parser 的结构化改进(namespace/module、import/export、type、call、top-level)

1 participant