Fix: ESLint Parsing error: Unexpected token (JSX, TypeScript, ES modules)
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix ESLint 'Parsing error: Unexpected token' for JSX, TypeScript, and ES module syntax by configuring the correct parser, parserOptions, and ESLint config format.
The Error
You run ESLint on your project and it throws:
Parsing error: Unexpected tokenOr one of its common variants:
Parsing error: Unexpected token <Parsing error: Unexpected token =>Parsing error: Unexpected token {Parsing error: Unexpected token importParsing error: Unexpected token :Parsing error: The keyword 'import' is reservedParsing error: Cannot use import statement outside a moduleThe error often points to a specific line in your code — a JSX angle bracket, a TypeScript type annotation, an arrow function, an import statement, or optional chaining syntax. ESLint stops dead and refuses to lint anything in that file.
This is the most common ESLint configuration error. It means ESLint’s parser doesn’t understand the syntax you’re using. The default parser only handles standard ECMAScript at whatever version you’ve configured. Anything beyond that — JSX, TypeScript, modern ECMAScript features, ES modules — requires explicit configuration.
Why This Happens
ESLint ships with Espree as its default parser. Espree supports standard ECMAScript syntax, but only the version and features you tell it about. Out of the box, it does not understand:
- JSX — the
<Component />syntax used by React and other frameworks. - TypeScript — type annotations like
const x: string, interfaces, generics, enums, and any TS-specific keyword. - Newer ECMAScript features — depending on the configured
ecmaVersion, things like optional chaining (?.), nullish coalescing (??), top-levelawait, or class fields may not parse. - ES module syntax —
importandexportstatements requiresourceType: "module".
When Espree hits syntax it doesn’t recognize, it throws “Parsing error: Unexpected token” and stops. The fix is always one of: switch to a parser that supports your syntax, or tell Espree which syntax features to enable.
Here is a quick reference for which syntax requires which setting:
| Syntax | Required Setting |
|---|---|
import / export | sourceType: "module" |
Optional chaining (?.) | ecmaVersion: 2020 or later |
Nullish coalescing (??) | ecmaVersion: 2020 or later |
Class fields (x = 1) | ecmaVersion: 2022 or later |
Top-level await | ecmaVersion: 2022 + sourceType: "module" |
import.meta | ecmaVersion: 2020 + sourceType: "module" |
| TypeScript syntax | @typescript-eslint/parser |
| JSX | ecmaFeatures: { jsx: true } or a JSX-aware parser |
Decorators (@decorator) | @babel/eslint-parser or @typescript-eslint/parser |
The exact fix depends on what syntax triggered the error. Below are the most common scenarios and their solutions.
Fix 1: Set the Parser to @typescript-eslint/parser (TypeScript Projects)
If you’re writing TypeScript and see the error on a type annotation, interface, enum, or any TypeScript-specific syntax, ESLint needs the TypeScript parser.
Install it:
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-pluginThen configure it in .eslintrc.json:
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
]
}Or in .eslintrc.js:
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
};The @typescript-eslint/parser replaces Espree entirely for .ts and .tsx files. It understands all TypeScript syntax and feeds the AST to ESLint in a format it can work with. Without it, ESLint chokes on the very first colon in a type annotation — which is why you see Parsing error: Unexpected token : on something like const name: string = 'hello'.
If you’re also seeing Type ‘X’ is not assignable to type ‘Y’ errors in your TypeScript project, that is a separate compiler error — but getting the parser right is the first step to having ESLint and TypeScript cooperate properly.
Fix 2: Enable JSX in parserOptions
If the error fires on a < character inside a .jsx or .tsx file, ESLint doesn’t know about JSX syntax.
For plain JavaScript with JSX (React without TypeScript):
{
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
}
}For TypeScript with JSX, set both the parser and jsx:
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"jsx": true
},
"plugins": ["@typescript-eslint"]
}If you use plugin:react/recommended, it typically enables JSX parsing automatically. But if you have a custom config without the React plugin’s recommended settings, you need to set ecmaFeatures.jsx manually.
JSX parsing errors are common when a component file triggers Too many re-renders during development and you try to lint it — the parsing error can mask the real runtime issue if your ESLint config is broken. Fix the ESLint config first so you can actually see the lint warnings that might explain the re-render loop.
Fix 3: Set ecmaVersion to Match Your JavaScript Features
ESLint defaults to a conservative ECMAScript version in legacy configs. If you use modern syntax — optional chaining, nullish coalescing, class fields, top-level await — you need to tell ESLint which version to parse.
{
"parserOptions": {
"ecmaVersion": 2022
}
}Common values and what they unlock:
ecmaVersion | Syntax Added |
|---|---|
2015 (6) | let, const, arrow functions, template literals, destructuring, classes, modules |
2017 (8) | async/await |
2020 (11) | Optional chaining (?.), nullish coalescing (??), BigInt, globalThis, import.meta |
2021 (12) | Logical assignment (&&=, ` |
2022 (13) | Top-level await, class fields, private methods, static initialization blocks |
2023 (14) | Hashbang comments (#!/usr/bin/env node) |
"latest" | Always the most recent supported version |
Setting ecmaVersion: "latest" is the safest choice for new projects. It tells Espree to support whatever the current version of ESLint supports.
If you’re on an older ecmaVersion and your code uses something like import() dynamic imports, you’ll get the unexpected token error. This is separate from a Module not found error you’d see at build time — that’s a bundler resolution issue, while this is purely a parser issue.
Fix 4: Set sourceType to "module"
If the error fires on an import or export statement:
Parsing error: Unexpected token importParsing error: The keyword 'import' is reservedESLint defaults to sourceType: "script", which doesn’t allow import/export. You need to switch to module mode:
{
"parserOptions": {
"sourceType": "module"
}
}For Node.js projects using CommonJS in some files and ES modules in others, you can use overrides:
{
"parserOptions": {
"sourceType": "script"
},
"overrides": [
{
"files": ["*.mjs", "src/**/*.js"],
"parserOptions": {
"sourceType": "module"
}
}
]
}Note that import.meta requires both sourceType: "module" and ecmaVersion: 2020 or later. Missing either one triggers the error. This catches people who set ecmaVersion: "latest" but forget sourceType: "module".
Fix 5: Use @babel/eslint-parser (Babel Projects)
If you use Babel for transpilation (common in projects not using TypeScript), Babel might support syntax that Espree doesn’t — decorators, pipeline operators, or other stage-3 proposals.
Install the Babel parser for ESLint:
npm install --save-dev @babel/eslint-parser @babel/coreConfigure it:
{
"parser": "@babel/eslint-parser",
"parserOptions": {
"requireConfigFile": false,
"babelOptions": {
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
}
}@babel/eslint-parser (formerly babel-eslint) delegates parsing to Babel, so any syntax your Babel config supports, ESLint will also understand. If you have a .babelrc or babel.config.js, you can omit babelOptions and set requireConfigFile: true (the default).
Note: don’t use both @babel/eslint-parser and @typescript-eslint/parser in the same config scope. Pick one based on your toolchain. TypeScript projects should use @typescript-eslint/parser. If you need both in a mixed project, use overrides to assign different parsers to different file extensions.
If your Babel setup is also causing build failures, you might be dealing with a Webpack Module parse failed: Unexpected token error, which has a similar root cause — the bundler’s loader chain doesn’t understand the syntax either.
Common Mistake: Upgrading to ESLint 9 without realizing it ignores your
.eslintrc.jsonentirely. ESLint 9 only readseslint.config.jsby default, so all your parser configuration silently vanishes and every TypeScript or JSX file throws parsing errors.
Fix 6: eslint.config.js (Flat Config) vs .eslintrc (Legacy Config)
ESLint 9+ uses the flat config format (eslint.config.js) by default, replacing .eslintrc.*. The configuration structure is completely different, and mixing up the two formats causes parsing errors.
Key Structural Differences
| Setting | Legacy (.eslintrc) | Flat (eslint.config.js) |
|---|---|---|
| Parser | "parser": "@typescript-eslint/parser" | languageOptions: { parser: importedParser } |
ecmaVersion | "parserOptions": { "ecmaVersion": "latest" } | languageOptions: { ecmaVersion: "latest" } |
sourceType | "parserOptions": { "sourceType": "module" } | languageOptions: { sourceType: "module" } |
| JSX | "parserOptions": { "ecmaFeatures": { "jsx": true } } | languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } |
| Globals | "env": { "browser": true } | languageOptions: { globals: { ...globals.browser } } |
Flat config format (eslint.config.js):
import typescriptParser from '@typescript-eslint/parser';
import typescriptPlugin from '@typescript-eslint/eslint-plugin';
export default [
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescriptPlugin,
},
rules: {
...typescriptPlugin.configs.recommended.rules,
},
},
];Critical differences from .eslintrc:
parsermoves insidelanguageOptions.parserand takes the imported module object, not a string name.parserOptionsmoves insidelanguageOptions.parserOptions.pluginsis an object mapping names to plugin modules, not an array of strings.extendsdoesn’t exist — you spread rule configs manually or use the plugin’s flat config helpers.
If you copy an .eslintrc config into eslint.config.js without restructuring it, ESLint won’t find the parser and will fall back to Espree, causing unexpected token errors on the first line of TypeScript or JSX.
Also watch for the config file format itself: eslint.config.js uses export default (ES module syntax). If your package.json doesn’t have "type": "module", rename the file to eslint.config.mjs. If your package.json does have "type": "module" and you need a CommonJS config, use eslint.config.cjs.
ESLint 9 Ignores .eslintrc by Default
ESLint 9 only reads eslint.config.js. If you upgraded ESLint to v9 but still have a .eslintrc.json or .eslintrc.js file, ESLint silently ignores it and uses Espree with default settings. All your parser configuration vanishes. Check your ESLint version:
npx eslint --versionIf you’re on v9+ and still need legacy config temporarily, set ESLINT_USE_FLAT_CONFIG=false as an environment variable.
Fix 7: TypeScript + React Project Setup
A complete configuration for a project using TypeScript and React, covering both .ts and .tsx files:
Legacy config (.eslintrc.json):
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
},
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"settings": {
"react": {
"version": "detect"
}
}
}Flat config (eslint.config.js):
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import globals from 'globals';
export default [
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx,js,jsx}'],
plugins: {
react,
'react-hooks': reactHooks,
},
languageOptions: {
globals: {
...globals.browser,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
settings: {
react: {
version: 'detect',
},
},
rules: {
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
},
},
];The project: "./tsconfig.json" option in legacy config enables type-aware linting rules. Without it, rules like @typescript-eslint/no-floating-promises won’t work. But adding it can slow down linting significantly on large codebases.
If you set project and then see Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser, make sure the files being linted are actually included in your tsconfig.json. Files outside include or inside exclude will trigger this secondary error. Common offenders are config files in the project root (like jest.config.ts, vite.config.ts) that aren’t covered by your main tsconfig.json. Either add them to include or create a tsconfig.eslint.json that extends your base config with broader includes:
{
"extends": "./tsconfig.json",
"include": ["src", "*.config.ts", "*.config.js"]
}Then point ESLint to this config:
{
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}Fix 8: Vue and Svelte File Parsing
Single-file components in Vue (.vue) and Svelte (.svelte) need dedicated parsers because they embed HTML, CSS, and JavaScript/TypeScript in one file.
Vue
Install the parser and plugin:
npm install --save-dev eslint-plugin-vueLegacy config:
{
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": "latest",
"sourceType": "module"
},
"extends": [
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended"
]
}Notice the double parser setup. The top-level parser is vue-eslint-parser, which handles the .vue file structure (template, script, style blocks). The parser inside parserOptions handles the JavaScript/TypeScript within <script> blocks. This is a very common source of confusion:
- If you set
@typescript-eslint/parseras the top-level parser, Vue template syntax will fail because the TypeScript parser can’t handle<template>blocks. - If you omit the inner parser, TypeScript in
<script lang="ts">blocks will fail because Espree can’t handle type annotations.
You must have both.
Flat config:
import vue from 'eslint-plugin-vue';
import tsParser from '@typescript-eslint/parser';
export default [
...vue.configs['flat/recommended'],
{
files: ['**/*.vue'],
languageOptions: {
parserOptions: {
parser: tsParser,
},
},
},
];Svelte
Install the parser and plugin:
npm install --save-dev eslint-plugin-svelte svelte-eslint-parserLegacy config:
{
"overrides": [
{
"files": ["*.svelte"],
"parser": "svelte-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
}
}
]
}Flat config:
import svelte from 'eslint-plugin-svelte';
export default [
...svelte.configs['flat/recommended'],
];The same nested parser pattern applies as with Vue. The framework-specific parser handles the component structure while delegating script parsing to @typescript-eslint/parser or Espree.
Fix 9: ESLint Plugin Conflicts and Version Mismatches
Sometimes the parser is configured correctly but a plugin conflict causes the parsing error. This is especially common in three scenarios:
Parser and Plugin Versions Out of Sync
@typescript-eslint/parser and @typescript-eslint/eslint-plugin must be on the same major version. Mixing v5 of the plugin with v6 of the parser (or vice versa) causes unpredictable failures, including parsing errors.
Check for version mismatches:
npm ls @typescript-eslint/parser @typescript-eslint/eslint-pluginCompatible combinations:
[email protected] + @typescript-eslint/*@5.x ---- compatible
[email protected] + @typescript-eslint/*@6.x ---- compatible
[email protected] + @typescript-eslint/*@7.x ---- compatible
[email protected] + @typescript-eslint/*@8.x ---- compatible
[email protected] + @typescript-eslint/*@5.x ---- NOT compatible
[email protected] + @typescript-eslint/*@6.x ---- NOT compatibleFix mismatches by installing matching versions:
npm install --save-dev @typescript-eslint/parser@latest @typescript-eslint/eslint-plugin@latestIf the error appeared after running npm install and you suddenly see build failures like npm ERR! code ELIFECYCLE, a dependency update likely pulled in an incompatible ESLint plugin version. Check your package-lock.json for duplicated ESLint-related packages.
Shared Configs Overriding Your Parser
A shared config like eslint-config-airbnb or eslint-config-standard might set its own parser, overriding yours. Debug the effective config with:
npx eslint --print-config src/App.tsxThis prints the fully merged configuration for a specific file, showing you exactly which parser and options are in effect. If the parser listed isn’t the one you configured, a shared config or override is responsible.
Multiple Versions of ESLint Installed
If a dependency installs its own version of ESLint, you can end up with two copies. Plugins loaded by one version may not be available to the other.
npm ls eslintIf you see multiple versions, deduplicate:
npm dedupeOr add an overrides field in package.json to force a single version:
{
"overrides": {
"eslint": "^9.0.0"
}
}Fix 10: Use .eslintignore for Generated Files
Sometimes the parsing error comes from a file you never wrote — generated code, build output, or vendored third-party files. These files often contain syntax that doesn’t match your project’s ESLint configuration.
Common culprits:
dist/,build/,.next/,.nuxt/,out/directories- Auto-generated GraphQL types
- Service worker files generated by Workbox
- Files generated by code generation tools (Prisma client, protobuf, OpenAPI generators)
- CSS-in-JS generated files
- Coverage reports in
coverage/
Create an .eslintignore file (or add to your existing one):
dist/
build/
.next/
.nuxt/
coverage/
node_modules/
*.generated.ts
*.generated.js
src/generated/Or set ignorePatterns in .eslintrc.json:
{
"ignorePatterns": [
"dist/",
"build/",
".next/",
"coverage/",
"*.generated.*",
"src/generated/"
]
}In flat config (eslint.config.js), use the ignores property as a standalone config object:
export default [
{
ignores: [
'dist/**',
'build/**',
'.next/**',
'coverage/**',
'*.generated.*',
'src/generated/**',
],
},
// ... rest of your config
];In flat config, the ignores must be in its own object (without other keys like rules or files) to act as a global ignore. If you put ignores inside a config object that also has rules, it only applies to that specific config block — not globally.
If you’re running ESLint on a project that also has module resolution issues at build time, the generated files that ESLint chokes on are often the same ones your bundler can’t find. Fixing the build pipeline usually fixes the lint errors on those files too.
How other tools handle this
ESLint’s parser-pluggability is both its biggest strength and the reason “Parsing error: Unexpected token” exists at all. The newer generation of JavaScript/TypeScript linters takes a different approach — one parser, no swapping, no config drift. Knowing what they do differently helps when you’re tired of debugging parserOptions.
Biome (formerly Rome) is a Rust-based formatter and linter that ships with a single bundled parser supporting JavaScript, TypeScript, JSX, and TSX out of the box. There is no parser field, no parserOptions, no ecmaVersion to set. You drop in a biome.json with { "linter": { "enabled": true } } and it lints every supported file. The trade-off: Biome doesn’t support Vue or Svelte single-file components, and it covers a smaller set of rules than ESLint’s plugin ecosystem (no eslint-plugin-import, no eslint-plugin-jsx-a11y equivalents at full parity). For a typical TypeScript+React project, Biome eliminates this entire class of error.
oxlint is another Rust-based linter (part of the Oxc toolchain). It also bundles a single parser and runs 50-100x faster than ESLint on large codebases. Like Biome, it has no parser configuration step — TypeScript and JSX work natively. oxlint is positioned as a fast first-pass linter that you run alongside ESLint, catching the obvious issues quickly while ESLint handles the framework-specific rules oxlint doesn’t have yet.
deno_lint powers Deno’s deno lint command. It uses the swc parser, which means the same parser handles JS, TS, JSX, and TSX without configuration. Deno’s philosophy is “no config files” for linting, so the parser layer is invisible — the same code that throws “Parsing error: Unexpected token” in ESLint just lints silently in deno lint. The catch: deno lint only supports Deno’s rule set, which is smaller than @typescript-eslint’s.
Rome was the predecessor to Biome and is now deprecated. If you find tutorials referencing rome.json, treat them as historical — use Biome instead.
TypeScript’s own compiler (tsc --noEmit) isn’t a linter, but it catches a superset of what ESLint’s parser would catch. If you’re already running tsc in CI as a type-check pass, half of the unexpected-token errors that ESLint catches are also caught there with better error messages. Many teams use tsc for type/syntax correctness and ESLint for stylistic and idiomatic rules, which sidesteps most parser-config issues.
Prettier uses Babel under the hood and is generally more permissive than ESLint’s default parser. If Prettier formats the file without complaining but ESLint throws an unexpected-token error, the problem is almost always ESLint’s parser config, not the syntax.
The takeaway: ESLint’s plugin-based parser model dates from a time when JavaScript dialects (CoffeeScript, Flow, early TypeScript) were proliferating and a swappable parser was a feature. In 2026, with TypeScript dominant and JSX universal, single-parser tools like Biome and oxlint are catching on precisely because they remove this configuration surface entirely. If you’re starting a new project and don’t need ESLint’s full plugin ecosystem, Biome’s zero-config experience saves real time.
Still Not Working?
The Error Points to a Perfectly Valid Line
ESLint parsing errors don’t always point to the actual problem. A missing closing brace, parenthesis, or bracket earlier in the file can cause the parser to lose track and report the error much later. If the line ESLint points to looks correct, scroll up and look for:
- Unclosed template literals (backtick strings spanning multiple lines)
- Missing closing braces on objects, functions, or classes
- Unclosed JSX tags or mismatched JSX element names
- A stray
<or>from a copy-paste error - An extra comma after the last property in a JSON-like object (trailing commas in certain contexts)
Run Prettier or your editor’s auto-formatter on the file first. If it also fails, the syntax error is real and not an ESLint config problem.
The Error Only Happens in CI
Your local machine might use a different ESLint version, Node.js version, or have a global ESLint installation that overrides the local one. Always run ESLint through a local npx eslint or a package.json script, never a global install.
Check Node.js version requirements — @typescript-eslint/parser v7+ requires Node.js 18 or later. Running on Node 16 in CI while using Node 20 locally will cause failures that you never see on your machine.
Also check that CI runs npm ci (not npm install) to ensure dependencies match the lockfile exactly. A different resolved version of @typescript-eslint/parser in CI vs. locally can produce different parsing behavior.
--ext Flag Missing (ESLint 8 and Below)
By default, ESLint 8 and below only lints .js files. If you run npx eslint src/, it will skip .ts, .tsx, .jsx, and .vue files entirely — or worse, try to parse them with the wrong settings if a glob pattern pulls them in.
Pass the extensions explicitly:
npx eslint --ext .js,.jsx,.ts,.tsx src/In ESLint 9 with flat config, the files property in each config object controls which files match, so --ext is no longer needed.
Multiple tsconfig.json Files (Monorepos)
In a monorepo with multiple tsconfig.json files, the project option in parserOptions needs to point to the right one for each package. Use an array or a glob:
{
"parserOptions": {
"project": ["./tsconfig.json", "./packages/*/tsconfig.json"]
}
}Or use tsconfigRootDir to set the base directory:
module.exports = {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
};Without tsconfigRootDir, ESLint resolves the project path relative to the working directory, which may differ from the config file’s directory in monorepo setups.
ESLint Cache Is Stale
ESLint caches results to speed up repeat runs. If you changed your config but ESLint still reports old errors, clear the cache:
rm -f .eslintcache
rm -rf node_modules/.cache/eslintThen re-run ESLint. If you’re using a CI system that caches node_modules, make sure the ESLint cache isn’t persisted between runs with different config files.
VS Code ESLint Extension Not Picking Up Changes
The VS Code ESLint extension caches configuration. After changing your ESLint config, restart the ESLint server:
- Open the command palette (
Ctrl+Shift+P/Cmd+Shift+P). - Run “ESLint: Restart ESLint Server”.
If that doesn’t work, run “Developer: Reload Window” to fully reset the extension.
Check the ESLint output channel for detailed errors: go to View > Output and select ESLint from the dropdown. Parser loading failures and config resolution issues show up here and are often more descriptive than the inline error message in the editor.
Verify Your Config Is Actually Being Loaded
Run ESLint with the --debug flag to see exactly which config file it reads and which parser it uses:
npx eslint --debug src/App.tsx 2>&1 | head -30Look for lines like:
eslint:eslint Using config file: /path/to/eslint.config.js
eslint:linter Parsing: /path/to/src/App.tsx with parser espreeIf it says parser espree when you expected @typescript-eslint/parser, your parser config isn’t being applied to that file. Check the files patterns in your config to ensure they match the file being linted.
eslint-plugin-import Resolving the Wrong File
eslint-plugin-import does its own resolution against the filesystem, separate from ESLint’s parser. If it imports a TypeScript file but the resolver is configured for Node CommonJS, it can pull in a .d.ts declaration file or a stale built .js artifact and try to parse that. You’ll see “Parsing error: Unexpected token” pointing at a file you didn’t even write. Fix it by installing eslint-import-resolver-typescript and adding:
{
"settings": {
"import/resolver": {
"typescript": {
"project": "./tsconfig.json"
}
}
}
}This forces the resolver to use the same path mapping and extensions as tsc, so it stops parsing stale build output.
File Encoding or BOM Issues
A file saved as UTF-16 or with a leading byte-order mark (BOM) can confuse Espree on the very first character. The error usually points at line 1, column 1, with no obvious cause. Re-save the file as UTF-8 without BOM. On Windows, this happens when a file is created by PowerShell’s Out-File (which defaults to UTF-16 LE) or when copy-pasting from certain editors. Run file path/to/file.ts on Linux/macOS or check the encoding indicator in your editor’s status bar.
Shebang on Non-Script Files
If a file starts with #!/usr/bin/env node, ESLint needs ecmaVersion: 2023 or later to parse it. On older configurations, you’ll see “Parsing error: Unexpected token #” on line 1. Bump ecmaVersion or remove the shebang from files that aren’t meant to be executed directly.
Editor Auto-Save Re-triggering Parser
Some editor setups run ESLint on every keystroke. If you’re mid-edit — typing an import statement that isn’t complete yet — ESLint legitimately can’t parse the partial file and shows a transient unexpected-token error. This is not a config bug. Either let the file settle before judging the error, or configure your editor to lint on save instead of on change.
Related: Parser configuration is one corner of a JavaScript toolchain that also includes the compiler, bundler, and runtime. When parser errors disappear, type errors and bundler errors tend to surface — treat them as the next layer to fix, not as new problems caused by the lint fix.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: ESLint Flat Config Not Working — eslint.config.js, ignores, Plugins, and Migration
How to fix ESLint flat config errors — eslint.config.js not found, .eslintrc.json ignored after upgrade, ignores replacing .eslintignore, plugin object form, typescript-eslint integration, monorepo configs, and ESLINT_USE_FLAT_CONFIG.
Fix: Oxlint Not Working — .oxlintrc.json Config, Rule Mapping, TypeScript, and ESLint Coexistence
How to fix Oxlint errors — .oxlintrc.json not loaded, rules not matching ESLint output, TypeScript files not linted, plugin-react/typescript wiring, IDE extension setup, and running alongside ESLint.
Fix: ESLint Config Not Working — Rules Ignored, Flat Config Errors, or Plugin Not Found
How to fix ESLint configuration issues — flat config vs legacy config, extends conflicts, parser options, plugin resolution, per-directory overrides, and migrating to ESLint 9.
Fix: ESLint no-unused-vars False Positives and Configuration
How to fix ESLint no-unused-vars false positives — TypeScript types, destructuring ignores, React imports, function arguments, and configuring the rule to match your codebase patterns.