Skip to content

feat(jscan): port module and directory quality rollups - #49

Open
DaisukeYoda wants to merge 1 commit into
mainfrom
feat/module-directory-quality-rollups
Open

feat(jscan): port module and directory quality rollups#49
DaisukeYoda wants to merge 1 commit into
mainfrom
feat/module-directory-quality-rollups

Conversation

@DaisukeYoda

Copy link
Copy Markdown
Member

Closes #31.

Ports pyscn's per-module quality hotspots (pyscn#686) and directory quality rollups (pyscn#687, issue #533) to jscan.

What this adds

  • by_directory on the complexity response — function count, average/max complexity, high-risk count, and average/max nesting depth per directory. Paths are relative to the deepest directory containing every analyzed file, ranked worst-first.
  • module_quality on the analyze output — one entry per file joining its line count, complexity rollups, and dead-code rollups, with the module name from dependency analysis.

Both are reported through JSON, YAML, text, CSV, and HTML (a directory table in the Complexity tab, a new Modules tab).

As upstream, the rollups are computed before the presentation filters: min_complexity and min_severity change what the report shows without changing what a module is measured as carrying. Both response types carry a json:"-" ModuleRollups map for this, and the behavior is covered by tests that run the same project at two filter settings.

The aggregation sits in the service layer rather than pyscn's use-case seam, because jscan's analyze command calls the services directly — BuildModuleQuality lives next to the existing BuildAnalyzeSummary.

nesting_depth was not actually available

The issue assumed jscan already computed per-function NestingDepth. It didn't: CalculateNestingDepth was never called from production code, and its implementation incremented a counter per control structure without ever decrementing, so it counted control structures rather than measuring depth. The published per-function nesting_depth was always 0.

Rather than ship two permanently-zero columns, this PR implements it: an else if continues the chain its if opened, a catch clause stays at its try's level, nested functions are measured separately, and the value is wired into the complexity result. Ten unit tests cover the rules. Nothing scores on NestingDepth, so health scores are unaffected.

Deliberate divergences from pyscn

Recorded in jscan/SYNC.md under a new "Ported with divergences" section:

  • No average_cognitive_complexity — cognitive complexity is still unported; the field is absent rather than reported as zero
  • No function_count — jscan records a complexity result for every function it parses, so it would always equal analyzed_function_count
  • lines_of_code comes from the complexity service, the only per-file reader already holding the file content; line counting matches pyscn's countSourceLines
  • The directory root is the analyzed files' common ancestor, not the caller-facing paths, which the services never receive
  • HTML tables are not sortable — jscan's report has no table sorting anywhere

Verification

gofmt, go vet, and the full test suite pass. make lint was not run: the locally installed golangci-lint is v1 and the repo config is v2.

Docs updated: jscan/CHANGELOG.md, jscan/SYNC.md, website/docs/output/json-schema.md, website/docs/output/html-report.md.

🤖 Generated with Claude Code

Closes #31.

Adds pyscn's per-module hotspots (#686) and directory rollups (#533/#687)
to jscan:

- `by_directory` on the complexity response, aggregating the reported
  functions per directory relative to the deepest directory containing
  every analyzed file
- `module_quality` on the analyze output, joining each file's line count,
  complexity rollups, and dead-code rollups with its module name

Both rollups are derived before the presentation filters, so a report
narrowed by min_complexity or min_severity still measures what each
module carries. The aggregation sits in the service layer rather than
pyscn's use-case seam because jscan's analyze command calls the services
directly.

Reported through JSON, YAML, text, CSV, and HTML (a directory table in
the complexity tab, a new Modules tab).

nesting_depth had no working backing metric: CalculateNestingDepth was
never called, and it counted a function's control structures instead of
measuring how deeply they nest, so the published per-function value was
always 0. It now measures real depth — an else-if chain stays flat, a
catch clause stays at its try's level, and nested functions are measured
separately — and the directory rollups aggregate it.

Divergences from pyscn are recorded in SYNC.md: no cognitive complexity,
no function_count (identical to analyzed_function_count in jscan),
lines_of_code sourced from the complexity service, and no HTML table
sorting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@DaisukeYoda DaisukeYoda self-assigned this Aug 7, 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.

jscan: port pyscn's module / directory quality rollups

1 participant