Skip to content

Add granularity to no-external-call-in-pure-ui for hooks and helper calls #6

Description

@zac-jung

Problem

branded-ui-react/no-external-call-in-pure-ui is useful as an architecture gate, but today it treats every external call inside inline Branded UI declarations the same way.

That makes gradual adoption hard in a real codebase because very different call kinds need different policies:

  • React/runtime hooks such as useState, useEffect, useForm are strong signals that behavior should move to a Binding/model hook.
  • UI-owned translation hooks may be intentionally allowed by project policy.
  • Pure or presentation helpers such as cn, predicates, formatters, and simple mapping helpers may be acceptable in Pure UI.
  • Domain/model helpers such as validation, normalization, filtering, and field-error extraction may be architecture smells, but teams may want to migrate them separately from hook bans.

The current allowedCalls option helps for exact names, but it is too coarse for this rollout: it cannot express "warn/error on hooks first, allow translations, review model helpers later" without either allowlisting many names or reporting many acceptable helper calls.

Concrete downstream audit

Against a Branded UI-migrated frontend using @jayjnu/oxlint-plugin-branded-ui-react@0.1.1, enabling no-external-call-in-pure-ui on frontend/src reported:

109 total diagnostics across 27 files
67 hook-call diagnostics across 25 files
42 non-hook/helper diagnostics across 8 files

Hook diagnostics:

16 useState
1  useEffect
1  useForm
46 useXxxTranslation
3  useMemberCredentialStateLabel

Non-hook/helper diagnostics:

16 validation helpers       validateName, validateEndpoint, validateCredential, validateAuthHeader, getFieldError
10 callback/ref helpers    previewRef, <expression>
6  builtin/collection      Boolean, appLocales.map, providerTypes.map
4  predicates              isThemePreference, isAppLocale, isProgressState, isFailureState
3  presentation helpers    badgeVariant, progressDescription, failureDescription
2  transforms              filterGatewayKeyList, normalizeMcpServerFormValues
1  styling helper          cn

Some of these are useful findings: validation/normalization/filtering probably belong in a model hook or Binding. Others, like Boolean, .map, cn, and presentation-only mapping, are not useful blockers for adopting a hook ban.

Requested capability

Please consider adding rule granularity so projects can adopt this rule incrementally. Possible shapes:

  1. A mode focused on hooks first:
{
  "branded-ui-react/no-external-call-in-pure-ui": [
    "warn",
    { "mode": "hooks-only" }
  ]
}
  1. Pattern-based allow/deny lists:
{
  "allowedCallPatterns": ["^use.*Translation$", "^cn$", "^Boolean$", "\\.map$"],
  "deniedCallPatterns": ["^use(State|Effect|Form)$"]
}
  1. Import/module-aware policy:
{
  "allowedModules": ["@/shared/ui/**", "@/shared/i18n/**"],
  "deniedModules": ["@tanstack/react-form", "react"]
}
  1. Separate rules, for example:
  • no-hook-call-in-pure-ui
  • no-model-helper-call-in-pure-ui
  • keep no-external-call-in-pure-ui as the strict all-calls mode

Expected outcome

A project should be able to start with a low-noise policy such as:

  • allow UI-owned translation hooks;
  • warn on useState, useEffect, useForm inside Pure UI;
  • later migrate validation/normalization/filtering helpers into model hooks;
  • avoid noise from builtin/presentation helpers.

This would keep the rule useful as an architecture guard without forcing teams to choose between "off" and "strict all external calls".

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions