The Java and TypeScript backends handle the same condition — two declarations spelling one name — with opposite failure modes. Java refuses; TypeScript overwrites without a word.
Java: raises
cldk/analysis/java/codeanalyzer/codeanalyzer.py:347-353, mirrored identically in cldk/analysis/java/neo4j/neo4j_backend.py:657-663:
def _add_type(self, t: JType, path: str) -> None:
name = t.qualified_name
if name in self._types:
raise CodeanalyzerExecutionException(duplicate_type_name(name))
self._types[name] = t
self._file_of[name] = path
duplicate_type_name (cldk/analysis/java/backend.py:515) states the rationale: the collision "would make a get_call_graph() node key and a get_class() key ambiguous, so it is surfaced rather than letting the second silently shadow the first."
TypeScript: shadows
cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:298-300:
def _add_type(self, t, fp: str) -> None:
self._file_of[t.signature] = fp
self._id_index[t.id] = (t.signature, t.kind)
Plain assignment, no guard. _add_callable (302-305) does the same for self._callables[c.signature] and self._file_of[c.signature]. A second declaration spelling the same signature silently replaces the first, so _file_of reports one file for several declarations and every consumer of it — get_typescript_file, the path= on TSCallableOverview / TSClassOverview, resolve_callable's SliceNode.file — silently attributes code to the wrong file.
Why it matters now
Vendored duplicates are the realistic trigger: several service repos analyzed in one run, each carrying its own copy of a shared internal module. That is exactly the input the Java guard exists to reject. A polyglot monorepo therefore gets a loud, correct refusal on its Java half and a quiet wrong answer on its TypeScript half, from the same analysis.
The Java docstring's own reasoning applies unchanged to TypeScript, so the two should agree. Which direction they agree in is the open question:
- TypeScript raises too — parity with Java, and consistent with the stated rationale. Breaking for any caller currently relying on last-wins, though it is hard to argue that reliance is intentional.
- Both warn instead of raising, with the ambiguity recorded so a caller can decide. Softer, but changes documented Java behavior and needs somewhere for the warning to go.
Option 1 is the smaller change and matches the existing intent; worth confirming before implementing, since it turns a silent pass into a hard failure for existing TypeScript callers.
Related: codellm-devkit/codeanalyzer-java#269 — the analyzer that produces these duplicates neither reports nor resolves them correctly, binding every caller to the last source root.
The Java and TypeScript backends handle the same condition — two declarations spelling one name — with opposite failure modes. Java refuses; TypeScript overwrites without a word.
Java: raises
cldk/analysis/java/codeanalyzer/codeanalyzer.py:347-353, mirrored identically incldk/analysis/java/neo4j/neo4j_backend.py:657-663:duplicate_type_name(cldk/analysis/java/backend.py:515) states the rationale: the collision "would make aget_call_graph()node key and aget_class()key ambiguous, so it is surfaced rather than letting the second silently shadow the first."TypeScript: shadows
cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:298-300:Plain assignment, no guard.
_add_callable(302-305) does the same forself._callables[c.signature]andself._file_of[c.signature]. A second declaration spelling the same signature silently replaces the first, so_file_ofreports one file for several declarations and every consumer of it —get_typescript_file, thepath=onTSCallableOverview/TSClassOverview,resolve_callable'sSliceNode.file— silently attributes code to the wrong file.Why it matters now
Vendored duplicates are the realistic trigger: several service repos analyzed in one run, each carrying its own copy of a shared internal module. That is exactly the input the Java guard exists to reject. A polyglot monorepo therefore gets a loud, correct refusal on its Java half and a quiet wrong answer on its TypeScript half, from the same analysis.
The Java docstring's own reasoning applies unchanged to TypeScript, so the two should agree. Which direction they agree in is the open question:
Option 1 is the smaller change and matches the existing intent; worth confirming before implementing, since it turns a silent pass into a hard failure for existing TypeScript callers.
Related: codellm-devkit/codeanalyzer-java#269 — the analyzer that produces these duplicates neither reports nor resolves them correctly, binding every caller to the last source root.