Skip to content

Commit 62f2bc1

Browse files
committed
feat: implement template reporting improvements with BASE_DIR filtering and transitive detection
Implemented comprehensive template analysis improvements to reduce false positives and false negatives in dead code detection. ## Phase 1: BASE_DIR Filtering - Added BASE_DIR retrieval from Django settings with error handling - Updated TemplateAnalyzer to filter templates by BASE_DIR - Implemented Python 3.8+ compatible path comparison helper - Proper symlink handling (resolved for comparison, original path stored) ## Phase 2: Include/Extends Detection - Implemented transitive closure algorithm for template relationship detection - Templates referenced via {% include %} or {% extends %} now marked as used - BFS-style algorithm with circular reference prevention - Updated dead code detection to use transitive + direct references ## Phase 3: Optional Relationship Reporting - Added --show-template-relationships CLI flag (default: False) - Updated all reporters (Console, JSON, Markdown) to respect flag - Conditionally show/hide template relationship information - Backward compatible with existing output format ## Phase 4: Testing & Documentation - Added 29 new tests (12 unit, 11 integration, 6 reporter) - All 62 tests passing with 93% code coverage - Comprehensive edge case testing (circular refs, deep chains, symlinks) - Updated README.md with usage examples and feature documentation - Updated CHANGELOG.md with comprehensive entry ## Files Changed Core Implementation: - django_deadcode/analyzers/template_analyzer.py - django_deadcode/management/commands/finddeadcode.py - django_deadcode/reporters/base.py Tests: - tests/test_template_analyzer.py - tests/test_command_integration.py (new) - tests/test_reporters.py - tests/settings.py Documentation: - README.md - CHANGELOG.md - agent-os/specs/2025-11-12-template-reporting-improvements/tasks.md - agent-os/specs/2025-11-12-template-reporting-improvements/verifications/ ## Test Results - 62/62 tests passing (100%) - 93% code coverage - No regressions detected - All acceptance criteria met ## Backward Compatibility No breaking changes. All existing functionality preserved. - New CLI flag is optional with sensible defaults - Output format unchanged (just fewer false positives) - All existing tests continue to pass
1 parent 2bbc2dc commit 62f2bc1

File tree

11 files changed

+1191
-406
lines changed

11 files changed

+1191
-406
lines changed

CHANGELOG.md

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
# CHANGELOG
22

3+
## Unreleased
4+
5+
### Feature
6+
7+
* feat: improve template reporting with BASE_DIR filtering and transitive detection
8+
9+
Add three major improvements to template dead code detection:
10+
11+
**1. BASE_DIR Filtering**
12+
- Only analyze templates within your project's BASE_DIR
13+
- Automatically excludes templates from installed packages (Django admin, third-party apps)
14+
- Properly handles symlinks (resolves for comparison but keeps original path)
15+
16+
**2. Transitive Template Detection**
17+
- Templates referenced via {% include %} or {% extends %} are now correctly marked as used
18+
- Recursive algorithm traces template chains (e.g., view → template1 → includes template2)
19+
- Handles circular references without infinite loops
20+
- Supports complex inheritance chains (10+ levels deep)
21+
22+
**3. Optional Relationship Reporting**
23+
- Add `--show-template-relationships` flag to show/hide template relationships
24+
- By default, relationships are hidden to reduce report verbosity
25+
- Works with all output formats (console, JSON, markdown)
26+
27+
Testing:
28+
- 62/62 tests passing (12 new template analyzer tests, 11 new integration tests)
29+
- 93% code coverage
30+
- Comprehensive edge case testing (circular includes, deep chains, missing templates, symlinks)
31+
32+
Files Modified:
33+
- django_deadcode/management/commands/finddeadcode.py (added BASE_DIR retrieval, transitive closure algorithm, flag support)
34+
- django_deadcode/analyzers/template_analyzer.py (added BASE_DIR filtering, Python 3.8+ compatibility helper)
35+
- django_deadcode/reporters/base.py (updated all reporters to support flag)
36+
- tests/test_template_analyzer.py (added 12 tests)
37+
- tests/test_command_integration.py (new file, 11 tests)
38+
- tests/test_reporters.py (added 6 tests)
39+
- tests/settings.py (added BASE_DIR)
40+
- README.md (documented new features)
41+
- CHANGELOG.md (this entry)
42+
43+
Breaking Changes: None
44+
- All changes are additive and backward compatible
45+
- Output format unchanged (just fewer false positives)
46+
- All existing tests continue to pass
47+
348
## v0.1.0 (2025-11-12)
449

