Fix 'The keyword enum is reserved' in ESLint + TypeScript
Why replacing react-app with airbnb drops the TypeScript parser Create React App quietly provided, and the one-line fix for both flat config and the now-removed .eslintrc format.
TL;DR#
ESLint's default parser, Espree, is built on Acorn, which has
no concept of a TypeScript enum declaration. Acorn tokenises the bare word enum as a plain
identifier, checks it against its own ECMAScript reserved-word list, finds it there, and raises
the error below before it ever reaches the {. The fix is to point ESLint at
@typescript-eslint/parser instead of the
default — one line, whether the project uses the flat eslint.config.mjs ESLint 9 and 10 read, or
the .eslintrc format ESLint 9 and earlier supported (ESLint 10 removed it outright — see the
Fix section). Raising ecmaVersion does not help; it was reproduced below and the error does
not move.
Parsing error: The keyword 'enum' is reservedThis is the exact message from the Stack Overflow
question
(10 votes, over 9,000 views): a Create React App project that linted its TypeScript fine until
.eslintrc.json switched to extends: ["airbnb"]. Reproduced on 26 October 2026 on the host
(Node.js v26.7.0), with eslint 9.39.2, @typescript-eslint/parser 8.63.0 and typescript
5.9.3 — the exact versions this reproduction installed, not necessarily the ones in the
original question's project.
Why it happens#
ESLint doesn't parse JavaScript itself — it delegates to a parser, and the default one is
Espree, which wraps Acorn. Neither
knows what a TypeScript enum is, because enum Color { ... } is not ECMAScript syntax; it's a
TypeScript-only extension, never part of the language, not merely newer than this parser. When
Acorn's statement parser meets the word enum at the start of a statement, not after export
(see the table and mechanism further down), it has nothing better to do with it than treat it as
an identifier reference, so it calls the same function it uses to check any other identifier:
checkUnreserved.
That function tests the word against Acorn's own reserved-word
list — and enum
is on it. Acorn's source for the version this reproduction installed (8.15.0, pulled in by
eslint@9.39.2 → espree@10.4.0 → acorn@^8.15.0) spells out the list directly:
export const reservedWords = {
3: "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile",
5: "class enum extends super const export import",
6: "enum",
strict: "implements interface let package private protected public static yield",
strictBind: "eval arguments"
}enum sits in the ECMAScript-6-and-above list on its own (6: "enum"), unconditionally — not
only in strict mode. The moment checkUnreserved matches it, Acorn raises `The keyword '${name}' is reserved` — the literal source of the message this page is about. That's also why
raising parserOptions.ecmaVersion never helps, however high: the grammar reserves the word, it
just never shipped a feature that uses it, so there's no version where a plain parser accepts it.
This was reproduced directly: with ecmaVersion: 'latest' and the default parser, the identical
error came back on the same file. ESLint's own documentation on configuring a
parser confirms Espree is the default and
lists @typescript-eslint/parser by name as a parser known to work with ESLint.
Why this specific project hit it: the question itself says "I used react-app config for the
.eslintrc.json" before switching to Airbnb, and eslint-config-react-app's own
source
carries a built-in override — files: ['**/*.ts?(x)'], parser: '@typescript-eslint/parser' —
that quietly parses every .ts/.tsx file with the TypeScript-aware parser. Switching extends
to ["airbnb"] alone drops that override: eslint-config-airbnb's own package
metadata lists eslint-config-airbnb-base,
object.assign and object.entries as dependencies and eslint plus four eslint-plugin-*
packages as peer dependencies — no @typescript-eslint/parser anywhere — so ESLint falls back to Espree on the
exact same files that used to lint cleanly.
Fix#
1. Install the parser (skip if already a dependency):
npm install --save-dev @typescript-eslint/parser2. Flat config (eslint.config.mjs, the default since ESLint 9 and, since ESLint 10, the
only format ESLint reads at all — more on that below) — set the parser under
languageOptions.parser, covering both .ts and .tsx:
import tsParser from '@typescript-eslint/parser';
export default [
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: 'module',
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
},
];3. Legacy .eslintrc.json — the exact one-line fix from the 2020 accepted answer, reproduced
below on ESLint 9.39.2 with a deprecation warning. Add the parser key at the top level:
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module"
}
}On ESLint 8 and earlier this is the default format: nothing else to do. On ESLint 9, flat config
is the default instead, so this file is only read with ESLINT_USE_FLAT_CONFIG=false set (the
exact condition this reproduction ran under); without it, ESLint 9 looks for eslint.config.js
and ignores this file. On ESLint 10, neither applies: ESLint 10.0.0's own release
notes (published 6 February 2026) list a
breaking change, feat!: remove eslintrc support, and 10.13.0 — not a 9.x release — is the
latest tag on npm as of this reproduction, so step 2 is the only option there.
4. If the project uses eslint-config-airbnb specifically, check which ESLint it actually
runs on first: eslint-config-airbnb 19.0.4's own peer dependencies are
"eslint": "^7.32.0 || ^8.2.0" — it doesn't list ESLint 9 or 10 in that range at all, so a
project that still has it installed is normally on ESLint 7 or 8, on the .eslintrc format by
default there (step 3, no environment variable needed on 8 and earlier). Moving that project to
flat config (step 2) fixes only the parsing error; step 2's block above contains none of
Airbnb's actual rules, so the rest of them would need a separate migration, not just this one
line. A separate, third-party package —
eslint-config-airbnb-typescript,
maintained independently of Airbnb by iamturns, not by Airbnb itself — sets the same
parser: '@typescript-eslint/parser' key internally for the .eslintrc format (confirmed
directly in its
lib/shared.js,
line 12). GitHub lists that repository as
archived; its README is headed
"This repo has been archived" and adds, in the maintainer's words: "After six years and reaching
2 million weekly downloads, I can no longer give this project the attention it deserves" — it
points to eslint-config-airbnb-extended as an alternative. Installing a shareable config just
for one parser line is optional either way: step 3 alone is enough to clear this error on the
version this reader is actually running.
One more check before installing anything: the current @typescript-eslint/parser (8.72.1, and
this reproduction's 8.63.0 alike) declares peer dependencies
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" — it covers neither ESLint 7.x nor any ESLint 8 release
below 8.57.0, only 8.57.0 and later. A reader whose eslint-config-airbnb install keeps them
below that may see a peer dependency conflict installing today's parser release; picking an older
@typescript-eslint/parser release whose own peer range covers that ESLint version is the
general way out of that kind of conflict (not reproduced here for this specific pair of
versions).
5. Full TypeScript linting, not just parsing. The parser alone only stops the crash. For the
complete, officially documented rule set, install the whole quickstart (this adds packages step 1
didn't, @eslint/js and the typescript-eslint meta-package):
npm install --save-dev eslint @eslint/js typescript typescript-eslintThen the typescript-eslint package's own
flat-config quickstart — quoted here verbatim from its docs, not independently reproduced in this
session:
// @ts-check
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig({
files: ['**/*.{js,ts}'],
extends: [js.configs.recommended, tseslint.configs.recommended],
});For a project with .tsx files too, the same docs widen files to
['**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}']. tseslint.configs.recommended does bundle the same
parser fix as step 2: the installed @typescript-eslint/eslint-plugin 8.63.0 package's own
dist/configs/flat/recommended.js composes base(plugin, parser) as its first entry, and
dist/configs/flat/base.js is exactly languageOptions: { parser, sourceType: 'module' } —
confirmed by reading both files directly in node_modules, not just the docs.
The install command above pins no versions, so it installs whatever latest is today for each
package: ESLint 10.13.0 (consistent with a flat-config-only quickstart) and, checked on the npm
registry 2026-10-26, typescript 7.0.2. @typescript-eslint/parser and
@typescript-eslint/eslint-plugin 8.x declare "typescript": ">=4.8.4 <6.1.0" as a peer
dependency — 7.0.2 is well outside that range. Pin typescript to a release the installed
@typescript-eslint packages actually declare support for, such as the 5.9.3 this reproduction
used, rather than running the quickstart's install line unpinned.
Verify the fix#
With a flat eslint.config.mjs (step 2 or 5), run ESLint the normal way — its own files glob
decides what gets linted, so a directory scan already reaches every .ts/.tsx file:
npx eslint .Reproduced directly: scanning this reproduction's whole fixture directory with the fixed flat
config reported zero problems on every enum/interface/type-annotation fixture (the only
findings left were unrelated rule errors from other, unrelated .ts reference files sitting in
that same folder).
With an .eslintrc.json on ESLint 9 (step 3), eslint . alone is not enough, even with the
environment variable set. Reproduced: pointed at this reproduction's directory, the legacy engine
skipped every .ts/.tsx fixture entirely and linted an unrelated .js file instead — not a
pass, a file list that never included the files this page is about. The legacy engine's default
scan is JavaScript files only; add --ext:
ESLINT_USE_FLAT_CONFIG=false npx eslint . --ext .ts,.tsxReproduced with that flag added, same directory, same .eslintrc.json: every enum/interface
fixture cleared, and only the same unrelated files kept their unrelated findings. On ESLint 8 and
earlier, drop the environment variable — eslintrc is simply the default there — but --ext
is still needed for the same reason.
Either way, once the right files are actually being scanned, the exact line that previously
failed, Parsing error: The keyword 'enum' is reserved, no longer prints. A project with real
lint rules enabled (Airbnb's, or typescript-eslint's own from step 5) will still print its
normal rule findings, and step 3's ESLintRCWarning deprecation notice, afterwards; what
disappears is only the Parsing error line itself.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
The 2026 mistake: pasting the old fix into a flat eslint.config.mjs#
The accepted answer's exact line, "parser": "@typescript-eslint/parser", is for .eslintrc.
Flat config has no top-level parser key at all, so pasting that line as-is into
eslint.config.mjs — instead of nesting it under languageOptions.parser as in step 2 — does not
reproduce this page's error. It fails ESLint's own config validation first, with ESLint naming
the right key:
Oops! Something went wrong! :(
ESLint: 9.39.2
A config object is using the "parser" key, which is not supported in flat config system.
Flat config uses "languageOptions.parser" to override the default parser.
Please see the following page for information on how to convert your config object into the correct format:
https://eslint.org/docs/latest/use/configure/migration-guide#custom-parsersIf a project is still on the legacy format, this doesn't apply — the plain "parser" string is
correct there, and step 3 above reproduced it working, deprecation warning and all: ESLint 9
still accepts .eslintrc files with ESLINT_USE_FLAT_CONFIG=false set, logging
ESLintRCWarning: ... support will be removed in v10.0.0 — and, as step 3 covers, ESLint 10 has
since shipped and removed it outright, that environment variable included. Moving to flat config
means moving the key, not just copying the line across.
Other TypeScript syntax fails the same way, with different wording#
enum is not the only TypeScript construct a default-parser project trips over, and the exact
wording isn't only about which word is involved — whether the declaration is exported changes it
too. Reproduced on four small files, same broken config throughout:
| File | Message |
|---|---|
enum Color { ... } | Parsing error: The keyword 'enum' is reserved |
export enum Color { ... } | Parsing error: Unexpected token enum |
interface Foo { x: number } | Parsing error: The keyword 'interface' is reserved |
export interface Foo { x: number } | Parsing error: Unexpected token interface |
A bare declaration is parsed as an expression statement, which runs the same identifier check
described above and hits "is reserved" for any word on Acorn's list — interface included, since
modules are always strict mode and interface sits in reservedWords.strict. Acorn's exported
form goes through a dedicated parseExport
function instead,
which only recognises var, const, class, function, let and async function as the
start of an export declaration
(shouldParseExportStatement);
enum and interface aren't on that list, so parseExport falls through to the branch that
expects export { ... } specifiers, finds enum or interface where it expected a {, and
rejects it immediately as "Unexpected token" — the identifier check never runs. The fix was
reproduced against all four rows of the table above, not only the enum row: the same parser
swap clears every one of them. A plain type annotation with no enum or interface anywhere
fails too, with yet another wording, for the same underlying reason — no TypeScript-aware parser
configured:
export function double(x: number): number {
return x * 2;
}Parsing error: Unexpected token :The wording differs because : number isn't a reserved word, just a token the plain grammar
doesn't expect there, while a bare enum or interface specifically hits Acorn's reserved-word
check. The precise wording of a parsing error, not its general shape, is what tells you which
exact gap in the grammar caused it — "is reserved"
points at Acorn's reserved-word list, "Unexpected token" points at a token the grammar simply has
no rule for. The same parser swap — step 2 or 3 above — clears all four wordings in the table,
and the type-annotation wording too; the underlying fix is the same for any TypeScript syntax the
default parser has no grammar for, whatever the exact wording turns out to be.
ESLint isn't the only tool whose parser throws on code it wasn't built to read, for one of two
different reasons. Node's own module loader rejects import statements on an old runtime with a
different message, because that runtime
predates ES module syntax in .js files — a real gap in time, closed by upgrading Node. This
page's enum and interface are not that: they were never part of any ECMAScript edition, so no
amount of upgrading Acorn would close the gap, only swapping the parser does. Vite 2's production
build fails on node_modules
code for a third reason entirely: a define text-replacement step turns a valid identifier into
invalid syntax before Rollup's own parser ever sees it, so the code itself, not the parser, is
where that one starts. The same symptom, a parser error, can come from a parser that is older
than the syntax (the Node case), a parser that was never going to support non-standard syntax at
all (this page), or a build step that corrupts otherwise-valid syntax before any parser reads it
(the Vite case). Reading the exact wording, as throughout this page, is what tells the three
apart.
If the next error is about parserOptions.project, or a version mismatch#
Setting the parser is usually the first of two steps, not the whole journey. The moment a project
also wants type-aware rules (anything that needs to know a value's actual type, not just its
syntax), parserOptions.project has to point at a tsconfig.json that genuinely includes every
linted file — Cypress spec files are one well-documented place this breaks, with a parsing error
of its own: see Fix 'ESLint was configured to run on' in Cypress
tests for the two ways that
file ends up outside every configured TSConfig: an exclude overriding include, or a relative
project path that resolves wrong without tsconfigRootDir.
The other common follow-on shows up later, after an upgrade: if @typescript-eslint/parser and
@typescript-eslint/eslint-plugin are pinned to an old major version while typescript itself
moves forward, the parsing error changes shape again, to DeprecationError: 'originalKeywordKind' has been deprecated — a version-mismatch problem, not a missing-parser one. See Fix
'originalKeywordKind' DeprecationError in
TypeScript if that's the
message that shows up instead.
Either way, the pattern holds across every case reproduced on this page: ESLint's parsing errors
name the exact thing the parser couldn't make sense of, and that exact wording — not the general
shape of "a parsing error happened" — is what tells you whether the fix is a missing parser, an
incomplete tsconfig.json, or a version that drifted out of sync.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix 'ESLint was configured to run on' in Cypress tests
Keep the Cypress folder out of the root tsconfig, as Cypress recommends, and still lint every .cy.ts file with type information.
Fix 'was not found by the project service' in ESLint
Stop ESLint from type-checking your compiled dist output, and avoid the newer project-vs-projectService conflict that now hides this exact message.
Fix 'originalKeywordKind' DeprecationError in TypeScript
The 'originalKeywordKind' DeprecationError fires when @typescript-eslint packages are stuck on v5 while TypeScript moved on. Upgrade to v6.x and restart ESLint to clear it.


