Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9daf014

Browse filesBrowse files
authored
Add support for parser object to parserOptions.parser (#165)
* Add support for parser object to `parserOptions.parser` * add comments * update
1 parent b70caf5 commit 9daf014
Copy full SHA for 9daf014

File tree

Expand file treeCollapse file tree

9 files changed

+216
-46
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+216
-46
lines changed

‎README.md

Copy file name to clipboardExpand all lines: README.md
+20Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,26 @@ You can also specify an object and change the parser separately for `<script lan
106106
}
107107
```
108108

109+
When using JavaScript configuration (`.eslintrc.js`), you can also give the parser object directly.
110+
111+
```js
112+
const tsParser = require("@typescript-eslint/parser")
113+
const espree = require("espree")
114+
115+
module.exports = {
116+
parser: "vue-eslint-parser",
117+
parserOptions: {
118+
// Single parser
119+
parser: tsParser,
120+
// Multiple parser
121+
parser: {
122+
js: espree,
123+
ts: tsParser,
124+
}
125+
},
126+
}
127+
```
128+
109129
If the `parserOptions.parser` is `false`, the `vue-eslint-parser` skips parsing `<script>` tags completely.
110130
This is useful for people who use the language ESLint community doesn't provide custom parser implementation.
111131

‎src/common/espree.ts

Copy file name to clipboardExpand all lines: src/common/espree.ts
+2-14Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,13 @@
1-
import type { ESLintExtendedProgram, ESLintProgram } from "../ast"
21
import type { ParserOptions } from "../common/parser-options"
32
import { getLinterRequire } from "./linter-require"
43
// @ts-expect-error -- ignore
54
import * as dependencyEspree from "espree"
65
import { lte, lt } from "semver"
76
import { createRequire } from "./create-require"
87
import path from "path"
8+
import type { BasicParserObject } from "./parser-object"
99

10-
/**
11-
* The interface of a result of ESLint custom parser.
12-
*/
13-
export type ESLintCustomParserResult = ESLintProgram | ESLintExtendedProgram
14-
15-
/**
16-
* The interface of ESLint custom parsers.
17-
*/
18-
export interface ESLintCustomParser {
19-
parse(code: string, options: any): ESLintCustomParserResult
20-
parseForESLint?(code: string, options: any): ESLintCustomParserResult
21-
}
22-
type Espree = ESLintCustomParser & {
10+
type Espree = BasicParserObject & {
2311
latestEcmaVersion?: number
2412
version: string
2513
}

‎src/common/parser-object.ts

Copy file name to clipboard
+41Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { ESLintExtendedProgram, ESLintProgram } from "../ast"
2+
3+
/**
4+
* The type of basic ESLint custom parser.
5+
* e.g. espree
6+
*/
7+
export type BasicParserObject<R = ESLintProgram> = {
8+
parse(code: string, options: any): R
9+
parseForESLint: undefined
10+
}
11+
/**
12+
* The type of ESLint custom parser enhanced for ESLint.
13+
* e.g. @babel/eslint-parser, @typescript-eslint/parser
14+
*/
15+
export type EnhancedParserObject<R = ESLintExtendedProgram> = {
16+
parseForESLint(code: string, options: any): R
17+
parse: undefined
18+
}
19+
20+
/**
21+
* The type of ESLint (custom) parsers.
22+
*/
23+
export type ParserObject<R1 = ESLintExtendedProgram, R2 = ESLintProgram> =
24+
| EnhancedParserObject<R1>
25+
| BasicParserObject<R2>
26+
27+
export function isParserObject<R1, R2>(
28+
value: ParserObject<R1, R2> | {} | undefined | null,
29+
): value is ParserObject<R1, R2> {
30+
return isEnhancedParserObject(value) || isBasicParserObject(value)
31+
}
32+
export function isEnhancedParserObject<R>(
33+
value: EnhancedParserObject<R> | {} | undefined | null,
34+
): value is EnhancedParserObject<R> {
35+
return Boolean(value && typeof (value as any).parseForESLint === "function")
36+
}
37+
export function isBasicParserObject<R>(
38+
value: BasicParserObject<R> | {} | undefined | null,
39+
): value is BasicParserObject<R> {
40+
return Boolean(value && typeof (value as any).parse === "function")
41+
}

‎src/common/parser-options.ts

Copy file name to clipboardExpand all lines: src/common/parser-options.ts
+26-5Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import * as path from "path"
22
import type { VDocumentFragment } from "../ast"
3+
import type { CustomTemplateTokenizerConstructor } from "../html/custom-tokenizer"
34
import { getLang, isScriptElement, isScriptSetupElement } from "./ast-utils"
5+
import type { ParserObject } from "./parser-object"
6+
import { isParserObject } from "./parser-object"
47

58
export interface ParserOptions {
69
// vue-eslint-parser options
7-
parser?: boolean | string
10+
parser?:
11+
| boolean
12+
| string
13+
| ParserObject
14+
| Record<string, string | ParserObject | undefined>
815
vueFeatures?: {
916
interpolationAsNonHTML?: boolean // default true
1017
filter?: boolean // default true
@@ -41,7 +48,10 @@ export interface ParserOptions {
4148
// others
4249
// [key: string]: any
4350

44-
templateTokenizer?: { [key: string]: string }
51+
templateTokenizer?: Record<
52+
string,
53+
string | CustomTemplateTokenizerConstructor | undefined
54+
>
4555
}
4656

4757
export function isSFCFile(parserOptions: ParserOptions) {
@@ -55,9 +65,17 @@ export function isSFCFile(parserOptions: ParserOptions) {
5565
* Gets the script parser name from the given parser lang.
5666
*/
5767
export function getScriptParser(
58-
parser: boolean | string | Record<string, string | undefined> | undefined,
68+
parser:
69+
| boolean
70+
| string
71+
| ParserObject
72+
| Record<string, string | ParserObject | undefined>
73+
| undefined,
5974
getParserLang: () => string | null | Iterable<string | null>,
60-
): string | undefined {
75+
): string | ParserObject | undefined {
76+
if (isParserObject(parser)) {
77+
return parser
78+
}
6179
if (parser && typeof parser === "object") {
6280
const parserLang = getParserLang()
6381
const parserLangs =
@@ -68,7 +86,10 @@ export function getScriptParser(
6886
: parserLang
6987
for (const lang of parserLangs) {
7088
const parserForLang = lang && parser[lang]
71-
if (typeof parserForLang === "string") {
89+
if (
90+
typeof parserForLang === "string" ||
91+
isParserObject(parserForLang)
92+
) {
7293
return parserForLang
7394
}
7495
}

‎src/html/custom-tokenizer.ts

Copy file name to clipboard
+51Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Namespace, ParseError, Token } from "../ast"
2+
import type { IntermediateToken } from "./intermediate-tokenizer"
3+
import type { TokenizerState } from "./tokenizer"
4+
5+
export interface CustomTemplateTokenizer {
6+
/**
7+
* The tokenized low level tokens, excluding comments.
8+
*/
9+
readonly tokens: Token[]
10+
/**
11+
* The tokenized low level comment tokens
12+
*/
13+
readonly comments: Token[]
14+
/**
15+
* The source code text.
16+
*/
17+
readonly text: string
18+
/**
19+
* The parse errors.
20+
*/
21+
readonly errors: ParseError[]
22+
/**
23+
* The current state.
24+
*/
25+
state: TokenizerState
26+
/**
27+
* The current namespace.
28+
*/
29+
namespace: Namespace
30+
/**
31+
* The current flag of expression enabled.
32+
*/
33+
expressionEnabled: boolean
34+
/**
35+
* Get the next intermediate token.
36+
* @returns The intermediate token or null.
37+
*/
38+
nextToken(): IntermediateToken | null
39+
}
40+
41+
/**
42+
* Initialize tokenizer.
43+
* @param templateText The contents of the <template> tag.
44+
* @param text The complete source code
45+
* @param option The starting location of the templateText. Your token positions need to include this offset.
46+
*/
47+
export type CustomTemplateTokenizerConstructor = new (
48+
templateText: string,
49+
text: string,
50+
option: { startingLine: number; startingColumn: number },
51+
) => CustomTemplateTokenizer

‎src/html/parser.ts

Copy file name to clipboardExpand all lines: src/html/parser.ts
+22-13Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ import {
5454
} from "../common/parser-options"
5555
import sortedIndexBy from "lodash/sortedIndexBy"
5656
import sortedLastIndexBy from "lodash/sortedLastIndexBy"
57+
import type {
58+
CustomTemplateTokenizer,
59+
CustomTemplateTokenizerConstructor,
60+
} from "./custom-tokenizer"
5761

5862
const DIRECTIVE_NAME = /^(?:v-|[.:@#]).*[^.:@#]$/u
5963
const DT_DD = /^d[dt]$/u
@@ -167,7 +171,7 @@ function propagateEndLocation(node: VDocumentFragment | VElement): void {
167171
* This is not following to the HTML spec completely because Vue.js template spec is pretty different to HTML.
168172
*/
169173
export class Parser {
170-
private tokenizer: IntermediateTokenizer
174+
private tokenizer: IntermediateTokenizer | CustomTemplateTokenizer
171175
private locationCalculator: LocationCalculatorForHtml
172176
private baseParserOptions: ParserOptions
173177
private isSFC: boolean
@@ -480,12 +484,17 @@ export class Parser {
480484
/**
481485
* Process the given template text token with a configured template tokenizer, based on language.
482486
* @param token The template text token to process.
483-
* @param lang The template language the text token should be parsed as.
487+
* @param templateTokenizerOption The template tokenizer option.
484488
*/
485-
private processTemplateText(token: Text, lang: string): void {
486-
// eslint-disable-next-line @typescript-eslint/no-require-imports
487-
const TemplateTokenizer = require(this.baseParserOptions
488-
.templateTokenizer![lang])
489+
private processTemplateText(
490+
token: Text,
491+
templateTokenizerOption: string | CustomTemplateTokenizerConstructor,
492+
): void {
493+
const TemplateTokenizer: CustomTemplateTokenizerConstructor =
494+
typeof templateTokenizerOption === "function"
495+
? templateTokenizerOption
496+
: // eslint-disable-next-line @typescript-eslint/no-require-imports
497+
require(templateTokenizerOption)
489498
const templateTokenizer = new TemplateTokenizer(
490499
token.value,
491500
this.text,
@@ -696,13 +705,13 @@ export class Parser {
696705
(a) => a.key.name === "lang",
697706
)
698707
const lang = (langAttribute?.value as VLiteral)?.value
699-
if (
700-
lang &&
701-
lang !== "html" &&
702-
this.baseParserOptions.templateTokenizer?.[lang]
703-
) {
704-
this.processTemplateText(token, lang)
705-
return
708+
if (lang && lang !== "html") {
709+
const templateTokenizerOption =
710+
this.baseParserOptions.templateTokenizer?.[lang]
711+
if (templateTokenizerOption) {
712+
this.processTemplateText(token, templateTokenizerOption)
713+
return
714+
}
706715
}
707716
}
708717
parent.children.push({

‎src/script/index.ts

Copy file name to clipboardExpand all lines: src/script/index.ts
+8-6Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import {
4242
analyzeExternalReferences,
4343
analyzeVariablesAndExternalReferences,
4444
} from "./scope-analyzer"
45-
import type { ESLintCustomParser } from "../common/espree"
4645
import {
4746
getEcmaVersionIfUseEspree,
4847
getEspreeFromUser,
@@ -60,6 +59,8 @@ import {
6059
} from "../script-setup/parser-options"
6160
import { isScriptSetupElement } from "../common/ast-utils"
6261
import type { LinesAndColumns } from "../common/lines-and-columns"
62+
import type { ParserObject } from "../common/parser-object"
63+
import { isEnhancedParserObject, isParserObject } from "../common/parser-object"
6364

6465
// [1] = aliases.
6566
// [2] = delimiter.
@@ -545,15 +546,16 @@ export function parseScript(
545546
code: string,
546547
parserOptions: ParserOptions,
547548
): ESLintExtendedProgram {
548-
const parser: ESLintCustomParser =
549+
const parser: ParserObject =
549550
typeof parserOptions.parser === "string"
550551
? loadParser(parserOptions.parser)
552+
: isParserObject(parserOptions.parser)
553+
? parserOptions.parser
551554
: getEspreeFromEcmaVersion(parserOptions.ecmaVersion)
552555

553-
const result: any =
554-
typeof parser.parseForESLint === "function"
555-
? parser.parseForESLint(code, parserOptions)
556-
: parser.parse(code, parserOptions)
556+
const result: any = isEnhancedParserObject(parser)
557+
? parser.parseForESLint(code, parserOptions)
558+
: parser.parse(code, parserOptions)
557559

558560
if (result.ast != null) {
559561
return result

‎src/sfc/custom-block/index.ts

Copy file name to clipboardExpand all lines: src/sfc/custom-block/index.ts
+6-8Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ import { getEslintScope } from "../../common/eslint-scope"
1414
import { getEcmaVersionIfUseEspree } from "../../common/espree"
1515
import { fixErrorLocation, fixLocations } from "../../common/fix-locations"
1616
import type { LocationCalculatorForHtml } from "../../common/location-calculator"
17+
import type { ParserObject } from "../../common/parser-object"
18+
import { isEnhancedParserObject } from "../../common/parser-object"
1719
import type { ParserOptions } from "../../common/parser-options"
1820
import { DEFAULT_ECMA_VERSION } from "../../script-setup/parser-options"
1921

20-
export interface ESLintCustomBlockParser {
21-
parse(code: string, options: any): any
22-
parseForESLint?(code: string, options: any): any
23-
}
22+
export type ESLintCustomBlockParser = ParserObject<any, any>
2423

2524
export type CustomBlockContext = {
2625
getSourceCode(): SourceCode
@@ -181,10 +180,9 @@ function parseBlock(
181180
parser: ESLintCustomBlockParser,
182181
parserOptions: any,
183182
): any {
184-
const result: any =
185-
typeof parser.parseForESLint === "function"
186-
? parser.parseForESLint(code, parserOptions)
187-
: parser.parse(code, parserOptions)
183+
const result = isEnhancedParserObject(parser)
184+
? parser.parseForESLint(code, parserOptions)
185+
: parser.parse(code, parserOptions)
188186

189187
if (result.ast != null) {
190188
return result

‎test/index.js

Copy file name to clipboardExpand all lines: test/index.js
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,46 @@ describe("Basic tests", () => {
308308
assert.deepStrictEqual(report[0].messages, [])
309309
assert.deepStrictEqual(report[1].messages, [])
310310
})
311+
312+
it("should notify no error with parser object with '@typescript-eslint/parser'", async () => {
313+
const cli = new ESLint({
314+
cwd: FIXTURE_DIR,
315+
overrideConfig: {
316+
env: { es6: true, node: true },
317+
parser: PARSER_PATH,
318+
parserOptions: {
319+
parser: require("@typescript-eslint/parser"),
320+
},
321+
rules: { semi: ["error", "never"] },
322+
},
323+
useEslintrc: false,
324+
})
325+
const report = await cli.lintFiles(["typed.js"])
326+
const messages = report[0].messages
327+
328+
assert.deepStrictEqual(messages, [])
329+
})
330+
331+
it("should notify no error with multiple parser object with '@typescript-eslint/parser'", async () => {
332+
const cli = new ESLint({
333+
cwd: FIXTURE_DIR,
334+
overrideConfig: {
335+
env: { es6: true, node: true },
336+
parser: PARSER_PATH,
337+
parserOptions: {
338+
parser: {
339+
ts: require("@typescript-eslint/parser"),
340+
},
341+
},
342+
rules: { semi: ["error", "never"] },
343+
},
344+
useEslintrc: false,
345+
})
346+
const report = await cli.lintFiles(["typed.ts", "typed.tsx"])
347+
348+
assert.deepStrictEqual(report[0].messages, [])
349+
assert.deepStrictEqual(report[1].messages, [])
350+
})
311351
}
312352
})
313353

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.