Skip to content

require-explicit-id matches ICU components by JSX name, not import — false positives on UI Select and false negatives on aliased macros #139

Description

@Zach-Jaensch

First, thank you

We've been using eslint-plugin-lingui across a large monorepo for some time and it's become something we genuinely rely on. With a big team — many of whom don't need to know the intricacies of i18n day to day — these rules quietly enforce the right thing: making sure every message carries the context translators need. That confidence collapses our development cycles, since problems are caught at lint time instead of surfacing later in review or translation. require-explicit-id is a big part of that, which is why we wanted to report this carefully rather than just disable it.

Describe the bug

Since the ICU-component check was added to require-explicit-id (#131, released in 0.14.0), the rule matches purely on the JSX element name (Plural, Select, SelectOrdinal), with no check on the import binding. This produces two symmetric problems:

  1. False positive — any element named Select is flagged as a "Lingui ICU component", even a plain UI dropdown (shadcn/ui, Radix, MUI, Ant Design, …) that has nothing to do with Lingui. Select is an extremely common component name, so this fires broadly. In our codebase, enabling 0.14.0 produced 30 errors — only 4 were genuine <Plural> messages; the other 26 were ordinary UI <Select> components.
  2. False negative — a genuine Lingui ICU macro imported under an alias (import { Select as I18nSelect }) is silently ignored, because the JSX name as written (I18nSelect) no longer matches.

The ICU query matches on element name only:

// helpers.ts
export const LinguiIcuComponentQuery =
  ':matches(JSXElement[openingElement.name.name=Plural], JSXElement[openingElement.name.name=SelectOrdinal], JSXElement[openingElement.name.name=Select])'

To Reproduce

Minimal repro: https://github.com/Zach-Jaensch/lingui-require-explicit-id-repro

git clone https://github.com/Zach-Jaensch/lingui-require-explicit-id-repro
cd lingui-require-explicit-id-repro
npm install
npm run lint

src/example.tsx contains three cases:

Element Source Should report? Actual
<Select> (ThemePicker) local UI component (./ui/select) ❌ no ⚠️ reported
<Plural> (ItemCount) @lingui/react/macro (canonical name) ✅ yes ✅ reported
<I18nSelect> (Greeting) @lingui/react/macro (Select as I18nSelect) ✅ yes ❌ not reported

Actual output

src/example.tsx
  15:5   error  Lingui ICU component requires an explicit 'id' attribute  lingui/require-explicit-id
  27:10  error  Lingui ICU component requires an explicit 'id' attribute  lingui/require-explicit-id

The exact inversion — a non-Lingui Select reported while a real (aliased) Lingui Select is not — confirms the check is keyed on the element name rather than the import.

Expected behavior

The rule should apply only to elements actually imported from a Lingui macro entry point, regardless of local alias — mirroring the "only apply to code that is 100% sure to be processed by lingui" principle already established for text-restrictions / no-unlocalized-strings in #41.

Suggested fix

Gate the ICU-component check (and ideally the <Trans> check too) on the element's binding resolving to an import from @lingui/react/macro / @lingui/macro, rather than matching the raw JSX name. This resolves the false positive and the false negative in one change.

Environment

  • eslint-plugin-lingui: 0.14.0
  • eslint: 9.x
  • @typescript-eslint/parser: 8.x
  • Node: >=18

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions