Skip to content

Commit

Permalink
Improve performance (#12)
Browse files Browse the repository at this point in the history
The performance has been improved by eliminating one parses step.
And change excludes yaml-unist-parser from the dependencies.
  • Loading branch information
ota-meshi committed Jan 11, 2021
1 parent e78ce14 commit 02f97cd
Show file tree
Hide file tree
Showing 18 changed files with 1,732 additions and 786 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -2,7 +2,7 @@

A YAML parser that produces output [compatible with ESLint](https://eslint.org/docs/developer-guide/working-with-custom-parsers#all-nodes).

This parser is backed by excellent [yaml](https://github.com/eemeli/yaml) and [yaml-unist-parser](https://github.com/ikatyang/yaml-unist-parser) packages.
*This parser is backed by excellent [yaml](https://github.com/eemeli/yaml) package and it is heavily inspired by [yaml-unist-parser](https://github.com/ikatyang/yaml-unist-parser) package.*

[![NPM license](https://img.shields.io/npm/l/yaml-eslint-parser.svg)](https://www.npmjs.com/package/yaml-eslint-parser)
[![NPM version](https://img.shields.io/npm/v/yaml-eslint-parser.svg)](https://www.npmjs.com/package/yaml-eslint-parser)
Expand Down
7 changes: 4 additions & 3 deletions package.json
Expand Up @@ -14,7 +14,7 @@
"eslint-fix": "npm run lint -- --fix",
"test": "mocha --require ts-node/register \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
"cover": "nyc --reporter=lcov npm run test",
"debug": "mocha --require ts-node/register --inspect \"tests/src/**/*.ts\" --reporter dot",
"debug": "mocha --require ts-node/register/transpile-only --inspect \"tests/src/**/*.ts\" --reporter dot",
"preversion": "npm run lint && npm test",
"update-fixtures": "ts-node ./tools/update-fixtures.ts"
},
Expand All @@ -35,13 +35,14 @@
"homepage": "https://github.com/ota-meshi/yaml-eslint-parser#readme",
"dependencies": {
"eslint-visitor-keys": "^1.3.0",
"yaml": "^1.10.0",
"yaml-unist-parser": "^1.3.1"
"lodash": "^4.17.20",
"yaml": "^1.10.0"
},
"devDependencies": {
"@ota-meshi/eslint-plugin": "^0.0.6",
"@types/eslint": "^7.2.0",
"@types/eslint-visitor-keys": "^1.0.0",
"@types/lodash": "^4.14.167",
"@types/mocha": "^7.0.2",
"@types/node": "^14.0.13",
"@typescript-eslint/eslint-plugin": "^4.9.1",
Expand Down
169 changes: 169 additions & 0 deletions src/context.ts
@@ -0,0 +1,169 @@
import type {
Comment,
Locations,
Position,
Range,
Token,
YAMLProgram,
} from "./ast"
import type { ASTNode } from "./yaml"
import lodash from "lodash"
import { traverseNodes } from "./traverse"

type CSTRangeData = {
start: number
end: number
}
export class Context {
public readonly code: string

public readonly tokens: Token[] = []

public readonly comments: Comment[] = []

public hasCR = false

private readonly locs: LinesAndColumns

private readonly locsMap = new Map<number, Position>()

private readonly crs: number[]

public constructor(origCode: string) {
const len = origCode.length
const lineStartIndices = [0]
const crs: number[] = []
let code = ""
for (let index = 0; index < len; ) {
const c = origCode[index++]
if (c === "\r") {
const next = origCode[index++] || ""
if (next === "\n") {
code += next
crs.push(index - 2)
} else {
code += `\n${next}`
}
lineStartIndices.push(code.length)
} else {
code += c
if (c === "\n") {
lineStartIndices.push(code.length)
}
}
}
this.code = code
this.locs = new LinesAndColumns(lineStartIndices)
this.hasCR = Boolean(crs.length)
this.crs = crs
}

public remapCR(ast: YAMLProgram): void {
const cache: Record<number, number> = {}
const remapIndex = (index: number): number => {
let result = cache[index]
if (result != null) {
return result
}
result = index
for (const cr of this.crs) {
if (cr < result) {
result++
} else {
break
}
}
return (cache[index] = result)
}
// eslint-disable-next-line func-style -- ignore
const remapRange = (range: [number, number]): [number, number] => {
return [remapIndex(range[0]), remapIndex(range[1])]
}

traverseNodes(ast, {
enterNode(node) {
node.range = remapRange(node.range)
},
leaveNode() {
// ignore
},
})
for (const token of ast.tokens) {
token.range = remapRange(token.range)
}
for (const comment of ast.comments) {
comment.range = remapRange(comment.range)
}
}

public getLocFromIndex(index: number): { line: number; column: number } {
let loc = this.locsMap.get(index)
if (!loc) {
loc = this.locs.getLocFromIndex(index)
this.locsMap.set(index, loc)
}
return {
line: loc.line,
column: loc.column,
}
}

/**
* Get the location information of the given node.
* @param node The node.
*/
public getConvertLocation(node: { range: Range } | ASTNode): Locations {
const [start, end] = node.range!

return {
range: [start, end],
loc: {
start: this.getLocFromIndex(start),
end: this.getLocFromIndex(end),
},
}
}

/**
* Get the location information of the given CSTRange.
* @param node The node.
*/
public getConvertLocationFromCSTRange(
range: CSTRangeData | undefined | null,
): Locations {
return this.getConvertLocation({ range: [range!.start, range!.end] })
}

public addComment(comment: Comment): void {
this.comments.push(comment)
}

/**
* Add token to tokens
*/
public addToken(type: Token["type"], range: Range): Token {
const token = {
type,
value: this.code.slice(...range),
...this.getConvertLocation({ range }),
}
this.tokens.push(token)
return token
}
}

class LinesAndColumns {
private readonly lineStartIndices: number[]

public constructor(lineStartIndices: number[]) {
this.lineStartIndices = lineStartIndices
}

public getLocFromIndex(index: number) {
const lineNumber = lodash.sortedLastIndex(this.lineStartIndices, index)
return {
line: lineNumber,
column: index - this.lineStartIndices[lineNumber - 1],
}
}
}

0 comments on commit 02f97cd

Please sign in to comment.