Skip to content

Add defineCustomBlocksVisitor to parserServices #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ But, it cannot be parsed with Vue 2.
- `defineTemplateBodyVisitor(templateVisitor, scriptVisitor)` ... returns ESLint visitor to traverse `<template>`.
- `getTemplateBodyTokenStore()` ... returns ESLint `TokenStore` to get the tokens of `<template>`.
- `getDocumentFragment()` ... returns the root `VDocumentFragment`.
- `defineCustomBlocksVisitor(context, customParser, rule, scriptVisitor)` ... returns ESLint visitor that parses and traverses the contents of the custom block.
- [ast.md](./docs/ast.md) is `<template>` AST specification.
- [mustache-interpolation-spacing.js](https://github.com/vuejs/eslint-plugin-vue/blob/b434ff99d37f35570fa351681e43ba2cf5746db3/lib/rules/mustache-interpolation-spacing.js) is an example.

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"devDependencies": {
"@mysticatea/eslint-plugin": "^13.0.0",
"@types/debug": "0.0.30",
"@types/estree": "0.0.38",
"@types/eslint": "^7.2.6",
"@types/estree": "0.0.45",
"@types/lodash": "^4.14.120",
"@types/mocha": "^5.2.4",
"@types/node": "^10.12.21",
Expand All @@ -35,6 +36,7 @@
"dts-bundle": "^0.7.3",
"eslint": "^7.0.0",
"fs-extra": "^7.0.1",
"jsonc-eslint-parser": "^0.6.0",
"mocha": "^6.1.4",
"npm-run-all": "^4.1.5",
"nyc": "^14.0.0",
Expand Down
67 changes: 67 additions & 0 deletions src/common/fix-locations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
ESLintExtendedProgram,
LocationRange,
Node,
traverseNodes,
} from "../ast"
import { LocationCalculator } from "./location-calculator"

/**
* Do post-process of parsing an expression.
*
* 1. Set `node.parent`.
* 2. Fix `node.range` and `node.loc` for HTML entities.
*
* @param result The parsing result to modify.
* @param locationCalculator The location calculator to modify.
*/
export function fixLocations(
result: ESLintExtendedProgram,
locationCalculator: LocationCalculator,
): void {
// There are cases which the same node instance appears twice in the tree.
// E.g. `let {a} = {}` // This `a` appears twice at `Property#key` and `Property#value`.
const traversed = new Set<Node | number[] | LocationRange>()

traverseNodes(result.ast, {
visitorKeys: result.visitorKeys,

enterNode(node, parent) {
if (!traversed.has(node)) {
traversed.add(node)
node.parent = parent

// `babel-eslint@8` has shared `Node#range` with multiple nodes.
// See also: https://github.com/vuejs/eslint-plugin-vue/issues/208
if (traversed.has(node.range)) {
if (!traversed.has(node.loc)) {
// However, `Node#loc` may not be shared.
// See also: https://github.com/vuejs/vue-eslint-parser/issues/84
node.loc.start = locationCalculator.getLocFromIndex(
node.range[0],
)
node.loc.end = locationCalculator.getLocFromIndex(
node.range[1],
)
traversed.add(node.loc)
}
} else {
locationCalculator.fixLocation(node)
traversed.add(node.range)
traversed.add(node.loc)
}
}
},

leaveNode() {
// Do nothing.
},
})

for (const token of result.ast.tokens || []) {
locationCalculator.fixLocation(token)
}
for (const comment of result.ast.comments || []) {
locationCalculator.fixLocation(comment)
}
}
9 changes: 9 additions & 0 deletions src/common/parser-options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as path from "path"

export interface ParserOptions {
// vue-eslint-parser options
parser?: boolean | string
Expand Down Expand Up @@ -33,3 +35,10 @@ export interface ParserOptions {
// others
// [key: string]: any
}

export function isSFCFile(parserOptions: ParserOptions) {
if (parserOptions.filePath === "<input>") {
return true
}
return path.extname(parserOptions.filePath || "unknown.vue") === ".vue"
}
6 changes: 2 additions & 4 deletions src/html/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
import * as path from "path"
import assert from "assert"
import last from "lodash/last"
import findLastIndex from "lodash/findLastIndex"
Expand Down Expand Up @@ -47,7 +46,7 @@ import {
Text,
} from "./intermediate-tokenizer"
import { Tokenizer } from "./tokenizer"
import { ParserOptions } from "../common/parser-options"
import { isSFCFile, ParserOptions } from "../common/parser-options"

const DIRECTIVE_NAME = /^(?:v-|[.:@#]).*[^.:@#]$/u
const DT_DD = /^d[dt]$/u
Expand Down Expand Up @@ -232,8 +231,7 @@ export class Parser {
tokenizer.lineTerminators,
)
this.parserOptions = parserOptions
this.isSFC =
path.extname(parserOptions.filePath || "unknown.vue") === ".vue"
this.isSFC = isSFCFile(parserOptions)
this.document = {
type: "VDocumentFragment",
range: [0, 0],
Expand Down
12 changes: 8 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function parseForESLint(
options = Object.assign(
{
comment: true,
ecmaVersion: 2015,
ecmaVersion: 2017,
loc: true,
range: true,
tokens: true,
Expand All @@ -92,14 +92,16 @@ export function parseForESLint(

let result: AST.ESLintExtendedProgram
let document: AST.VDocumentFragment | null
let locationCalculator: LocationCalculator | null
if (!isVueFile(code, options)) {
result = parseScript(code, options)
document = null
locationCalculator = null
} else {
const skipParsingScript = options.parser === false
const tokenizer = new HTMLTokenizer(code, options)
const rootAST = new HTMLParser(tokenizer, options).parse()
const locationCalcurator = new LocationCalculator(
locationCalculator = new LocationCalculator(
tokenizer.gaps,
tokenizer.lineTerminators,
)
Expand All @@ -119,7 +121,7 @@ export function parseForESLint(
if (skipParsingScript || script == null) {
result = parseScript("", options)
} else {
result = parseScriptElement(script, locationCalcurator, options)
result = parseScriptElement(script, locationCalculator, options)
}

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

result.services = Object.assign(
result.services || {},
services.define(result.ast, document),
services.define(code, result.ast, document, locationCalculator, {
parserOptions: options,
}),
)

return result
Expand Down
Loading