550
### Feature
@@ -28,20 +73,20 @@
2873
* fix: resolve semantic-release build failure by using pre-built artifacts
2974

3075
The release job was failing because python-semantic-release tried to run
31-
'python -m build' but the build package wasn't installed in its container.
76+
'python -m build' but the build package wasn't installed in its container.
3277

3378
This fix:
3479
- Downloads the already-built artifacts from the build job
3580
- Sets build_command to empty string in semantic-release config
3681
- Avoids duplicate builds and uses tested artifacts
3782
- More efficient workflow execution
3883

39-
Fixes the error: "/usr/local/bin/python: No module named build" ([`064f467`](https://github.com/nanorepublica/django-deadcode/commit/064f46797ccfbb6366f4d31ba078b44a2bab4bf2))
84+
Fixes the error: "/usr/local/bin/python: No module named build" ([`064f467`](https://github.com/nanorepublica/django-deadcode/commit/064f46797ccfbb6366f4d31ba078b44a2bab4bf2))
4085

4186
* fix: resolve all ruff linting errors
4287

4388
- Add missing reverse_analyzer parameter to _compile_analysis_data
44-
- Update type hints: Set -> set, IOError -> OSError
89+
- Update type hints: Set -> set, IOError -> OSError
4590
- Fix line length violations (split long lines)
4691
- Remove unused imports in test files ([`6bb93d3`](https://github.com/nanorepublica/django-deadcode/commit/6bb93d32a14011888cd5e75638ce08afea52885b))
4792

@@ -67,9 +112,9 @@ feat: debug and identify release issues ([`dc88b8d`](https://github.com/nanorepu
67112

68113
Add GitHub Actions for CI/CD and PyPI publishing ([`c80007f`](https://github.com/nanorepublica/django-deadcode/commit/c80007f33467c76e5ffaaa748d50bc25393427b6))
69114

70-
* Merge branch 'claude/cicd-github-actions-011CV2ofdGATxikzCT7taJ3w' of http://127.0.0.1:57967/git/nanorepublica/django-deadcode into claude/cicd-github-actions-011CV2ofdGATxikzCT7taJ3w ([`cb2e384`](https://github.com/nanorepublica/django-deadcode/commit/cb2e38413da80dbba2036c4d802f2652f317704d))
115+
* Merge branch 'claude/cicd-github-actions-011CV2ofdGATxikzCT7taJ3w' of http://127.0.0.1:57967/git/nanorepublica/django-deadcode into claude/cicd-github-actions-011CV2ofdGATxikzCT7taJ3w ([`cb2e384`](https://github.com/nanorepublica/django-deadcode/commit/cb2e38413da80dbba2036c4d802f2652f317704d))
71116

72-
* Merge branch 'main' into claude/cicd-github-actions-011CV2ofdGATxikzCT7taJ3w ([`7117dfc`](https://github.com/nanorepublica/django-deadcode/commit/7117dfce628e757082dea55fecfab1ea27eeb74b))
117+
* Merge branch 'main' into claude/cicd-github-actions-011CV2ofdGATxikzCT7taJ3w ([`7117dfc`](https://github.com/nanorepublica/django-deadcode/commit/7117dfce628e757082dea55fecfab1ea27eeb74b))
73118

74119
* Merge pull request #3 from nanorepublica/claude/feature-reverse-redirect-detection-011CV2ofdGATxikzCT7taJ3w
75120

@@ -88,8 +133,8 @@ New Features:
88133

89134
Implementation Details:
90135
- AST parsing of all Python files (excluding migrations/third-party)
91-
- Handles nested patterns: HttpResponseRedirect(reverse('url'))
92-
- Supports namespaced URLs: reverse('app:view-name')
136+
- Handles nested patterns: HttpResponseRedirect(reverse('url'))
137+
- Supports namespaced URLs: reverse('app:view-name')
93138
- Ignores method calls (self.reverse(), list.reverse())
94139
- Combines template and Python URL references
95140

@@ -98,7 +143,7 @@ Testing:
98143
- 39/39 tests passing (100% success rate)
99144
- 100% code coverage on ReverseAnalyzer
100145
- 0 regressions in existing tests
101-
- Performance impact < 10%
146+
- Performance impact < 10%
102147

103148
Files Created:
104149
- django_deadcode/analyzers/reverse_analyzer.py (65 lines)
@@ -123,7 +168,7 @@ Django deadcode - v0.1.0 ([`0566864`](https://github.com/nanorepublica/django-de
123168
Applied automatic and manual fixes to resolve 95 linting issues:
124169

125170
Automatic fixes (93 issues):
126-
- Updated typing imports: Dict -&gt; dict, List -&gt; list, Set -&gt; set
171+
- Updated typing imports: Dict -> dict, List -> list, Set -> set
127172
- Removed deprecated Optional[X] in favor of X | None
128173
- Removed unused imports (Template, TemplateSyntaxError, get_template,
129174
inspect, apps, TemplateView, importlib, sys, Path, etc.)
@@ -142,23 +187,23 @@ All tests pass (19/19). All ruff checks now pass. ([`a71a9b5`](https://github.co
142187

143188
* Add pythonpath to pytest config to fix module import in CI
144189

145-
Added &#39;pythonpath = [&#34;.&#34;]&#39; to pytest configuration to ensure the
190+
Added 'pythonpath = ["."]' to pytest configuration to ensure the
146191
project root is on the Python path when pytest-django initializes.
147192

148-
This fixes the &#39;No module named tests&#39; error in GitHub Actions CI.
193+
This fixes the 'No module named tests' error in GitHub Actions CI.
149194
The pythonpath setting tells pytest to add the current directory to
150195
sys.path before importing test modules, allowing pytest-django to
151-
import &#39;tests.settings&#39; successfully.
196+
import 'tests.settings' successfully.
152197

153198
Tests pass locally and should now pass in CI. ([`cb66329`](https://github.com/nanorepublica/django-deadcode/commit/cb66329ad91251f4d01d047310c595c99999ce49))
154199

155200
* Fix pytest-django configuration for library packages
156201

157-
Add &#39;django_find_project = false&#39; to pytest configuration to prevent
202+
Add 'django_find_project = false' to pytest configuration to prevent
158203
pytest-django from looking for manage.py.
159204

160205
This is necessary because django-deadcode is a Django package/library,
161-
not a Django project. Libraries don&#39;t have manage.py files, but still
206+
not a Django project. Libraries don't have manage.py files, but still
162207
need Django settings for testing.
163208

164209
The setting tells pytest-django to use the DJANGO_SETTINGS_MODULE
@@ -173,12 +218,12 @@ Updated testing matrix to focus on currently supported versions:
173218
Python versions:
174219
- Removed: 3.8 (EOL Oct 2024), 3.9 (EOL Oct 2025)
175220
- Testing: 3.10, 3.11, 3.12, 3.13
176-
- Minimum required: Python &gt;=3.10
221+
- Minimum required: Python >=3.10
177222

178223
Django versions:
179224
- Removed: 3.2 LTS (EOL Apr 2024), 4.0 (EOL Apr 2023), 4.1 (EOL Dec 2023)
180225
- Testing: 4.2 LTS, 5.0, 5.1
181-
- Minimum required: Django &gt;=4.2
226+
- Minimum required: Django >=4.2
182227

183228
Additional updates:
184229
- Updated pyproject.toml classifiers and dependencies
@@ -220,7 +265,7 @@ Security Features:
220265

221266
Setup Required:
222267
1. Configure PyPI Trusted Publisher at pypi.org
223-
2. Optionally create &#39;pypi&#39; environment in GitHub Settings
268+
2. Optionally create 'pypi' environment in GitHub Settings
224269
3. Update version in pyproject.toml before release
225270
4. Create GitHub Release to trigger publishing
226271

@@ -232,8 +277,8 @@ Created comprehensive task breakdown organized into 5 phases:
232277
- Phase 1: Foundation (optional AST refactoring)
233278
- Phase 2: Core Implementation (ReverseAnalyzer + pattern detection)
234279
- Phase 3: Integration (finddeadcode command)
235-
- Phase 4: Testing &amp; QA (16-28 tests total)
236-
- Phase 5: Documentation &amp; polish
280+
- Phase 4: Testing & QA (16-28 tests total)
281+
- Phase 5: Documentation & polish
237282

238283
Key features:
239284
- Test-driven approach with focused test groups
@@ -259,19 +304,19 @@ Ready for task list creation. ([`3564f2d`](https://github.com/nanorepublica/djan
259304

260305
Documented key decisions from requirements gathering phase:
261306

262-
Scope &amp; Architecture:
307+
Scope & Architecture:
263308
- Analyze all Python files (views, forms, models, utils, etc.)
264309
- Create separate ReverseAnalyzer class
265310
- Refactor common AST parsing logic between analyzers
266311

267312
Detection Patterns:
268-
- reverse(&#39;url-name&#39;)
269-
- redirect(&#39;url-name&#39;)
270-
- HttpResponseRedirect(reverse(&#39;url-name&#39;))
271-
- reverse_lazy(&#39;url-name&#39;)
313+
- reverse('url-name')
314+
- redirect('url-name')
315+
- HttpResponseRedirect(reverse('url-name'))
316+
- reverse_lazy('url-name')
272317

273318
Behavior:
274-
- Mark detected URLs as &#34;referenced&#34; to prevent false positives
319+
- Mark detected URLs as "referenced" to prevent false positives
275320
- Detect dynamic URLs and flag for manual investigation
276321
- Exclude from unreferenced URL list
277322

@@ -335,7 +380,7 @@ Features implemented:
335380
- Comprehensive test suite: 19 tests with 69% coverage
336381

337382
Package structure:
338-
- Uses Django&#39;s native management command structure
383+
- Uses Django's native management command structure
339384
- Installable via pip with pyproject.toml configuration
340385
- Supports Django 3.2+ and Python 3.8+
341386
- CLI options for custom output formats, file export, and app filtering

README.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ A Django dead code analysis tool that tracks relationships between templates, UR
1919
- **View Tracking**: Identify which templates are used by which views
2020
- **Python Code Analysis**: Detect `reverse()` and `redirect()` URL references in Python code
2121
- **Relationship Mapping**: Track template inheritance (extends/includes) and relationships
22+
- **Smart Template Detection**: Templates referenced via `{% include %}` or `{% extends %}` are correctly marked as used
23+
- **Project Boundary Filtering**: Automatically excludes templates from installed packages (outside BASE_DIR)
2224
- **Multiple Output Formats**: Console, JSON, and Markdown reports
2325
- **Django Native**: Uses Django's management command structure for seamless integration
2426

@@ -94,6 +96,16 @@ python manage.py finddeadcode --apps myapp otherapp
9496
python manage.py finddeadcode --templates-dir /path/to/templates
9597
```
9698

99+
### Show Template Relationships
100+
101+
By default, template include/extends relationships are hidden in reports. To show them:
102+
103+
```bash
104+
python manage.py finddeadcode --show-template-relationships
105+
```
106+
107+
This is useful for understanding how templates are connected but can make reports verbose for large projects.
108+
97109
## What It Detects
98110

99111
### Unreferenced URL Patterns
@@ -131,14 +143,17 @@ class MyView(UpdateView):
131143

132144
### Unused Templates
133145

134-
Templates that exist but are not referenced by any view:
146+
Templates that exist but are not referenced by any view (directly or indirectly through includes/extends):
135147

136148
```python
137149
# views.py - No view renders 'unused_template.html'
150+
# And no other template includes or extends it
138151

139152
# But the file templates/unused_template.html exists
140153
```
141154

155+
**Note**: Templates referenced via `{% include %}` or `{% extends %}` are now correctly identified as used, even if not directly referenced by views.
156+
142157
### Template Relationships
143158

144159
Tracks which templates include or extend other templates:
@@ -151,6 +166,8 @@ Tracks which templates include or extend other templates:
151166
{% include 'partials/header.html' %}
152167
```
153168

169+
Use the `--show-template-relationships` flag to see these relationships in your report.
170+
154171
## Example Output
155172

156173
```
@@ -189,34 +206,37 @@ These templates are not directly referenced by views (may be included/extended):
189206

190207
## How It Works
191208

192-
1. **Template Analysis**: Scans all template files for:
209+
1. **Template Analysis**: Scans all template files **within your project's BASE_DIR** for:
193210
- `{% url 'name' %}` tags
194211
- `href="/path/"` attributes (internal links)
195212
- `{% include 'template' %}` tags
196213
- `{% extends 'template' %}` tags
197214

198-
2. **URL Pattern Discovery**: Inspects Django's URL configuration to find all defined URL patterns and their names
215+
2. **Project Boundary Filtering**: Only templates within your project's `BASE_DIR` are analyzed. Templates from installed packages (e.g., Django admin, third-party apps) are automatically excluded.
199216

200-
3. **View Analysis**: Parses Python files to find:
217+
3. **URL Pattern Discovery**: Inspects Django's URL configuration to find all defined URL patterns and their names
218+
219+
4. **View Analysis**: Parses Python files to find:
201220
- `render(request, 'template.html')` calls
202221
- `template_name = 'template.html'` in class-based views
203222

204-
4. **Reverse/Redirect Analysis**: Uses AST parsing to detect:
223+
5. **Reverse/Redirect Analysis**: Uses AST parsing to detect:
205224
- `reverse('url-name')` calls
206225
- `reverse_lazy('url-name')` calls
207226
- `redirect('url-name')` calls
208227
- `HttpResponseRedirect(reverse('url-name'))` patterns
209228
- Dynamic URL patterns (f-strings, concatenation) are flagged for manual review
210229

211-
5. **Relationship Mapping**: Connects templates ↔ URLs ↔ views to identify dead code
230+
6. **Transitive Template Detection**: Recursively traces template relationships to mark templates as used if they're referenced via `{% include %}` or `{% extends %}` from any used template
231+
232+
7. **Relationship Mapping**: Connects templates ↔ URLs ↔ views to identify dead code
212233

213234
## Limitations
214235

215236
- **Static Analysis Only**: Does not execute code or track runtime behavior
216237
- **Dynamic Templates**: Cannot detect templates loaded with dynamic names (e.g., `render(request, f'{variable}.html')`)
217238
- **Dynamic URLs**: Cannot automatically detect URLs generated with f-strings or concatenation (but flags them for manual review)
218-
- **Indirect Usage**: May flag templates used only through includes/extends as "unused"
219-
- **Third-party Packages**: Analyzes your code only, not installed packages
239+
- **Third-party Packages**: Analyzes your code only, not installed packages (templates outside BASE_DIR are automatically excluded)
220240

221241
## Development
222242

0 commit comments

Comments
 (0)