typescript_scanner: TS AST path silently disabled due to stale `tree_sitter_typescript` language-module API
#12 opened on Jul 4, 2026
Repository metrics
- Stars
- (180 stars)
- PR merge metrics
- (PR metrics pending)
Description
Summary
TreeSitterParser._init_tree_sitter in packages/audit/agent_audit/parsers/treesitter_parser.py recognizes two language-module APIs: a LANGUAGE module attribute (used by tree_sitter_python ≥0.23) and a language() module function (used by tree_sitter_javascript). It does not recognize the API exposed by tree_sitter_typescript ≥0.23, which uses language_typescript() and language_tsx() (two separate functions for the TypeScript / TSX dialects) instead of a single language symbol.
As a result, when tree_sitter_typescript is installed and a .ts/.tsx file is scanned, _init_tree_sitter falls through to else: return and is_tree_sitter_available remains False. The TypeScript scanner then transparently falls back to its regex path.
User impact
None end-to-end. The regex fallback in typescript_scanner._scan_dangerous_calls_regex implements the same detection surface as the tree-sitter AST path — including the 0.19.2 Redis EVAL false-positive fix (bare eval(...) via negative-lookbehind, explicit window / globalThis / global / self.eval aliases, eval(args): Type declaration skip). Smoke-tested on the PyPI 0.19.2 install: redisClient.eval(luaScript) is not flagged; eval(userInput) is flagged with AGENT-034 BLOCK. So users are not currently missing any real detection because of this bug.
This issue is scoped to (a) TS AST branches being dead code across all environments (including CI, developer laptops, and end-user installs), (b) the fact that the downgrade happens with no user-visible signal, and (c) the resulting under-reported coverage on Codecov even after the tree-sitter extra is installed (see companion CI issue).
Root cause
packages/audit/agent_audit/parsers/treesitter_parser.py:191-196:
# tree-sitter-python 0.23+ uses LANGUAGE attribute
if hasattr(parser_module, 'LANGUAGE'):
lang = parser_module.LANGUAGE
elif hasattr(parser_module, 'language'):
lang = parser_module.language()
else:
return
Reproduction:
>>> import tree_sitter_typescript as mod
>>> [x for x in dir(mod) if not x.startswith('_')]
['language_tsx', 'language_typescript']
>>> hasattr(mod, 'LANGUAGE'), hasattr(mod, 'language')
(False, False)
Neither the LANGUAGE attribute nor the language() function exists on the module. The else: return at line 195 is taken, _use_tree_sitter stays False, and no log line is emitted (this early return is not the except block at line 203-205, which uses logger.debug — this branch is silent).
Proposed fix
Two changes are needed. The exact language-key wiring is a design choice that should be confirmed against the LANGUAGE_EXTENSIONS mapping in treesitter_parser.py:128-137 before implementation.
1. Handle the tree_sitter_typescript API in _init_tree_sitter
Verified facts:
tree_sitter_typescript(≥0.23) exposeslanguage_typescript()andlanguage_tsx().- The current
LANGUAGE_EXTENSIONSmapping sends both.tsand.tsxfiles toself.language == 'typescript'.
Given the current mapping, the minimal, non-speculative fix is to add a single branch that resolves the typescript key to language_typescript():
# Sketch — exact key(s) to be confirmed against LANGUAGE_EXTENSIONS and any
# dialect handling elsewhere in the parser.
elif self.language == 'typescript' and hasattr(parser_module, 'language_typescript'):
lang = parser_module.language_typescript()
This makes both .ts and .tsx files use the pure-TypeScript grammar. Pure TypeScript parses correctly; JSX embedded in .tsx may not (tree_sitter_typescript ships a separate language_tsx() grammar for that dialect precisely because JSX is not valid TS).
Whether to split .tsx into its own self.language == 'tsx' key and add a language_tsx() branch is a follow-up design decision — it requires splitting the LANGUAGE_EXTENSIONS mapping and confirming no other code assumes .tsx → 'typescript'. Not blocking for this issue; call it out and defer.
2. Elevate the silent-downgrade signal
- Change the silent
returnat line 195 intologger.warning(f"tree-sitter language module for {self.language} does not expose a known language accessor; falling back to regex"). As-is, an integration bug like this cannot be caught in production without directly inspectingis_tree_sitter_available. - Consider changing the
except Exceptionfallback at line 203-205 fromlogger.debugtologger.warningfor the same reason.
Verification plan
After the fix:
poetry install -E tree-sitter && poetry run python -c "from agent_audit.parsers.treesitter_parser import TreeSitterParser; p = TreeSitterParser(source='eval(x);', file_path='x.ts'); assert p.is_tree_sitter_available"— should not raise.- Re-run
pytest tests/test_typescript_scanner.py --cov=agent_audit.scanners.typescript_scanner --cov-report=term-missingafter installing the tree-sitter extra — the missing ranges381-454and459-520(AST paths) should shrink significantly. TestEvalMemberCallDistinctionshould continue to pass — the AST path implements the same fix as the regex fallback.
Target
0.20.0. This is not a hotfix candidate for a patch release: user-visible behavior is correct, and the fix requires (a) the CI-side install change in the companion issue to be measurable, and (b) a light review of the .tsx dialect handling before choosing between the single-branch and split-mapping fixes.