Skip to content

Commit ee011d2

Browse files
committed
fix: unintended version of espree loaded
1 parent b0e0ccc commit ee011d2

File tree

6 files changed

+130
-170
lines changed

6 files changed

+130
-170
lines changed

src/common/espree.ts

+76-59
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ParserOptions } from "../common/parser-options"
22
import { getLinterRequire } from "./linter-require"
33
// @ts-expect-error -- ignore
44
import * as dependencyEspree from "espree"
5-
import { lte, lt } from "semver"
5+
import { lte, satisfies } from "semver"
66
import { createRequire } from "./create-require"
77
import path from "path"
88
import type { BasicParserObject } from "./parser-object"
@@ -11,73 +11,100 @@ type Espree = BasicParserObject & {
1111
latestEcmaVersion?: number
1212
version: string
1313
}
14-
let espreeCache: Espree | null = null
15-
16-
/**
17-
* Gets the espree that the given ecmaVersion can parse.
18-
*/
19-
export function getEspreeFromEcmaVersion(
20-
ecmaVersion: ParserOptions["ecmaVersion"],
21-
): Espree {
22-
const linterEspree = getEspreeFromLinter()
23-
if (ecmaVersion == null) {
24-
return linterEspree
25-
}
26-
if (ecmaVersion === "latest") {
27-
return getNewestEspree()
28-
}
29-
if (
30-
normalizeEcmaVersion(ecmaVersion) <= getLatestEcmaVersion(linterEspree)
31-
) {
32-
return linterEspree
33-
}
34-
const userEspree = getEspreeFromUser()
35-
if (normalizeEcmaVersion(ecmaVersion) <= getLatestEcmaVersion(userEspree)) {
36-
return userEspree
37-
}
38-
return linterEspree
39-
}
4014

