Migrating from @graphql-eslint/eslint-plugin
@graphql-analyzer/eslint-plugin is designed as a drop-in replacement for
@graphql-eslint/eslint-plugin.
Plugin name, rule names, message text, source positions, and messageId
values all match the upstream plugin where the rule is shared. CI enforces
this with a parity test that fails on any unexplained drift.
This page documents what to change in your project, what is structurally identical, and what behaviors still diverge during the alpha.
For most projects, migration is two find-and-replace passes:
@graphql-eslint/eslint-plugin→@graphql-analyzer/eslint-plugin(inpackage.jsonand your ESLint config imports).- The plugin object you bind in flat config keeps the same
@graphql-eslintkey by convention — but you may switch it to@graphql-analyzerto match the package name. Rule names below the prefix are unchanged.
If you don’t customize message text or messageIds, nothing else needs to change.
What’s identical
Section titled “What’s identical”- Rule names. All 31 shared rules use the same kebab-case name as upstream. Migration to a new prefix is mechanical.
- Recommended preset names.
flat/schema-recommendedandflat/operations-recommendedare exposed under theconfigsexport. - Rule options. Options pass through unchanged — anything the upstream
rule accepted, this plugin accepts (and ignores unknown keys rather than
rejecting them, for forward compatibility). Options can come from either
ESLint’s
rules: { rule: [severity, options] }config or theextensions.graphql-analyzer.lint.rulesblock in.graphqlrc.yaml; ESLint takes precedence per-rule when both are present. .graphqlrc.yamlresolution. Both plugins read schema/documents from the same graphql-config file.- Diagnostic shape.
LintMessage.message,messageId,line,column,endLine,endColumn, andfixare populated to match upstream per-diagnostic-site. The parity test pairs every diagnostic by source position and fails on any divergence.
What changed (rule renames)
Section titled “What changed (rule renames)”Three rules were renamed during the alpha so all shared rules are 1:1 by name. If you were using these earlier names, update your config:
| Old name (graphql-analyzer alpha) | New name (matches graphql-eslint) |
|---|---|
@graphql-analyzer/unused-fields | @graphql-analyzer/no-unused-fields |
@graphql-analyzer/unused-fragments | @graphql-analyzer/no-unused-fragments |
@graphql-analyzer/unused-variables | @graphql-analyzer/no-unused-variables |
In .graphqlrc.yaml lint config, the camelCase keys are now noUnusedFields,
noUnusedFragments, noUnusedVariables.
Validation rules: handled, accepted, but not configurable
Section titled “Validation rules: handled, accepted, but not configurable”@graphql-eslint exposes 30+ rules that are direct re-exports of
graphql-js’s specifiedRules (known-type-names,
fields-on-correct-type, no-undefined-variables, etc.). The same checks
always run inside graphql-analyzer’s validation pass, so:
"@graphql-eslint/known-type-names": "error","@graphql-eslint/fields-on-correct-type": "error","@graphql-eslint/no-undefined-variables": "error",…can stay in your config when you migrate. The plugin exposes each of
those names as a no-op stub so your config loads cleanly — the
configurable entry doesn’t itself emit anything, but the underlying
check still runs and fires diagnostics regardless. The full stub list
lives in packages/eslint-plugin/test/parity.test.mjs (STUB_RULES).
What graphql-analyzer adds
Section titled “What graphql-analyzer adds”A handful of rules ship under @graphql-analyzer that don’t exist in
graphql-eslint. Adding them is optional; they don’t conflict with existing
configs:
operation-name-suffix— operation names should end inQuery/Mutation/Subscription.redundant-fields— flags fields that are already covered by a sibling fragment spread.require-selections— enforces required field selections (e.g.id,__typename) on object types.unique-names— enforces project-wide uniqueness of operation/fragment names (useful for persisted-queries setups).
Known limitations during alpha
Section titled “Known limitations during alpha”- ESLint legacy config (
.eslintrc.*) is not supported. Flat config only. - Autofix coverage matches upstream. Both plugins ship
fixonly onalphabetize. Upstream additionally shipssuggest(ESLint suggestions) on a number of rules; those have not yet been wired through our shim and surface as plain diagnostics here. Open an issue if a specific rule’s suggestion is blocking your migration.
Verifying migration
Section titled “Verifying migration”The plugin ships a parity test that runs @graphql-analyzer/eslint-plugin
and @graphql-eslint/eslint-plugin against the same fixture per shared
rule and asserts:
- Same diagnostic count.
- Same
messagetext. - Same
messageId. - Same
line/column/endLine/endColumn. - Same
fix(or bothnull).
If you migrate and see different results, that’s a parity bug — please open an issue.
In addition to the live integration parity test
(packages/eslint-plugin/test/parity.test.mjs, which runs both plugins
side by side), every shared rule has its valid:/invalid: cases
ported verbatim from upstream’s unit tests into Rust tests under
crates/linter/src/rules/upstream/. Each ported case carries a
permalink to the original upstream source line at a pinned SHA, and
runs against the rule implementation directly — failures point at a
specific upstream test the rule no longer matches.