Skip to content

feat(dead-code): bdt dead-code flags unreferenced SQL files and backend routes nothing uses - #67

Open
aersam wants to merge 5 commits into
mainfrom
api-usage-lint
Open

aersam wants to merge 5 commits into
mainfrom
api-usage-lint

Conversation

@aersam

@aersam aersam commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What

One new command, bdt dead-code, for dead code a linter can't see. Developed from an experiment run against OneSales, CCMT2 and MDMApp. Configured in one table, [tool.bdt.dead_code]; each check runs when configured, --only sql|routes runs one (the routes check imports the app, so it is the slow one).

sql -- unreferenced .sql files (opt-in via sql_roots)

Reports .sql files under sql_roots that no Python code references: literal/keyword load_sql("topic","name"), dynamic names (stem appears as a literal in the calling file), segment-aligned repo-relative path literals, and bare filenames under the SQL folder's parent. Always scans the whole repo for references. Unparseable Python files are reported (their references are unknown).

routes -- FastAPI operations nothing uses ([[tool.bdt.dead_code.apps]])

Inventory from app.openapi() (recursing into mounted sub-apps, in a stdlib-only subprocess so a repo-local bmsdna package can't shadow devtools) or a committed openapi.json. An operation is used when:

  • non-generated frontend code calls it: hey-api SDK functions + react-query helpers (scoped per frontend dir), openapi-fetch .GET("/x") (method-exact), URL/template literals (nested templates, concatenation). Generated code, tests and e2e specs never count; comments are masked;
  • backend code refers to it by name: a string literal passed to url_for / url_path_for (configurable url_for_functions) in non-test Python files or Jinja-style templates under app_dir -- how auth redirects and OAuth callbacks are wired (request.url_for("auth_callback")). The route name is recovered from the operationId (FastAPI default form, bare name, or <prefix>-<name>), so included routers and mounted sub-apps work.

Excludes by prefix/tag/glob; optional baseline ratchet with stale-entry detection (--update-baseline, routes only).

bdt lint is unchanged from main (no SQL-file rule, no lint-api-usage).

Validated against real repos (harness only)

Repo SQL: unreferenced Routes: flagged (before excludes)
OneSales 0 31 (4 are SPA/auth/health, ~20 truly dead)
CCMT2 1 22 (7 are /external_api/*, 15 truly dead)
MDMApp akeneo_editor 13 (in akeneo_editor/backend/sql) 7 (3 auth/me, 4 truly dead)
MDMApp mdmapp (7 frontends) – 13

These numbers predate the url_for rule; routes like OneSales' auth_callback (request.url_for("auth_callback")) are no longer flagged. Also exercised end to end against a real FastAPI 0.142 app with an included router (url_for route not reported).

Known limitations (documented in README)

URL literals match every method of a path; SPA <Link> targets can mask a route; server-provided URLs, fully computed paths and non-literal url_for names are invisible (exclude them or list SQL in sql_unreferenced_ignore). The url_for match is textual, so a call in a comment counts.

🤖 Generated with Claude Code

aersam and others added 2 commits October 1, 2026 10:17
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…loads

Opt-in via [tool.bdt.lint] sql_roots. Recognises literal and dynamic
load_sql topic/name calls, repo-relative path literals and scoped bare
filenames. Validated against OneSales (0 findings), CCMT2 (1) and MDMApp (13).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
aersam and others added 2 commits October 1, 2026 10:23
…calls

Inventory from app.openapi() (recursing into mounted sub-apps, in a subprocess
using only stdlib so a repo-local top-level package cannot shadow bdt) or a
committed openapi.json. Callers are found in non-generated TS/JS/Vue only:
hey-api SDK functions + react-query helpers, openapi-fetch calls and URL
literals (nested templates handled). Excludes by prefix/tag/glob and an
optional baseline ratchet. Validated against OneSales, CCMT2 and MDMApp.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…-api-usage

sql-file-unreferenced: keyword loader args, segment-aligned path matching, Windows
separators, report unparseable python files, reuse lint._iter_files, ignore the
topic literal for dynamic names.
lint-api-usage: per-frontend SDK scoping, method-exact openapi-fetch, JS/Vue
generated+test exclusion, comments blanked via _mask, concatenated URLs, drop the
reverse suffix match and bare '/', fail on empty inventory, validate config and
baselines before writing, stale-baseline reasons, bounded regexes/recursion,
subprocess timeout. Command-specific CLI output and docs/skill updates.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@aersam
aersam marked this pull request as ready for review October 1, 2026 08:36
@aersam aersam changed the title feat(lint): flag unreferenced SQL files and backend routes never called from the frontend feat(dead-code): bdt dead-code flags unreferenced SQL files and backend routes nothing uses Oct 3, 2026
…`bdt dead-code`

One command and one config table ([tool.bdt.dead_code]) for both dead-code checks:
`sql` (unreferenced .sql files, `sql_roots`) and `routes` (uncalled FastAPI routes,
`[[tool.bdt.dead_code.apps]]`). `--only sql|routes` runs one; `bdt lint` no longer
carries the SQL file rule and `bdt lint-api-usage` is gone.

A route the backend refers to by name -- `request.url_for("auth_callback")`,
`app.url_path_for(...)`, `{{ url_for('login') }}` in a template -- now counts as used
(auth redirects/OAuth callbacks have no frontend caller). The route name is recovered
from the operationId, so it works for included routers and mounted sub-apps too.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant