Languages¶
| Language | Metrics | Extensions | Needs treesitter |
|---|---|---|---|
| Python | CC, cognitive, MI, SLOC | .py |
no |
| JavaScript / TypeScript | CC, SLOC | .js .jsx .mjs .cjs .ts .mts .cts .tsx |
yes |
| Go | CC, SLOC | .go |
yes |
| Ruby | CC, SLOC | .rb |
yes |
| Rust | CC, SLOC | .rs |
yes |
| Java | CC, SLOC | .java |
yes |
| PHP | CC, SLOC | .php .phtml |
yes |
A single report can mix languages. Each file is dispatched by extension to the analyzer that claims it.
The tree-sitter extra¶
Every language other than Python is parsed with tree-sitter and requires the optional extra:
Without it, codaviz still runs. The tree-sitter analyzers never register, so files in those languages are skipped without a message.
tree-sitter 0.26 or newer
The extra requires tree-sitter>=0.26,<0.27. The binding's Node API changed shape between 0.25 and 0.26: 0.25.x exposed it as methods (node.kind(), tree.root_node(), parse(str)), and 0.26 reverted to properties (node.type, tree.root_node, parse(bytes)). The two are mutually incompatible, so the upper bound is deliberate.
On a mismatched binding, every non-Python file fails with source must be a bytestring or a callable, not str on stderr. The run still exits 0 with an empty report. If you installed codaviz as a standalone tool before this was pinned, reinstall it: uv tool install --force "codaviz[treesitter]".
Walkthrough: a JavaScript / TypeScript project¶
uv sync --extra treesitter # or: pip install "codaviz[treesitter]"
codaviz ~/src/my-app
open report.html
.js .jsx .mjs .cjs .ts .mts .cts .tsx are all picked up, in one pass, in one report. node_modules/, dist/, and build/ are excluded by default. Inside a git repository, anything .gitignored never reaches the analyzer.
The report opens on Max cyclomatic, because there is no MI data to color a treemap with. The MI and cognitive columns stay blank. The circular-imports panel reads "none detected": that is Python-only detection finding nothing.
Three things will bite you on a JS/TS codebase specifically.
The treesitter extra is not optional here
Without it, no JS/TS analyzer registers, .ts and .js are not recognised extensions, and codaviz produces an empty report with no error. If your report has zero modules, that is the first thing to check:
A list containing only ['.py'] means the extra is missing.
Test files are not excluded automatically¶
The built-in test heuristic is Python-shaped: a test/ or tests/ directory, conftest.py, test_*.py, *_test.py. It does not catch the JavaScript conventions: foo.test.ts, foo.spec.js, and __tests__/ are all analyzed as production code and will pollute your hotspot ranking.
Exclude them explicitly:
[tool.codaviz]
exclude = [
"*.test.ts", "*.test.tsx", "*.test.js",
"*.spec.ts", "*.spec.js",
"__tests__/*", "__mocks__/*",
"*.min.js", "*.bundle.js", "*.d.ts",
]
Patterns are matched with Path.match, which matches a relative pattern from the right: "*.test.ts" catches src/api/router.test.ts at any depth. "__tests__/*" catches any file directly inside a __tests__ directory.
Yes, in a pyproject.toml
Configuration lives in [tool.codaviz] in a pyproject.toml at the root you point codaviz at. On a project with no Python in it, that file exists solely to configure codaviz; nothing else reads it.
src/ stays in the tree¶
Source-root detection looks for Python under src/. A TypeScript project's src/ therefore is not collapsed. Module ids keep the prefix: src/api/router.ts, under a top-level src package.
Aggregates and the treemap are unaffected; the module tree gains one extra level of nesting. If it bothers you, run codaviz from inside the directory: codaviz src/.
Anonymous functions¶
A function gets a real name when the grammar gives it one, or when it is assigned to a variable: const handler = (req) => ... reports as handler. An arrow passed straight into a call, like a describe() or useEffect() callback, reports as (anonymous) with its line number, which is enough to find it in the drill-down.
Exclude bundled or minified output: one minified line becomes a single (anonymous) function with an enormous cyclomatic score that dominates every ranking.
What Python gets that others do not¶
- Maintainability index: needs Halstead volume, which has no principled cross-grammar definition. Approximating it would print a number codaviz cannot defend, so non-Python modules show a blank MI.
- Cognitive complexity: a shared SonarSource walker for the tree-sitter analyzers is planned; until it lands, those analyzers leave cognitive unset.
- Circular imports: detection is an AST-based Python import graph. Other languages report no cycles.
A non-Python project therefore reports cyclomatic complexity and SLOC. The report opens on a cyclomatic lens.
How comparable are the numbers?¶
Within a language, the numbers are consistent. Across languages, they are not; codaviz does not claim otherwise.
Cyclomatic complexity for the tree-sitter languages is computed from the syntax tree: 1 per function, plus 1 per decision node beneath it (branches, loops, case clauses, exception handlers, ternaries, short-circuiting boolean operators), not descending into nested functions. That is McCabe-family. It is not a reimplementation of gocyclo, PMD, or ESLint's complexity rule. The numbers are not expected to match them.
30 CC in Go and 30 CC in Python both mean "this function has a lot of branches". They do not mean the same amount of trouble.
How the hierarchy is built¶
Every analyzer currently uses the path-based hierarchy: one package per ancestor directory, one module per file, with the module's id being its source-root-relative path.
For languages whose unit of namespacing is declared in source rather than implied by the filesystem (Go's package clause, Rust's mod, Java's package), the path-based hierarchy is an approximation. A plugin can override it (see Writing a plugin); doing so for Go, Rust, and Java is a deferred refinement.
In a Go project, each .go file appears as its own module even though Go considers the whole directory one package. Package-level aggregates still roll up correctly, because the directory is the package node.
Adding a language¶
A new tree-sitter language is a small subclass declaring its grammar and which node kinds count: no new parsing code. See Writing a plugin.