4115
/**
4216
* Load `espree` from the user dir.
4317
*/
44-
export function getEspreeFromUser(): Espree {
18+
function getEspreeFromUser(): Espree {
4519
try {
4620
const cwd = process.cwd()
4721
const relativeTo = path.join(cwd, "__placeholder__.js")
48-
return createRequire(relativeTo)("espree")
22+
const require = createRequire(relativeTo)
23+
const espree = getEspreeFromRequireFunction(require)
24+
if (espree) {
25+
if (espree !== dependencyEspree) {
26+
return espree
27+
}
28+
// If the user's espree is the same as the parser package's dependency espree,
29+
// it checks whether the user has explicitly installed it.
30+
if (isExplicitlyInstalledEspree(require as NodeRequire)) {
31+
return espree
32+
}
33+
}
4934
} catch {
50-
return getEspreeFromLinter()
35+
// ignore
36+
}
37+
return getEspreeFromLinter()
38+
39+
function isExplicitlyInstalledEspree(require: NodeRequire): boolean {
40+
try {
41+
const espreeRootPath = path.dirname(
42+
require.resolve("espree/package.json"),
43+
)
44+
const nodeModulesPath = path.dirname(espreeRootPath)
45+
const packageRootPath = path.dirname(nodeModulesPath)
46+
let pkg
47+
try {
48+
pkg = require(path.join(packageRootPath, "package.json"))
49+
} catch {
50+
// ignore
51+
}
52+
if (pkg) {
53+
return Boolean(
54+
pkg.dependencies?.espree || pkg.devDependencies?.espree,
55+
)
56+
}
57+
} catch {
58+
// ignore
59+
}
60+
// If no package.json is found,
61+
// it is assumed to have been explicitly installed by the user.
62+
return true
5163
}
5264
}
5365

5466
/**
5567
* Load `espree` from the loaded ESLint.
5668
* If the loaded ESLint was not found, just returns `require("espree")`.
5769
*/
58-
export function getEspreeFromLinter(): Espree {
59-
if (!espreeCache) {
60-
espreeCache = getLinterRequire()?.("espree")
61-
if (!espreeCache) {
62-
espreeCache = dependencyEspree
70+
function getEspreeFromLinter(): Espree {
71+
const require = getLinterRequire()
72+
if (require) {
73+
const espree = getEspreeFromRequireFunction(require)
74+
if (espree) {
75+
return espree
6376
}
6477
}
78+
return dependencyEspree
79+
}
6580

66-
return espreeCache!
81+
/**
82+
* Load `espree` from the given require function.
83+
*/
84+
function getEspreeFromRequireFunction(
85+
require: (name: string) => any,
86+
): Espree | null {
87+
try {
88+
const pkg = require("espree/package.json")
89+
const supportNodeVersion = pkg.engines?.node
90+
if (
91+
// If the node version is not supported then espree will not use it.
92+
!supportNodeVersion ||
93+
satisfies(process.version, supportNodeVersion)
94+
) {
95+
return require("espree")
96+
}
97+
} catch {
98+
// ignore
99+
}
100+
return null
67101
}
68102

69103
/**
70104
* Load the newest `espree` from the loaded ESLint or dependency.
71105
*/
72-
function getNewestEspree(): Espree {
73-
let newest = dependencyEspree
74-
const linterEspree = getEspreeFromLinter()
75-
if (
76-
linterEspree.version != null &&
77-
lte(newest.version, linterEspree.version)
78-
) {
79-
newest = linterEspree
80-
}
106+
export function getNewestEspree(): Espree {
107+
let newest = getEspreeFromLinter()
81108
const userEspree = getEspreeFromUser()
82109
if (userEspree.version != null && lte(newest.version, userEspree.version)) {
83110
newest = userEspree
@@ -87,30 +114,20 @@ function getNewestEspree(): Espree {
87114

88115
export function getEcmaVersionIfUseEspree(
89116
parserOptions: ParserOptions,
90-
getDefault?: (defaultVer: number) => number,
91117
): number | undefined {
92118
if (parserOptions.parser != null && parserOptions.parser !== "espree") {
93119
return undefined
94120
}
95121

96-
if (parserOptions.ecmaVersion === "latest") {
122+
if (
123+
parserOptions.ecmaVersion === "latest" ||
124+
parserOptions.ecmaVersion == null
125+
) {
97126
return normalizeEcmaVersion(getLatestEcmaVersion(getNewestEspree()))
98127
}
99-
if (parserOptions.ecmaVersion == null) {
100-
const defVer = getDefaultEcmaVersion()
101-
return getDefault?.(defVer) ?? defVer
102-
}
103128
return normalizeEcmaVersion(parserOptions.ecmaVersion)
104129
}
105130

106-
function getDefaultEcmaVersion(): number {
107-
if (lt(getEspreeFromLinter().version, "9.0.0")) {
108-
return 5
109-
}
110-
// Perhaps the version 9 will change the default to "latest".
111-
return normalizeEcmaVersion(getLatestEcmaVersion(getNewestEspree()))
112-
}
113-
114131
/**
115132
* Normalize ECMAScript version
116133
*/

src/script-setup/parser-options.ts

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { lte } from "semver"
2-
import { getEcmaVersionIfUseEspree, getEspreeFromUser } from "../common/espree"
1+
import { getEcmaVersionIfUseEspree } from "../common/espree"
32
import type { ParserOptions } from "../common/parser-options"
43

54
export const DEFAULT_ECMA_VERSION = 2017
@@ -10,21 +9,11 @@ export const DEFAULT_ECMA_VERSION = 2017
109
export function getScriptSetupParserOptions(
1110
parserOptions: ParserOptions,
1211
): ParserOptions {
13-
const espreeEcmaVersion = getEcmaVersionIfUseEspree(
14-
parserOptions,
15-
getDefaultEcmaVersion,
16-
)
12+
const espreeEcmaVersion =
13+
getEcmaVersionIfUseEspree(parserOptions) ?? parserOptions.ecmaVersion
1714

1815
return {
1916
...parserOptions,
2017
ecmaVersion: espreeEcmaVersion,
2118
}
2219
}
23-
24-
function getDefaultEcmaVersion(def: number) {
25-
if (lte("8.0.0", getEspreeFromUser().version)) {
26-
// Script setup requires top level await support, so default the ecma version to 2022.
27-
return getEspreeFromUser().latestEcmaVersion!
28-
}
29-
return Math.max(def, DEFAULT_ECMA_VERSION)
30-
}

src/script/index.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,7 @@ import {
4343
analyzeExternalReferences,
4444
analyzeVariablesAndExternalReferences,
4545
} from "./scope-analyzer"
46-
import {
47-
getEcmaVersionIfUseEspree,
48-
getEspreeFromUser,
49-
getEspreeFromEcmaVersion,
50-
} from "../common/espree"
46+
import { getEcmaVersionIfUseEspree, getNewestEspree } from "../common/espree"
5147
import type { ParserOptions } from "../common/parser-options"
5248
import {
5349
fixErrorLocation,
@@ -571,7 +567,7 @@ function loadParser(parser: string) {
571567
// eslint-disable-next-line @typescript-eslint/no-require-imports
572568
return require(parser)
573569
}
574-
return getEspreeFromUser()
570+
return getNewestEspree()
575571
}
576572

577573
/**
@@ -590,7 +586,7 @@ export function parseScript(
590586
? loadParser(parserOptions.parser)
591587
: isParserObject(parserOptions.parser)
592588
? parserOptions.parser
593-
: getEspreeFromEcmaVersion(parserOptions.ecmaVersion)
589+
: getNewestEspree()
594590

595591
const result: any = isEnhancedParserObject(parser)
596592
? parser.parseForESLint(code, parserOptions)

0 commit comments

Comments
 (0)