Skip to content

Commit 45f8900

Browse files
authored
Add defineCustomBlocksVisitor to parserServices (#91)
* Add defineCustomBlocksVisitor to parserServices * fix * Update * Add testcase * ignore not SFC
1 parent 495abaa commit 45f8900

File tree

15 files changed

+1525
-75
lines changed

15 files changed

+1525
-75
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ But, it cannot be parsed with Vue 2.
173173
- `defineTemplateBodyVisitor(templateVisitor, scriptVisitor)` ... returns ESLint visitor to traverse `<template>`.
174174
- `getTemplateBodyTokenStore()` ... returns ESLint `TokenStore` to get the tokens of `<template>`.
175175
- `getDocumentFragment()` ... returns the root `VDocumentFragment`.
176+
- `defineCustomBlocksVisitor(context, customParser, rule, scriptVisitor)` ... returns ESLint visitor that parses and traverses the contents of the custom block.
176177
- [ast.md](./docs/ast.md) is `<template>` AST specification.
177178
- [mustache-interpolation-spacing.js](https://github.com/vuejs/eslint-plugin-vue/blob/b434ff99d37f35570fa351681e43ba2cf5746db3/lib/rules/mustache-interpolation-spacing.js) is an example.
178179

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"devDependencies": {
2424
"@mysticatea/eslint-plugin": "^13.0.0",
2525
"@types/debug": "0.0.30",
26-
"@types/estree": "0.0.38",
26+
"@types/eslint": "^7.2.6",
27+
"@types/estree": "0.0.45",
2728
"@types/lodash": "^4.14.120",
2829
"@types/mocha": "^5.2.4",
2930
"@types/node": "^10.12.21",
@@ -35,6 +36,7 @@
3536
"dts-bundle": "^0.7.3",
3637
"eslint": "^7.0.0",
3738
"fs-extra": "^7.0.1",
39+
"jsonc-eslint-parser": "^0.6.0",
3840
"mocha": "^6.1.4",
3941
"npm-run-all": "^4.1.5",
4042
"nyc": "^14.0.0",

src/common/fix-locations.ts

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
ESLintExtendedProgram,
3+
LocationRange,
4+
Node,
5+
traverseNodes,
6+
} from "../ast"
7+
import { LocationCalculator } from "./location-calculator"
8+
9+
/**
10+
* Do post-process of parsing an expression.
11+
*
12+
* 1. Set `node.parent`.
13+
* 2. Fix `node.range` and `node.loc` for HTML entities.
14+
*
15+
* @param result The parsing result to modify.
16+
* @param locationCalculator The location calculator to modify.
17+
*/
18+
export function fixLocations(
19+
result: ESLintExtendedProgram,
20+
locationCalculator: LocationCalculator,
21+
): void {
22+
// There are cases which the same node instance appears twice in the tree.
23+
// E.g. `let {a} = {}` // This `a` appears twice at `Property#key` and `Property#value`.
24+
const traversed = new Set<Node | number[] | LocationRange>()
25+
26+
traverseNodes(result.ast, {
27+
visitorKeys: result.visitorKeys,
28+
29+
enterNode(node, parent) {
30+
if (!traversed.has(node)) {
31+
traversed.add(node)
32+
node.parent = parent
33+
34+
// `babel-eslint@8` has shared `Node#range` with multiple nodes.
35+
// See also: https://github.com/vuejs/eslint-plugin-vue/issues/208
36+
if (traversed.has(node.range)) {
37+
if (!traversed.has(node.loc)) {
38+
// However, `Node#loc` may not be shared.
39+
// See also: https://github.com/vuejs/vue-eslint-parser/issues/84
40+
node.loc.start = locationCalculator.getLocFromIndex(
41+
node.range[0],
42+
)
43+
node.loc.end = locationCalculator.getLocFromIndex(
44+
node.range[1],
45+
)
46+
traversed.add(node.loc)
47+
}
48+
} else {
49+
locationCalculator.fixLocation(node)
50+
traversed.add(node.range)
51+
traversed.add(node.loc)
52+
}
53+
}
54+
},
55+
56+
leaveNode() {
57+
// Do nothing.
58+
},
59+
})
60+
61+
for (const token of result.ast.tokens || []) {
62+
locationCalculator.fixLocation(token)
63+
}
64+
for (const comment of result.ast.comments || []) {
65+
locationCalculator.fixLocation(comment)
66+
}
67+
}

src/common/parser-options.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as path from "path"
2+
13
export interface ParserOptions {
24
// vue-eslint-parser options
35
parser?: boolean | string
@@ -33,3 +35,10 @@ export interface ParserOptions {
3335
// others
3436
// [key: string]: any
3537
}
38+
39+
export function isSFCFile(parserOptions: ParserOptions) {
40+
if (parserOptions.filePath === "<input>") {
41+
return true
42+
}
43+
return path.extname(parserOptions.filePath || "unknown.vue") === ".vue"
44+
}

src/html/parser.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* @copyright 2017 Toru Nagashima. All rights reserved.
44
* See LICENSE file in root directory for full license.
55
*/
6-
import * as path from "path"
76
import assert from "assert"
87
import last from "lodash/last"
98
import findLastIndex from "lodash/findLastIndex"
@@ -47,7 +46,7 @@ import {
4746
Text,
4847
} from "./intermediate-tokenizer"
4948
import { Tokenizer } from "./tokenizer"
50-
import { ParserOptions } from "../common/parser-options"
49+
import { isSFCFile, ParserOptions } from "../common/parser-options"
5150

5251
const DIRECTIVE_NAME = /^(?:v-|[.:@#]).*[^.:@#]$/u
5352
const DT_DD = /^d[dt]$/u
@@ -232,8 +231,7 @@ export class Parser {
232231
tokenizer.lineTerminators,
233232
)
234233
this.parserOptions = parserOptions
235-
this.isSFC =
236-
path.extname(parserOptions.filePath || "unknown.vue") === ".vue"
234+
this.isSFC = isSFCFile(parserOptions)
237235
this.document = {
238236
type: "VDocumentFragment",
239237
range: [0, 0],

src/index.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export function parseForESLint(
8282
options = Object.assign(
8383
{
8484
comment: true,
85-
ecmaVersion: 2015,
85+
ecmaVersion: 2017,
8686
loc: true,
8787
range: true,
8888
tokens: true,
@@ -92,14 +92,16 @@ export function parseForESLint(
9292

9393
let result: AST.ESLintExtendedProgram
9494
let document: AST.VDocumentFragment | null
95+
let locationCalculator: LocationCalculator | null
9596
if (!isVueFile(code, options)) {
9697
result = parseScript(code, options)
9798
document = null
99+
locationCalculator = null
98100
} else {
99101
const skipParsingScript = options.parser === false
100102
const tokenizer = new HTMLTokenizer(code, options)
101103
const rootAST = new HTMLParser(tokenizer, options).parse()
102-
const locationCalcurator = new LocationCalculator(
104+
locationCalculator = new LocationCalculator(
103105
tokenizer.gaps,
104106
tokenizer.lineTerminators,
105107
)
@@ -119,7 +121,7 @@ export function parseForESLint(
119121
if (skipParsingScript || script == null) {
120122
result = parseScript("", options)
121123
} else {
122-
result = parseScriptElement(script, locationCalcurator, options)
124+
result = parseScriptElement(script, locationCalculator, options)
123125
}
124126

125127
result.ast.templateBody = templateBody
@@ -128,7 +130,9 @@ export function parseForESLint(
128130

129131
result.services = Object.assign(
130132
result.services || {},
131-
services.define(result.ast, document),
133+
services.define(code, result.ast, document, locationCalculator, {
134+
parserOptions: options,
135+
}),
132136
)
133137

134138
return result

0 commit comments

Comments
 (0)