feat(domain): structured container semantics for multi-language parser#43
feat(domain): structured container semantics for multi-language parser#43
Conversation
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.
📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~90 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
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
CodeTypeRefwith support for generics, unions, pointers, slices, channels, tuples, and language-specific constructs (Rust lifetimes/traits, TypeScript mapped types, Go channels, etc.) - Enhanced
CodeContainerwithLanguage,ContainerKind,DeclaredPackage,ResolvedModulePath, andNamespacePathfields - Updated
CodeImportandCodeExportwith structuredImportKind/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
CodeTypeRefand 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.
| // 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() | ||
| )) |
There was a problem hiding this comment.
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.
| codeContainer.PackageName = nsName | ||
| codeContainer.DeclaredPackage = nsName | ||
| codeContainer.NamespacePath = codeContainer.NamespacePath + nsName |
There was a problem hiding this comment.
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.
| 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 |
| private var codeContainer: CodeContainer = CodeContainer( | ||
| FullName = fileName, | ||
| Language = "python", | ||
| Kind = ContainerKind.MODULE, | ||
| ResolvedModulePath = fileName.substringBeforeLast('/').substringBeforeLast('\\').replace('/', '.').replace('\\', '.') | ||
| ) |
There was a problem hiding this comment.
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).
Codecov Report❌ Patch coverage is 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. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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
Packagefield of classes reflects only the immediate namespace name (e.g.,"B"), not the full path (e.g.,"A.B"). This occurs becausecurrentNamespaceis set tonsNameinstead 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 withOriginalName = "*"andLocalName = "*". 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 onDeclaredPackage/NamespacePath, consider filling them alongsidePackageName(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()andisCollection()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.RELATIVEfor any relative import (from .x import a, b), but these are semantically named imports from a relative path. TheRELATIVEkind might be better reserved for imports where the path itself is the primary concern, or you could use bothRELATIVEandNAMEDinformation.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 328chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoFullIdentListener.kt (1)
43-77: Consider adding specifiers for DOT and SIDE_EFFECT imports.For consistency,
DOTimports (. "pkg") could also have specifiers since they import all exported names. Similarly,SIDE_EFFECTimports could have an empty specifier list explicitly set. Currently, onlyNAMEDimports 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() + } }
| import java.time.Duration | ||
| import org.w3c.dom.Element | ||
| import javax.xml.parsers.DocumentBuilderFactory |
There was a problem hiding this comment.
🧩 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 -20Repository: 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.ktsRepository: 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.
| 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 | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "build.gradle.kts" -type fRepository: phodal/chapi
Length of output: 662
🏁 Script executed:
# Get file size first
wc -l build.gradle.ktsRepository: phodal/chapi
Length of output: 76
🏁 Script executed:
# Read the signing configuration section and surrounding context
sed -n '100,140p' build.gradle.ktsRepository: 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.
| 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.
| 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 | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "CPPBasicIdentListener.kt" -type fRepository: phodal/chapi
Length of output: 129
🏁 Script executed:
head -80 ./chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt | tail -50Repository: phodal/chapi
Length of output: 1851
🏁 Script executed:
rg -A 5 "class CodeContainer" --type kotlinRepository: phodal/chapi
Length of output: 1211
🏁 Script executed:
cat -n ./chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | head -60Repository: phodal/chapi
Length of output: 2589
🏁 Script executed:
cat -n ./chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | head -120 | tail -70Repository: phodal/chapi
Length of output: 3110
🏁 Script executed:
cat -n ./chapi-domain/src/main/kotlin/chapi/domain/core/CodeContainer.kt | head -150 | tail -40Repository: phodal/chapi
Length of output: 1919
🏁 Script executed:
rg "PackageName|DeclaredPackage" ./chapi-ast-cpp/src --type kotlin -B 2 -A 2 | head -100Repository: phodal/chapi
Length of output: 1386
🏁 Script executed:
cat -n ./chapi-ast-cpp/src/test/kotlin/chapi/ast/cppast/CPPBasicIdentListenerTest.kt | head -80Repository: 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 -lRepository: phodal/chapi
Length of output: 59
🏁 Script executed:
cat -n ./chapi-ast-cpp/src/test/kotlin/chapi/ast/cppast/CPPBasicIdentListenerTest.ktRepository: phodal/chapi
Length of output: 6637
🏁 Script executed:
rg "CodeDataStruct\|PackageName" ./chapi-ast-cpp/src --type kotlin -B 3 -A 3 | head -80Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
rg "class CodeDataStruct" --type kotlin -A 20Repository: phodal/chapi
Length of output: 4410
🏁 Script executed:
rg "enterClassSpecifier|enterStructSpecifier" ./chapi-ast-cpp/src --type kotlin -A 10 | head -80Repository: phodal/chapi
Length of output: 1358
🏁 Script executed:
rg "exitClassSpecifier|Package =" ./chapi-ast-cpp/src --type kotlin -B 3 -A 8 | head -120Repository: phodal/chapi
Length of output: 2567
🏁 Script executed:
rg "\.Package\s*=" ./chapi-ast-cpp/src --type kotlin -B 3 -A 3Repository: 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.
| 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) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the file
fd "TypeScriptFullIdentListener.kt" --type fRepository: phodal/chapi
Length of output: 147
🏁 Script executed:
# Check the CodeContainer class to understand NamespacePath type
fd "CodeContainer" --type f -e kt -e javaRepository: phodal/chapi
Length of output: 187
🏁 Script executed:
# Search for NamespacePath definition
rg "NamespacePath" --type kt -B 2 -A 2Repository: 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 -100Repository: 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.ktRepository: 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 2Repository: 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 -5Repository: 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 -40Repository: phodal/chapi
Length of output: 1880
🏁 Script executed:
# Check TypeScript grammar to see how namespaceName is parsed
fd "TypeScript.*\.g4" --type fRepository: 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 -20Repository: 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 dRepository: 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 -10Repository: 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/nullRepository: 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 10Repository: 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/nullRepository: 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 5Repository: 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.jarRepository: 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 -30Repository: 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 2Repository: 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 3Repository: 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 -10Repository: 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)]}")
EOFRepository: 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 -40Repository: 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.
Summary
Implements Issue #41 - structured improvements to chapi-domain for multi-language parser support.
Changes Included
1. Container Semantics (Part A)
ContainerKindenum for classifying containers (SourceFile, Module, Config, etc.)Languagefield to identify the source languageNamespacePathfor namespace/module path representationDeclaredPackagevsResolvedModulePathdistinction2. Top-Level Support (Part B)
TopLevelScopestructure for file-scope declarationsdefaultnode hack3. Import/Export Semantics (Part C)
CodeImportSpecwith structured import representationCodeExportSpecwith structured export representationtypeOnlyflag for TypeScript type-only imports4. Type System (Part D) ⬅️ New in this update
CodeTypeReffor structured type representationTypeReffield toCodeField,CodePropertyReturnTypeReffield toCodeFunctionFiles Changed
Test Plan
CodeTypeRef(47 tests)Next Steps
Parser implementations need to be updated to populate the new structured fields:
Closes #41
Summary by CodeRabbit
Release Notes
Chores
New Features
✏️ Tip: You can customize this high-level summary in your review settings.