Skip to content

feat(framework-edges): TYPO3 convention-file awareness#114

Open
CybotTM wants to merge 1 commit intorepowise-dev:mainfrom
CybotTM:feat/typo3-framework-edges
Open

feat(framework-edges): TYPO3 convention-file awareness#114
CybotTM wants to merge 1 commit intorepowise-dev:mainfrom
CybotTM:feat/typo3-framework-edges

Conversation

@CybotTM
Copy link
Copy Markdown

@CybotTM CybotTM commented Apr 30, 2026

Summary

  • Adds TYPO3 framework-edges detection so dead-code analysis stops flagging convention-loaded files (ext_localconf.php, Configuration/TCA/*.php, etc.) as unreachable. Mirrors the Laravel/Rails/Django pattern: composer-based discovery + synthetic edges.
  • Fixes a latent CLI bug — repowise dead-code never invoked add_framework_edges, so even existing Laravel/Rails/Django repos showed convention files as dead. The CLI now runs the same edge-synthesis pipeline as the indexing path.
  • Introduces a framework: synthetic-node prefix distinct from external:. Framework-mediated wiring (TYPO3 core loading every Configuration/*) now counts as a cross-package importer in zombie-package detection, preventing convention dirs like Configuration/ from showing as zombies. external: (third-party imports) still doesn't.

What's new for TYPO3

Discovery (in framework_edges._find_typo3_extension_roots):

  • Primary: composer.json with "type": "typo3-cms-extension" — canonical across v11→v14, present even when ext_emconf.php is gone in v14.
  • Fallback: any ext_emconf.php in path_set (legacy non-composer installs).
  • Walks repo root, monorepo subdirs, and vendor/<vendor>/<package>/composer.json for project-mode TYPO3 installs. Skips node_modules, .git, .bare, Build, hidden dirs.

Edges added:

  • From framework:typo3-core to convention files at the extension root (ext_localconf.php, ext_emconf.php, ext_tables.php (legacy), ext_tables.sql).
  • From framework:typo3-core to Configuration/* convention files (PHP and YAML): JavaScriptModules.php, ContentSecurityPolicies.php, RequestMiddlewares.php, Icons.php, Services.{php,yaml,yml}, TCA/*.php, TCA/Overrides/*.php, Backend/*.php, RTE/*.{yaml,yml}.
  • From each extension's Configuration/JavaScriptModules.php to the JS files it registers — parsed via regex over 'EXT:<key>/...js' literals, with own-extension filtering so cross-extension references aren't fabricated.

tech_stack.detect_tech_stack: learns to read composer.json and emit TYPO3 / Symfony / Laravel framework labels (TYPO3 takes precedence when both match, since TYPO3 ships Symfony).

Why these are the right anchor points

  • composer.json type is canonical and version-stable — degrades gracefully when a v14 extension drops ext_tables.php (the file simply isn't a target; no false negative).
  • framework: predecessors model real framework-mediated dependencies (different semantics from third-party external: imports), so the analyzer can treat them differently in zombie-package detection without overloading the existing external: prefix.
  • Parsing JavaScriptModules.php by content is more accurate than a glob — only files actually registered get rescued from "unreachable", so genuinely orphaned JS still surfaces.

Test Plan

  • Tests pass — pytest tests/unit/1283 passed, 0 regressions, 19 new tests:

    • 10 in tests/unit/ingestion/test_typo3_framework_edges.py (composer-type discovery, legacy ext_emconf.php fallback, non-TYPO3 negative case, TCA/Backend/Services edges, v14 layout without legacy files, YAML-only Configuration/, JavaScriptModules.php parser, cross-extension JS skip, project-mode vendor/ discovery, node_modules/ skip).
    • 7 in tests/unit/generation/test_tech_stack.py (TYPO3/Symfony/Laravel composer detection, TYPO3-precedence-over-Symfony, plain-PHP-library negative, malformed JSON robustness).
    • 2 in tests/unit/test_dead_code.py (framework: predecessors rescue from zombie status; framework: nodes themselves not flagged dead).
  • Lint passes — ruff check clean on changed code (one pre-existing en-dash on line 381 of framework_edges.py is unrelated upstream code).

  • Mypy clean on changed files.

  • Real-world validation: ran repowise dead-code against 20 production TYPO3 extensions (~29,151 total findings):

    • 0 framework-file false positives across all 20 repos
    • 0 false Configuration/ zombie packages (was 1 before YAML coverage was added)
    • 8 Resources/ zombie findings remaining — confidence 0.5, safe_to_delete=False. These are legitimate signal: TYPO3's indirect loading paths (Fluid templates, XLF lookups, TypoScript references, EXT: resolution via getFileAbsFileName()) aren't traced. Out of scope for this PR.

    Repos validated: lehrerbuero (11.5k findings), nrc_template, t3x-contexts, t3x-cowriter, t3x-nr-image-sitemap, t3x-nr-llm, t3x-nr-passkeys-be, t3x-nr-saml-auth, t3x-nr-textdb, t3x-nr-vault, t3x-nr-xliff-streaming, t3x-rte_ckeditor_image, t3x-scheduler, t3x-sync, t3x-universal-messenger, temporal-cache-fix, ter, ter_fe2, ter_layout, ter_rest.

Out of scope (potential follow-ups)

  • TYPO3 indirect loading paths: Fluid templates, XLF translations, TypoScript references, EXT: path resolution. Would address the residual Resources/ zombie findings.
  • CKEditor plugin class methods: still surface as unused_export symbol-level findings since CKEditor invokes commands via dynamic dispatch internal to the editor.
  • Per-version convention deltas: composer constraint parsing to enable/disable specific edge targets per TYPO3 major (ext_tables.php was removed in v14; many extensions still constrain ^11.5 || ^12.4 || ^13.4 so the v1 union behavior is correct anyway). The hook is in place — adding constraint parsing later is purely additive.
  • Symfony / Drupal / WordPress framework edges: detection landed in tech_stack.py but no edge functions yet. Same scaffolding as TYPO3 once the maintainer is happy with the shape here.

Checklist

  • My code follows the project's code style
  • I have added tests for new functionality
  • All existing tests still pass
  • I have updated documentation (README, docs/LANGUAGE_SUPPORT.md, docs/CHANGELOG.md)

TYPO3 loads a fixed set of convention-named files from each extension at
bootstrap (`ext_localconf.php`, `Configuration/TCA/*.php`, etc.). These
were never imported via PHP/JS imports, so the static graph reported
`in_degree=0` and the dead-code analyzer flagged them as unreachable.

Adds:
- `_add_typo3_edges()` in `framework_edges.py`. Discovers extensions via
  `composer.json` `"type": "typo3-cms-extension"` (canonical for v11-v14)
  with legacy fallback to `ext_emconf.php`. Walks the repo root,
  monorepo subdirs, and `vendor/<vendor>/<package>` for project-mode
  installs. Anchors convention files (PHP and YAML) from a synthetic
  `framework:typo3-core` node. Parses `Configuration/JavaScriptModules.php`
  and adds edges to the JS modules it registers, so CKEditor plugins and
  backend modules become reachable precisely (no globbing).
- `tech_stack.detect_tech_stack` learns to read composer.json and emit
  TYPO3 / Symfony / Laravel framework labels.

Two related bugs fixed along the way:
- The `repowise dead-code` CLI never invoked `add_framework_edges`, so
  even Django/Laravel/Rails repos showed convention files as unreachable.
  The CLI now calls `detect_tech_stack` + `add_framework_edges` after
  `graph_builder.build()`.
- The dead-code analyzer skipped all `external:`-prefixed predecessors
  in zombie-package detection. That's correct for third-party imports
  but wrong for framework-mediated wiring. New `framework:` prefix is
  introduced for synthetic anchors and `_is_synthetic_node()` consolidates
  the skip rules: `framework:` nodes are ignored in unreachable / unused-
  export passes (like `external:`), but `framework:` predecessors *do*
  count as cross-package importers in zombie detection, preventing
  legitimate convention dirs (e.g. `Configuration/`) from showing up
  as zombie packages.

Tests: 19 new (10 TYPO3 framework-edge cases incl. v14-without-legacy,
project-mode vendor/ discovery, JavaScriptModules.php parsing, cross-
extension JS skip, node_modules skip; 7 tech_stack composer.json cases;
2 analyzer regression tests for the `framework:` prefix). Full suite:
1283 pass, 0 regressions.

Validated on 20 real-world TYPO3 extensions (~29k findings total):
zero framework-file false positives, zero legitimate Configuration
zombie packages.

Out of scope (follow-up work):
- TYPO3 indirect loading: Fluid templates, XLF translations, TypoScript
  references, `EXT:` path resolution via `getFileAbsFileName()`.
- CKEditor plugin class methods (called via dynamic dispatch by the
  editor framework).
- Per-version convention deltas (v15 deprecations etc.).
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