feat: static analysis, abandoned code discovery for frappe apps#189
Merged
agritheory merged 1 commit intomainfrom Feb 24, 2026
Merged
feat: static analysis, abandoned code discovery for frappe apps#189agritheory merged 1 commit intomainfrom
agritheory merged 1 commit intomainfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Copied form the docs:
Frappe Static Analysis
static_analysisis a suite of static checks for Frappe apps that runs entirely from the filesystem — no running bench, no imports. It validates that the paths and references scattered across a Frappe app's configuration files, frontend code, and Python source actually point to real, correctly-decorated code.Checks
hooks
Parses
hooks.pywith the AST and validates every dotted Python path it finds. Keys whose values are never Python paths (asset bundles, URL rules, app metadata, fixture lists, etc.) are excluded automatically.Errors when a referenced function, class, or attribute does not exist in the resolved file.
Warns when a path resolves to a module rather than a callable.
patches
Parses
patches.txt— including theexecute:prefix format introduced in Frappe v14 and lines with trailing reload-doc suffixes — and validates each path.Frappe's convention of pointing a patch at a plain module (where
execute()is called automatically) is handled: if the path resolves to a module, the validator checks thatexecute()exists inside it.frontend
Scans all
.js,.ts,.vue,.jsx, and.tsxfiles forfrappe.call(...)andfrappe.xcall(...)invocations and validates the referenced Python path.Errors when:
@frappe.whitelist()The
method: "..."dict-key pattern is also matched when the file already contains afrappe.callorfrappe.xcallcall.python_calls
The same check as frontend, applied to Python files. Uses the AST to find
frappe.call/frappe.xcallinvocations and supports positional string args,{"method": "..."}dicts, andmethod="..."keyword arguments.jinja
Validates template paths referenced in:
frappe.get_template("path")frappe.render_template("path", ...)(when the first argument looks like a file path){% include "path" %}and{% extends "path" %}in HTML/Jinja filesSearches for templates in the current app and all dependency apps, so references to base templates in
frappe(e.g.templates/web.html) resolve correctly.reports
Scans every Python file inside any
report/directory in the app (including nested module structures likeapp/module/report/). Flags top-level functions that are:execute,get_data,get_columns,get_filters,get_chart_data,get_summary)@frappe.whitelist()execute()orphans
Runs Vulture against the app to detect unused imports, variables, and functions. Before running, the analyzer seeds a Vulture whitelist with all discovered entry points (whitelisted functions, hooks paths, doctype controllers) so they are not incorrectly flagged.
Vulture exit code 3 (dead code found alongside syntax errors) is handled gracefully — results are still shown.
Dependency resolution
When the app lives inside a standard Frappe bench
apps/directory, dependency apps are discovered automatically:frappeis always included as an implicit dependencyrequired_appsinhooks.pyare resolved from the siblingapps/directory"owner/app"format inrequired_appsis handled — only the app name portion is usedThis means cross-app dotted paths (e.g. calling
erpnext.setup.utils.get_exchange_ratefromhrms) resolve correctly without any extra configuration.Configuration
Whitelisting paths
Add a
[tool.test_utils.static-analysis]section to the app'spyproject.tomlto permanently skip specific paths:Glob-style
.*suffixes whitelist an entire module subtree.Suppressing individual lines
Add
frappe-vulture:ignoreas a comment on any line to skip it across all passes:Pre-commit integration
Add the hook to your app's
.pre-commit-config.yaml:The hook is configured with
always_run: trueandpass_filenames: false— it analyses the whole app on every commit regardless of which files changed.Enabling orphan detection in pre-commit
Orphan detection requires Vulture. The hook definition in this repo already declares it as an
additional_dependency, so it will be installed automatically in the pre-commit environment:To set a custom confidence threshold:
GitHub Actions / CI integration
Standalone workflow
Copy
.github/workflows/static-analysis.ymlfrom this repo into your app, or add the following job to an existing workflow:--no-orphansis recommended for standalone jobs because orphan detection produces false positives when the app's dependencies (frappe,erpnext, etc.) are not present on the filesystem. Remove it in full-bench CI environments.Inside a full-bench CI job
If your CI workflow already sets up a complete bench (e.g. using
frappe/frappe-dockeror a custom install script), run with all passes enabled:Because the app is inside
apps/, dependency auto-discovery will findfrappeand any other installed apps automatically.CLI reference
Python API