MetaMask / MetaMask/metamask-extension
[Epic] `ui/helpers/utils/util.js` decomposition (relocate helpers by subject)
- Dominant language
- TypeScript
- Stars
- 13.2k
- Forks
- 5.6k
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 451
Description
**Parent Epic:** [MetaMask-planning#5284 — [EPIC|P2] Typescript](https://github.com/MetaMask/MetaMask-planning/issues/5284) → area sub-epic [#7383 — TypeScript conversion of `ui/contexts`, `ui/helpers`, `ui/selectors`, `ui/ducks`, `ui/hooks`](https://github.com/MetaMask/metamask-extension/issues/7383)
**Labels:** `Epic`, `team-extension-platform`
---
## 🎯 Goal
Empty and delete `ui/helpers/utils/util.js` (1,021 lines, 54 exports) by **relocating each helper to its domain-appropriate module** — improving modularity, maintainability, and reviewability, and unblocking incremental JS→TS migration (most helpers land in modules that are already TypeScript). Decompose by subject; this is relocation, not arbitrary splitting.
## Motivation
`util.js` is a grab-bag catch-all: address display, ENS/URL parsing, chain/contract helpers, date formatting, snap helpers, account/keyring predicates, and dead code all share one file with no unifying subject. It is imported by **140 files**, so it is a hub whose churn touches the whole app, and it cannot be converted to TypeScript coherently in one pass. Moving each helper to a module that already owns its subject (most already `.ts`) shrinks the hub, advances those modules' own migrations, and — because several helpers are near-duplicates of existing ones — removes duplication (`getAssetImageURL`↔`asset-utils`, `getURL`↔`getValidUrl`, `sanitizeMessage`↔`sanitizeMessageRecursively`). Applies the #41735 decomposition model to a utils hub.
## Context
- `ui/helpers/utils/util.js` — 1,021 lines, 54 exports + 8 private helpers.
- **Blast radius: 140 importing files** across `ui/`, `shared/`, `app/`. **Three are namespace imports** (`import * as util`) that any import-rewrite codemod must special-case (two `dapp-swap-comparison` hook tests + one `confirm-add-suggested-nft` test).
- **Size is dominated by import-rewrite fan-out, not LOC** — the code per helper is tiny; the cost is rewiring call sites. High-fan-out relocations (`shortenAddress` 50, `getAccountByAddress` 21, `getSnapName` 20, the `getURL*` cluster) warrant a `jscodeshift`/`ts-morph` codemod, not manual edits.
## Scope
Each ticket is self-contained: move the helper(s) to the target module, move/keep their tests, codemod the import sites, delete the originals. Ordered lowest-fan-out / clearest-home first; the high-fan-out moves and the file deletion come last.
| Order | Ticket | Target | Size | Pri |
|---|---|---|---|---|
| 1 | Delete dead / test-only exports (10) | — | S | P1 |
| 2 | Numeric + typed-data helpers | `conversion.utils`/`number-utils`/`typed-signature` | S | P2 |
| 3 | Browser + clipboard | `browser-runtime.utils` / ui `browser.ts` | S | P2 |
| 4 | Token / contract / price | `token-util` | M | P2 |
| 5 | Asset image (+ dedupe) | `asset-utils` | M | P2 |
| 6 | String helpers | `string-utils` | M | P2 |
| 7 | Date / time formatters | new `ui/helpers/utils/date.ts` | M | P2 |
| 8 | URL / origin (+ dedupe) | `url-utils` | L | P1 |
| 9 | Snap helpers | `shared/lib/snaps` / ui `snaps.ts` | L | P2 |
| 10 | Account / keyring | `shared/lib/accounts` | L | P1 |
| 11 | Address display (`shortenAddress`) | new `ui/helpers/utils/address.ts` | XL | P1 |
| 12 | Close out: replace `isNullish`, inline `valuesFor`, delete `util.js` + test | — | S | P1 |
**Order rationale:** dead-code deletion first (zero rewrites, shrinks the file); low-fan-out moves to existing homes next; `shortenString` moves before the address group so `shortenAddress` can re-point; the three highest-fan-out moves (URL, account, address) are codemod-driven and land late; the file is deleted only once empty.
## ✅ Success Criteria
- All 54 exports relocated to subject-appropriate modules (existing where one owns the subject; new `date.ts`/`address.ts` only where none exists); near-duplicates reconciled.
- Import sites codemod-rewritten (incl. the 3 namespace-import tests); no behavior change.
- `ui/helpers/utils/util.js` and `util.test.js` **deleted** — the file does not survive as a near-empty catch-all.
- Tests pass; no new Sentry signatures.
Contributor guide
Research direction
Start with ui/helpers/utils/util.js and ui/helpers/utils/util.test.js, then inspect their 140 importing files and the ticket order in this issue. Begin with the dead or test-only exports, run the relevant utility tests, and proceed through the listed relocations; done means all exports and imports are moved, tests pass, and util.js plus util.test.js are deleted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- frontend, tooling
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100