Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
✨ use espree in eslint deps
  • Loading branch information
mysticatea committed May 14, 2020
1 parent 6234993 commit 27a275f
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 18 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/CI.yml
@@ -1,7 +1,7 @@
name: CI
on:
push:
branches: [master]
branches: [master, try]
pull_request:
branches: [master]
schedule:
Expand All @@ -21,7 +21,7 @@ jobs:
with:
node-version: 14
- name: Install Packages
run: npm install
run: npm install && cd test/fixtures/eslint && npm install
- name: Lint
run: npm run -s lint

Expand Down
69 changes: 69 additions & 0 deletions src/script/espree.ts
@@ -0,0 +1,69 @@
import Module from "module"
import path from "path"
import { ESLintExtendedProgram, ESLintProgram } from "../ast"

/**
* The interface of a result of ESLint custom parser.
*/
export type ESLintCustomParserResult = ESLintProgram | ESLintExtendedProgram

/**
* The interface of ESLint custom parsers.
*/
export interface ESLintCustomParser {
parse(code: string, options: any): ESLintCustomParserResult
parseForESLint?(code: string, options: any): ESLintCustomParserResult
}

const createRequire: (filename: string) => (filename: string) => any =
// Added in v12.2.0
(Module as any).createRequire ||
// Added in v10.12.0, but deprecated in v12.2.0.
Module.createRequireFromPath ||
// Polyfill - This is not executed on the tests on node@>=10.
/* istanbul ignore next */
(filename => {
const mod = new Module(filename)

mod.filename = filename
mod.paths = (Module as any)._nodeModulePaths(path.dirname(filename))
;(mod as any)._compile("module.exports = require;", filename)
return mod.exports
})

let espreeCache: ESLintCustomParser | null = null

function isLinterPath(p: string): boolean {
return (
// ESLint 6 and above
p.includes(
`eslint${path.sep}lib${path.sep}linter${path.sep}linter.js`,
) ||
// ESLint 5
p.includes(`eslint${path.sep}lib${path.sep}linter.js`)
)
}

/**
* Load `espree` from the loaded ESLint.
* If the loaded ESLint was not found, just returns `require("espree")`.
*/
export function getEspree(): ESLintCustomParser {
if (!espreeCache) {
// Lookup the loaded eslint
const linterPath = Object.keys(require.cache).find(isLinterPath)
if (linterPath) {
try {
espreeCache = createRequire(linterPath)("espree")
} catch {
// ignore
}
}
if (!espreeCache) {
//eslint-disable-next-line @mysticatea/ts/no-require-imports
espreeCache = require("espree")
}
}

return espreeCache!
}
18 changes: 2 additions & 16 deletions src/script/index.ts
Expand Up @@ -17,7 +17,6 @@ import {
ESLintForOfStatement,
ESLintFunctionExpression,
ESLintPattern,
ESLintProgram,
ESLintVariableDeclaration,
ESLintUnaryExpression,
HasLocation,
Expand All @@ -39,6 +38,7 @@ import {
analyzeExternalReferences,
analyzeVariablesAndExternalReferences,
} from "./scope-analyzer"
import { ESLintCustomParser, getEspree } from "./espree"

// [1] = spacing before the aliases.
// [2] = aliases.
Expand All @@ -51,14 +51,6 @@ const DUMMY_PARENT: any = {}
const IS_FUNCTION_EXPRESSION = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/u
const IS_SIMPLE_PATH = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?'\]|\["[^"]*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/u

/**
* The interface of ESLint custom parsers.
*/
interface ESLintCustomParser {
parse(code: string, options: any): ESLintCustomParserResult
parseForESLint?(code: string, options: any): ESLintCustomParserResult
}

/**
* Do post-process of parsing an expression.
*
Expand Down Expand Up @@ -548,11 +540,6 @@ export interface ExpressionParseResult<T extends Node> {
variables: Variable[]
}

/**
* The interface of a result of ESLint custom parser.
*/
export type ESLintCustomParserResult = ESLintProgram | ESLintExtendedProgram

/**
* Parse the given source code.
*
Expand All @@ -568,8 +555,7 @@ export function parseScript(
typeof parserOptions.parser === "string"
? // eslint-disable-next-line @mysticatea/ts/no-require-imports
require(parserOptions.parser)
: // eslint-disable-next-line @mysticatea/ts/no-require-imports
require("espree")
: getEspree()
const result: any =
// eslint-disable-next-line @mysticatea/ts/unbound-method
typeof parser.parseForESLint === "function"
Expand Down
60 changes: 60 additions & 0 deletions test/espree.js
@@ -0,0 +1,60 @@
"use strict"

const path = require("path")

/**
* Spawn a child process to run `childMain()`.
*/
function parentMain() {
const { spawn } = require("child_process")

describe("Loading espree from ESLint", () => {
it("should load espree from the ESLint location.", done => {
spawn(process.execPath, [__filename, "--child"], {
stdio: "inherit",
})
.on("error", done)
.on("exit", code =>
code
? done(new Error(`Exited with non-zero: ${code}`))
: done()
)
})
})
}

/**
* Check this parser loads the `espree` from the location of the loaded ESLint.
*/
function childMain() {
const assert = require("assert")
const { Linter } = require("./fixtures/eslint")
const linter = new Linter()
linter.defineParser("vue-eslint-parser", require("../src"))

const beforeEsprees = Object.keys(require.cache).filter(isEspreePath)

linter.verify(
"<script>'hello'</script>",
{ parser: "vue-eslint-parser" },
{ filename: "a.vue" }
)

const afterEsprees = Object.keys(require.cache).filter(isEspreePath)

assert.strictEqual(
afterEsprees.length,
beforeEsprees.length,
"espree should be loaded from the expected place"
)
}

function isEspreePath(p) {
return p.includes(`${path.sep}node_modules${path.sep}espree${path.sep}`)
}

if (process.argv.includes("--child")) {
childMain()
} else {
parentMain()
}

0 comments on commit 27a275f

Please sign in to comment.