Add script to trace removable Vue provide values
What does this MR do?
Part of #604216
Adds scripts/frontend/trace_provide_inject_usage.mjs, a developer aid that reports whether the values a file provide:s can be removed — the inverse of the local-rules/vue-no-unused-injects rule (!242236 (merged)), which catches dead inject declarations. Removing a dead provide can't be decided by a single file, so this script reasons across the component tree.
How it decides
For each top-level provide key:
- Is it injected anywhere? AST-scan of all
injectdeclarations (handlinginject: { local: { from: 'x' } }aliasing and Composition-APIinject('x')). A key injected nowhere is unconditionally REMOVABLE (sound — nothing can consume it). - Is an injector reachable? Otherwise it walks the module import graph from the provider with dependency-cruiser (reusing the webpack aliases, following dynamic
import()). Because a component can only render as a descendant if its module is importable from the root, import-graph unreachability is a sound proof of "not a descendant" → LIKELY REMOVABLE. A reachable injector is flagged IN USE (possible) for runtime confirmation (shared barrels can link unrelated modules, so reachability is possible-not-proven).
Dynamic boundaries that limit soundness — global Vue.component(...) registration and unresolvable (computed) dynamic imports — are surfaced as INCONCLUSIVE.
Usage
node scripts/frontend/trace_provide_inject_usage.mjs <file|glob> [<file|glob>…]- Accepts files or globs (quote globs to avoid shell expansion); non-
.vue/.jsmatches and unparseable files are skipped. - Requires ripgrep (
rg) onPATH. - Files with a
provide:get one line per key, sorted dead-first (⚠️ ) ahead of in-use (✅ ); files without one collapse to a single skipped line. - Ends with a cross-file roundup of removal candidates.
Example Output
I picked a pretty extreme example: app/assets/javascripts/boards/index.js:
────────────────────────────────────────────────────────────────────────────────
app/assets/javascripts/boards/index.js — 57 keys
[V] Using Vue.js 2 (compiler 2)
IncrementalWebpackCompiler: Status – disabled
· 1174 modules reached
⚠️ REMOVABLE: Injected nowhere (3)
boardWeight
emailsEnabled
multipleAssigneesFeatureAvailable
⚠️ LIKELY REMOVABLE: No injector reachable (6)
labelsFilterBasePath ← ~/vue_shared/issuable/create/components/issuable_label_selector.vue
allowLabelEdit ← ~/sidebar/components/labels/labels_select_widget/labels_select_root.vue
hasIterationsFeature ← ee/work_items/pages/planning_view.vue +4
hasIssueWeightsFeature ← ee/work_items/pages/planning_view.vue +5
hasIssuableHealthStatusFeature ← ee/work_items/pages/planning_view.vue +6
hasOkrsFeature ← ~/work_items/pages/planning_view.vue +1
✅ IN USE: Injector reachable (48)
initialBoardId ← ~/boards/components/board_app.vue
disabled ← ee/boards/components/epics_swimlanes.vue +5 reachable
groupId ← ee/boards/components/group_select.vue +2 reachable
rootPath ← ~/boards/components/board_card_inner.vue
fullPath ← ee/boards/components/group_select.vue +26 reachable
groupPath ← ~/work_items/components/create_work_item.vue +2 reachable
initialFilterParams ← ~/boards/components/board_filtered_search.vue +1 reachable
boardBaseUrl ← ee/boards/components/board_filtered_search.vue +2 reachable
boardType ← ee/boards/components/issues_lane_list.vue +14 reachable
isGroup ← ~/work_items/components/work_item_detail_panel.vue +2 reachable
isGroupBoard ← ee/boards/components/issue_board_filtered_search.vue +9 reachable
isProjectBoard ← ee/boards/components/milestone_select.vue +4 reachable
currentUserId ← ~/boards/components/board_list_header.vue
labelsManagePath ← ~/sidebar/components/labels/labels_select_widget/dropdown_footer.vue +1 reachable
releasesFetchPath ← ~/boards/components/issue_board_filtered_search.vue
timeTrackingLimitToHours ← ~/boards/components/issue_time_estimate.vue +1 reachable
issuableType ← ee/boards/components/board_add_new_column.vue +11 reachable
hasMissingBoards ← ~/boards/components/boards_selector.vue
weights ← ~/boards/components/boards_selector.vue
isIssueBoard ← ee/boards/components/board_form.vue +7 reachable
isEpicBoard ← ee/boards/components/board_form.vue +10 reachable
reportAbusePath ← ~/work_items/components/work_item_abuse_modal.vue
issuesListPath ← ee/work_items/components/work_item_custom_fields_single_select.vue +2 reachable
canUpdate ← ~/sidebar/components/sidebar_editable_item.vue
canAdminList ← ~/boards/components/board_settings_sidebar.vue +2 reachable
canAdminBoard ← ~/boards/components/boards_selector.vue
allowLabelCreate ← ~/sidebar/components/labels/labels_select_widget/dropdown_footer.vue
isSignedIn ← ~/boards/components/issue_board_filtered_search.vue +1 reachable
canAdminLabel ← ~/work_items/components/work_item_labels.vue
epicFeatureAvailable ← ee/boards/components/issue_board_filtered_search.vue
iterationFeatureAvailable ← ee/boards/components/issue_board_filtered_search.vue
weightFeatureAvailable ← ee/boards/components/board_list_header.vue +1 reachable
scopedLabelsAvailable ← ee/boards/components/board_add_new_column.vue +3 reachable
milestoneListsAvailable ← ee/boards/components/board_add_new_column.vue
assigneeListsAvailable ← ee/boards/components/board_add_new_column.vue
iterationListsAvailable ← ee/boards/components/board_add_new_column.vue
healthStatusFeatureAvailable ← ee/boards/components/issue_board_filtered_search.vue
allowScopedLabels ← ~/sidebar/components/labels/labels_select_widget/dropdown_value.vue
swimlanesFeatureAvailable ← ~/boards/components/board_top_bar.vue
multipleIssueBoardsAvailable ← ~/boards/components/boards_selector.vue
scopedIssueBoardFeatureEnabled ← ~/boards/components/boards_selector.vue
allowSubEpics ← ~/boards/components/board_card_inner.vue
hasScopedLabelsFeature ← ~/vue_shared/components/filtered_search_bar/tokens/label_token.vue
hasSubepicsFeature ← ~/work_items/components/work_item_change_type_modal.vue +3 reachable
hasLinkedItemsEpicsFeature ← ~/work_items/components/work_item_detail.vue
hasCustomFieldsFeature ← ee/boards/components/issue_board_filtered_search.vue +2 reachable
statusListsAvailable ← ee/boards/components/issue_board_filtered_search.vue +1 reachable
hasStatusFeature ← ee/boards/components/issue_board_filtered_search.vue
================================================================================
Removal candidates (9):
app/assets/javascripts/boards/index.js:
REMOVABLE boardWeight
REMOVABLE emailsEnabled
REMOVABLE multipleAssigneesFeatureAvailable
LIKELY-REMOVABLE labelsFilterBasePath
LIKELY-REMOVABLE allowLabelEdit
LIKELY-REMOVABLE hasIterationsFeature
LIKELY-REMOVABLE hasIssueWeightsFeature
LIKELY-REMOVABLE hasIssuableHealthStatusFeature
LIKELY-REMOVABLE hasOkrsFeature
IN USE = possible descent (confirm at runtime); it is not proof of use.This report, in combination with clean up for .eslint_todo/local-rules-vue-no-unused-injects.mjs could help remove a lot of unused code.
Documentation
This MR also documents the workflow so both humans and agents can find it:
doc/development/fe_guide/tooling.md— a Trace unused Vue provides section covering how to run the script, what each verdict means, and how to remove a key layer by layer.doc/development/fe_guide/vue.md— theprovide/injectsection points to the tooling docs.
Notes for reviewers
- Developer tooling only, I don't think this could be wired to any CI verification. It's an investigation aid (the
IN USEverdict is intentionally "possible", to be confirmed at runtime!).