Skip to content

Commit 10e28ba

Browse files
authored
Add support for parser object to parserOptions.parser (#196)
* Add support for parser object to `parserOptions.parser` * update
1 parent 88ae20c commit 10e28ba

14 files changed

+169
-49
lines changed

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,28 @@ If you want to switch the parser for each lang, specify the object.
162162
}
163163
```
164164

165+
#### Parser Object
166+
167+
When using JavaScript configuration (`.eslintrc.js`), you can also give the parser object directly.
168+
169+
```js
170+
const tsParser = require("@typescript-eslint/parser")
171+
const espree = require("espree")
172+
173+
module.exports = {
174+
parser: "svelte-eslint-parser",
175+
parserOptions: {
176+
// Single parser
177+
parser: tsParser,
178+
// Multiple parser
179+
parser: {
180+
js: espree,
181+
ts: tsParser,
182+
}
183+
},
184+
}
185+
```
186+
165187
## :computer: Editor Integrations
166188

167189
### Visual Studio Code

explorer-v2/build-system/pre-build/webpack.config.js

-13
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,6 @@ export default [
7474
'svelte/compiler': '$$inject_svelte_compiler$$',
7575
espree: '$$inject_espree$$'
7676
},
77-
module: {
78-
rules: [
79-
{
80-
test: /\/resolve-parser\.js$/u,
81-
loader: 'string-replace-loader',
82-
options: {
83-
search: 'require\\(name\\)',
84-
replace: `__non_webpack_require__(name)`,
85-
flags: ''
86-
}
87-
}
88-
]
89-
},
9077
plugins: [
9178
new WrapperPlugin({
9279
test: /svelte-eslint-parser\.js/,

explorer-v2/src/lib/VirtualScriptCode.svelte

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import MonacoEditor from './MonacoEditor.svelte';
55
import * as svelteEslintParser from 'svelte-eslint-parser';
66
7+
let tsParser = undefined;
78
let loaded = false;
89
import('@typescript-eslint/parser')
910
.then((parser) => {
@@ -14,8 +15,8 @@
1415
env: {}
1516
};
1617
}
17-
window.require.define('@typescript-eslint/parser', parser);
1818
}
19+
tsParser = parser;
1920
})
2021
.then(() => {
2122
loaded = true;
@@ -49,7 +50,7 @@
4950
const start = Date.now();
5051
try {
5152
virtualScriptCode = svelteEslintParser.parseForESLint(svelteValue, {
52-
parser: '@typescript-eslint/parser'
53+
parser: tsParser
5354
})._virtualScriptCode;
5455
} catch (e) {
5556
// eslint-disable-next-line no-console -- Demo

src/context/index.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import type {
1111
import type ESTree from "estree";
1212
import { ScriptLetContext } from "./script-let";
1313
import { LetDirectiveCollections } from "./let-directive-collection";
14-
import { getParserName } from "../parser/resolve-parser";
14+
import { getParserForLang } from "../parser/resolve-parser";
1515
import type { AttributeToken } from "../parser/html";
1616
import { parseAttributes } from "../parser/html";
17+
import { maybeTSESLintParserObject } from "../parser/parser-object";
1718

1819
export class ScriptsSourceCode {
1920
private raw: string;
@@ -202,13 +203,20 @@ export class Context {
202203
if (!lang) {
203204
return (this.state.isTypeScript = false);
204205
}
205-
const parserName = getParserName(
206+
const parserValue = getParserForLang(
206207
this.sourceCode.scripts.attrs,
207208
this.parserOptions?.parser
208209
);
209-
if (parserName === "@typescript-eslint/parser") {
210+
if (
211+
maybeTSESLintParserObject(parserValue) ||
212+
parserValue === "@typescript-eslint/parser"
213+
) {
210214
return (this.state.isTypeScript = true);
211215
}
216+
if (typeof parserValue !== "string") {
217+
return (this.state.isTypeScript = false);
218+
}
219+
const parserName = parserValue;
212220
if (parserName.includes("@typescript-eslint/parser")) {
213221
let targetPath = parserName;
214222
while (targetPath) {

src/parser/espree.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Module from "module";
22
import path from "path";
3-
import type { ESLintCustomParser } from "./resolve-parser";
3+
import type { BasicParserObject } from "./parser-object";
44

55
const createRequire: (filename: string) => (modName: string) => any =
66
// Added in v12.2.0
@@ -19,7 +19,7 @@ const createRequire: (filename: string) => (modName: string) => any =
1919
return mod.exports;
2020
});
2121

22-
let espreeCache: ESLintCustomParser | null = null;
22+
let espreeCache: BasicParserObject | null = null;
2323

2424
/** Checks if given path is linter path */
2525
function isLinterPath(p: string): boolean {
@@ -35,7 +35,7 @@ function isLinterPath(p: string): boolean {
3535
* Load `espree` from the loaded ESLint.
3636
* If the loaded ESLint was not found, just returns `require("espree")`.
3737
*/
38-
export function getEspree(): ESLintCustomParser {
38+
export function getEspree(): BasicParserObject {
3939
if (!espreeCache) {
4040
// Lookup the loaded eslint
4141
const linterPath = Object.keys(require.cache || {}).find(isLinterPath);

src/parser/parser-object.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { ESLintExtendedProgram, ESLintProgram } from ".";
2+
import type * as tsESLintParser from "@typescript-eslint/parser";
3+
type TSESLintParser = typeof tsESLintParser;
4+
/**
5+
* The type of basic ESLint custom parser.
6+
* e.g. espree
7+
*/
8+
export type BasicParserObject = {
9+
parse(code: string, options: any): ESLintProgram;
10+
parseForESLint: undefined;
11+
};
12+
/**
13+
* The type of ESLint custom parser enhanced for ESLint.
14+
* e.g. @babel/eslint-parser, @typescript-eslint/parser
15+
*/
16+
export type EnhancedParserObject = {
17+
parseForESLint(code: string, options: any): ESLintExtendedProgram;
18+
parse: undefined;
19+
};
20+
21+
/**
22+
* The type of ESLint (custom) parsers.
23+
*/
24+
export type ParserObject = EnhancedParserObject | BasicParserObject;
25+
26+
/** Checks whether given object is ParserObject */
27+
export function isParserObject(value: unknown): value is ParserObject {
28+
return isEnhancedParserObject(value) || isBasicParserObject(value);
29+
}
30+
/** Checks whether given object is EnhancedParserObject */
31+
export function isEnhancedParserObject(
32+
value: unknown
33+
): value is EnhancedParserObject {
34+
return Boolean(value && typeof (value as any).parseForESLint === "function");
35+
}
36+
/** Checks whether given object is BasicParserObject */
37+
export function isBasicParserObject(
38+
value: unknown
39+
): value is BasicParserObject {
40+
return Boolean(value && typeof (value as any).parse === "function");
41+
}
42+
43+
/** Checks whether given object is "@typescript-eslint/parser" */
44+
export function maybeTSESLintParserObject(
45+
value: unknown
46+
): value is TSESLintParser {
47+
return (
48+
isEnhancedParserObject(value) &&
49+
isBasicParserObject(value) &&
50+
typeof (value as any).createProgram === "function" &&
51+
typeof (value as any).clearCaches === "function" &&
52+
typeof (value as any).version === "string"
53+
);
54+
}

src/parser/resolve-parser.ts

+26-26
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
1-
import type { ESLintExtendedProgram, ESLintProgram } from ".";
21
import { getEspree } from "./espree";
3-
/**
4-
* The interface of a result of ESLint custom parser.
5-
*/
6-
export type ESLintCustomParserResult = ESLintProgram | ESLintExtendedProgram;
7-
/**
8-
* The interface of ESLint custom parsers.
9-
*/
10-
export interface ESLintCustomParser {
11-
parse(code: string, options: any): ESLintCustomParserResult;
12-
parseForESLint?(code: string, options: any): ESLintCustomParserResult;
13-
}
2+
import type { ParserObject } from "./parser-object";
3+
import { isParserObject } from "./parser-object";
4+
5+
type UserOptionParser =
6+
| string
7+
| ParserObject
8+
| Record<string, string | ParserObject | undefined>
9+
| undefined;
1410

15-
/** Get parser name */
16-
export function getParserName(
11+
/** Get parser for script lang */
12+
export function getParserForLang(
1713
attrs: Record<string, string | undefined>,
18-
parser: any
19-
): string {
14+
parser: UserOptionParser
15+
): string | ParserObject {
2016
if (parser) {
21-
if (typeof parser === "string" && parser !== "espree") {
17+
if (typeof parser === "string" || isParserObject(parser)) {
2218
return parser;
23-
} else if (typeof parser === "object") {
24-
const name = parser[attrs.lang || "js"];
25-
if (typeof name === "string") {
26-
return getParserName(attrs, name);
19+
}
20+
if (typeof parser === "object") {
21+
const value = parser[attrs.lang || "js"];
22+
if (typeof value === "string" || isParserObject(value)) {
23+
return value;
2724
}
2825
}
2926
}
@@ -33,12 +30,15 @@ export function getParserName(
3330
/** Get parser */
3431
export function getParser(
3532
attrs: Record<string, string | undefined>,
36-
parser: any
37-
): ESLintCustomParser {
38-
const name = getParserName(attrs, parser);
39-
if (name !== "espree") {
33+
parser: UserOptionParser
34+
): ParserObject {
35+
const parserValue = getParserForLang(attrs, parser);
36+
if (isParserObject(parserValue)) {
37+
return parserValue;
38+
}
39+
if (parserValue !== "espree") {
4040
// eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
41-
return require(name);
41+
return require(parserValue);
4242
}
4343
return getEspree();
4444
}

src/parser/script.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { analyzeScope } from "./analyze-scope";
33
import { traverseNodes } from "../traverse";
44
import type { ScriptsSourceCode } from "../context";
55
import { getParser } from "./resolve-parser";
6+
import { isEnhancedParserObject } from "./parser-object";
67

78
/**
89
* Parse for script
@@ -47,8 +48,9 @@ function parseScriptWithoutAnalyzeScope(
4748
): ESLintExtendedProgram {
4849
const parser = getParser(attrs, options.parser);
4950

50-
const result =
51-
parser.parseForESLint?.(vcode, options) ?? parser.parse?.(vcode, options);
51+
const result = isEnhancedParserObject(parser)
52+
? parser.parseForESLint(vcode, options)
53+
: parser.parse(vcode, options);
5254

5355
if ("ast" in result && result.ast != null) {
5456
result._virtualScriptCode = vcode;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
export let num: number;
3+
</script>
4+
5+
{num}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* eslint eslint-comments/require-description: 0, @typescript-eslint/explicit-module-boundary-types: 0 */
2+
import { BASIC_PARSER_OPTIONS } from "../../../src/parser/test-utils";
3+
import * as ts from "@typescript-eslint/parser";
4+
5+
export function getConfig() {
6+
return {
7+
parser: "svelte-eslint-parser",
8+
parserOptions: {
9+
...BASIC_PARSER_OPTIONS,
10+
parser: { ts },
11+
},
12+
env: {
13+
browser: true,
14+
es2021: true,
15+
},
16+
};
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
export let num: number;
3+
</script>
4+
5+
{num}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* eslint eslint-comments/require-description: 0, @typescript-eslint/explicit-module-boundary-types: 0 */
2+
import { BASIC_PARSER_OPTIONS } from "../../../src/parser/test-utils";
3+
import * as parser from "@typescript-eslint/parser";
4+
5+
export function getConfig() {
6+
return {
7+
parser: "svelte-eslint-parser",
8+
parserOptions: {
9+
...BASIC_PARSER_OPTIONS,
10+
parser,
11+
},
12+
env: {
13+
browser: true,
14+
es2021: true,
15+
},
16+
};
17+
}

0 commit comments

Comments
 (0)