Skip to content

Commit

Permalink
✨ Add getDocumentFragment() to parserServices (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored and mysticatea committed Nov 9, 2019
1 parent 57c8624 commit 075d3ff
Show file tree
Hide file tree
Showing 12 changed files with 5,430 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -95,6 +95,7 @@ This is useful for people who use the language ESLint community doesn't provide
- This parser provides `parserServices` to traverse `<template>`.
- `defineTemplateBodyVisitor(templateVisitor, scriptVisitor)` ... returns ESLint visitor to traverse `<template>`.
- `getTemplateBodyTokenStore()` ... returns ESLint `TokenStore` to get the tokens of `<template>`.
- `getDocumentFragment()` ... returns the root `VDocumentFragment`.
- [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
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -60,7 +60,7 @@
"test": "npm run -s test:mocha",
"test:mocha": "nyc mocha \"test/*.js\" --reporter dot --timeout 10000",
"preupdate-fixtures": "npm run -s build",
"update-fixtures": "node scripts/update-fixtures-ast.js",
"update-fixtures": "node scripts/update-fixtures-ast.js && node scripts/update-fixtures-document-fragment.js",
"preversion": "npm test",
"version": "npm run -s build",
"postversion": "git push && git push --tags",
Expand Down
68 changes: 68 additions & 0 deletions scripts/update-fixtures-document-fragment.js
@@ -0,0 +1,68 @@
"use strict"

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const fs = require("fs")
const path = require("path")
const parser = require("../")

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const ROOT = path.join(__dirname, "../test/fixtures/document-fragment")
const TARGETS = fs.readdirSync(ROOT)
const PARSER_OPTIONS = {
comment: true,
ecmaVersion: 6,
loc: true,
range: true,
tokens: true,
sourceType: "module",
}

/**
* Remove `parent` proeprties from the given AST.
* @param {string} key The key.
* @param {any} value The value of the key.
* @returns {any} The value of the key to output.
*/
function replacer(key, value) {
if (key === "parent") {
return undefined
}
if (key === "errors" && Array.isArray(value)) {
return value.map(e => ({
message: e.message,
index: e.index,
lineNumber: e.lineNumber,
column: e.column,
}))
}
return value
}

//------------------------------------------------------------------------------
// Main
//------------------------------------------------------------------------------

for (const name of TARGETS) {
const sourceFileName = fs
.readdirSync(path.join(ROOT, name))
.find(f => f.startsWith("source."))
const sourcePath = path.join(ROOT, `${name}/${sourceFileName}`)
const source = fs.readFileSync(sourcePath, "utf8")
const result = parser.parseForESLint(
source,
Object.assign({ filePath: sourcePath }, PARSER_OPTIONS)
)
const actual = result.services.getDocumentFragment()

const resultPath = path.join(ROOT, `${name}/document-fragment.json`)

console.log("Update:", name)

fs.writeFileSync(resultPath, JSON.stringify(actual, replacer, 4))
}
5 changes: 4 additions & 1 deletion src/index.ts
Expand Up @@ -90,8 +90,10 @@ export function parseForESLint(
)

let result: AST.ESLintExtendedProgram
let document: AST.VDocumentFragment | null
if (!isVueFile(code, options)) {
result = parseScript(code, options)
document = null
} else {
const skipParsingScript = options.parser === false
const tokenizer = new HTMLTokenizer(code)
Expand Down Expand Up @@ -120,11 +122,12 @@ export function parseForESLint(
}

result.ast.templateBody = templateBody
document = rootAST
}

result.services = Object.assign(
result.services || {},
services.define(result.ast),
services.define(result.ast, document),
)

return result
Expand Down
26 changes: 24 additions & 2 deletions src/parser-services.ts
Expand Up @@ -6,7 +6,12 @@
import EventEmitter from "events"
import NodeEventGenerator from "./external/node-event-generator"
import TokenStore from "./external/token-store"
import { traverseNodes, ESLintProgram, VElement } from "./ast"
import {
traverseNodes,
ESLintProgram,
VElement,
VDocumentFragment,
} from "./ast"

//------------------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -35,13 +40,22 @@ export interface ParserServices {
* @returns The token store of template body.
*/
getTemplateBodyTokenStore(): TokenStore

/**
* Get the root document fragment.
* @returns The root document fragment.
*/
getDocumentFragment(): VDocumentFragment | null
}

/**
* Define the parser service
* @param rootAST
*/
export function define(rootAST: ESLintProgram): ParserServices {
export function define(
rootAST: ESLintProgram,
document: VDocumentFragment | null,
): ParserServices {
return {
/**
* Define handlers to traverse the template body.
Expand Down Expand Up @@ -118,5 +132,13 @@ export function define(rootAST: ESLintProgram): ParserServices {

return store
},

/**
* Get the root document fragment.
* @returns The root document fragment.
*/
getDocumentFragment(): VDocumentFragment | null {
return document
},
}
}
80 changes: 80 additions & 0 deletions test/document-fragment.js
@@ -0,0 +1,80 @@
"use strict"

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const assert = require("assert")
const fs = require("fs")
const path = require("path")
const parser = require("..")

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const ROOT = path.join(__dirname, "fixtures/document-fragment")
const TARGETS = fs.readdirSync(ROOT)
const PARSER_OPTIONS = {
comment: true,
ecmaVersion: 6,
loc: true,
range: true,
tokens: true,
sourceType: "module",
}

/**
* Remove `parent` proeprties from the given AST.
* @param {string} key The key.
* @param {any} value The value of the key.
* @returns {any} The value of the key to output.
*/
function replacer(key, value) {
if (key === "parent") {
return undefined
}
if (key === "errors" && Array.isArray(value)) {
return value.map(e => ({
message: e.message,
index: e.index,
lineNumber: e.lineNumber,
column: e.column,
}))
}
return value
}

//------------------------------------------------------------------------------
// Main
//------------------------------------------------------------------------------

describe("services.getDocumentFragment", () => {
for (const name of TARGETS) {
const sourceFileName = fs
.readdirSync(path.join(ROOT, name))
.find(f => f.startsWith("source."))
const sourcePath = path.join(ROOT, `${name}/${sourceFileName}`)
const source = fs.readFileSync(sourcePath, "utf8")
const result = parser.parseForESLint(
source,
Object.assign({ filePath: sourcePath }, PARSER_OPTIONS)
)
const actual = result.services.getDocumentFragment()

describe(`'test/fixtures/document-fragment/${name}/${sourceFileName}'`, () => {
it("should be parsed to valid document fragment.", () => {
const resultPath = path.join(
ROOT,
`${name}/document-fragment.json`
)
const expected = fs.readFileSync(resultPath, "utf8")

assert.strictEqual(
JSON.stringify(actual, replacer, 4),
expected
)
})
})
}
})

0 comments on commit 075d3ff

Please sign in to comment.