Skip to content

Commit

Permalink
fix: add helper getLocFromRange
Browse files Browse the repository at this point in the history
close #292
  • Loading branch information
JounQin committed Mar 19, 2021
1 parent f5d288a commit 618e5ec
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 28 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module.exports = {
// related to https://github.com/eslint/eslint/issues/14207
rules: {
'prettier/prettier': 0,
'react/no-unescaped-entities': 1,
'unicorn/filename-case': 0,
},
settings: {
Expand Down
67 changes: 47 additions & 20 deletions packages/eslint-mdx/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference path="../typings.d.ts" />

import type { Linter } from 'eslint'
import type { SourceLocation } from 'estree'
import type { Position as ESPosition, SourceLocation } from 'estree'
import type { Position } from 'unist'

import type { Arrayable, JsxNode, ParserFn, ParserOptions } from './types'
Expand Down Expand Up @@ -90,45 +90,72 @@ export const hasProperties = <T, P extends keyof T = keyof T>(
obj &&
properties.every(property => property in obj)

export const getLinesFromCode = (code: string) => code.split('\n')

// fix #292
export const getLocFromRange = (
lines: string[],
[start, end]: [number, number],
): SourceLocation => {
let offset = 0

let startPos: ESPosition
let endPos: ESPosition

for (const [index, { length }] of lines.entries()) {
const line = index + 1
const nextOffset = offset + length

if (nextOffset >= start) {
startPos = {
line,
column: start - offset,
}
}

if (nextOffset >= end) {
endPos = {
line,
column: end - offset,
}

break
}

offset = nextOffset + 1 // add a line break `\n` offset
}

return {
start: startPos,
end: endPos,
}
}

export const restoreNodeLocation = <T>(
lines: string[],
node: T,
startLine: number,
offset: number,
): T => {
if (node && typeof node === 'object') {
for (const value of Object.values(node)) {
restoreNodeLocation(value, startLine, offset)
restoreNodeLocation(lines, value, offset)
}
}

if (!hasProperties<BaseNode>(node, ['loc', 'range'])) {
return node
}

const {
loc: { start: startLoc, end: endLoc },
range,
} = node
const { range } = node

const start = range[0] + offset
const end = range[1] + offset

const restoredStartLine = startLine + startLoc.line
const restoredEndLine = startLine + endLoc.line

return Object.assign(node, {
start,
end,
range: [start, end],
loc: {
start: {
line: restoredStartLine,
column: startLoc.column + (restoredStartLine === 1 ? offset : 0),
},
end: {
line: restoredEndLine,
column: endLoc.column + (restoredEndLine === 1 ? offset : 0),
},
},
loc: getLocFromRange(lines, [start, end]),
})
}

Expand Down
12 changes: 6 additions & 6 deletions packages/eslint-mdx/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Node, Parent } from 'unist'

import {
arrayify,
getLinesFromCode,
hasProperties,
isJsxNode,
last,
Expand Down Expand Up @@ -183,6 +184,7 @@ export class Parser {
}

if (isMdx) {
const lines = getLinesFromCode(code)
traverse(root, {
code,
enter: (node, parent) => {
Expand All @@ -193,7 +195,7 @@ export class Parser {
for (const normalizedNode of arrayify(
this.normalizeJsxNode(node, parent, options),
)) {
this._nodeToAst(normalizedNode, options)
this._nodeToAst(lines, normalizedNode, options)
}
},
})
Expand Down Expand Up @@ -333,7 +335,7 @@ export class Parser {
}

// @internal
private _nodeToAst(node: Node, options: ParserOptions) {
private _nodeToAst(lines: string[], node: Node, options: ParserOptions) {
if (node.data && node.data.jsxType === 'JSXElementWithHTMLComments') {
this._services.JSXElementsWithHTMLComments.push(node)
}
Expand All @@ -354,8 +356,6 @@ export class Parser {
return
}

const startLine = loc.start.line - 1 // ! line is 1-indexed, change to 0-indexed to simplify usage

let program: AST.Program

try {
Expand All @@ -366,7 +366,7 @@ export class Parser {
// should be handled by `_normalizeJsxNodes`, just for robustness
e.index += start
e.column = e.lineNumber > 1 ? e.column : e.column + loc.start.column
e.lineNumber += startLine
e.lineNumber += loc.start.line - 1
}
throw e
}
Expand All @@ -377,7 +377,7 @@ export class Parser {
this._ast[prop].push(
// ts doesn't understand the mixed type
...program[prop].map((item: never) =>
restoreNodeLocation(item, startLine, offset),
restoreNodeLocation(lines, item, offset),
),
)
}
Expand Down
23 changes: 22 additions & 1 deletion test/__snapshots__/fixtures.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ Array [

exports[`fixtures should match all snapshots: 287.mdx 1`] = `Array []`;

exports[`fixtures should match all snapshots: 292.mdx 1`] = `Array []`;

exports[`fixtures should match all snapshots: adjacent.mdx 1`] = `Array []`;

exports[`fixtures should match all snapshots: basic.mdx 1`] = `Array []`;
Expand Down Expand Up @@ -231,7 +233,26 @@ exports[`fixtures should match all snapshots: markdown.md 1`] = `Array []`;

exports[`fixtures should match all snapshots: no-jsx-html-comments.mdx 1`] = `Array []`;

exports[`fixtures should match all snapshots: no-unescaped-entities.mdx 1`] = `Array []`;
exports[`fixtures should match all snapshots: no-unescaped-entities.mdx 1`] = `
Array [
Object {
"column": 8,
"line": 2,
"message": "\`>\` can be escaped with \`&gt;\`.",
"nodeType": "JSXText",
"ruleId": "react/no-unescaped-entities",
"severity": 1,
},
Object {
"column": 13,
"line": 5,
"message": "\`>\` can be escaped with \`&gt;\`.",
"nodeType": "JSXText",
"ruleId": "react/no-unescaped-entities",
"severity": 1,
},
]
`;

exports[`fixtures should match all snapshots: processor.mdx 1`] = `
Array [
Expand Down
27 changes: 27 additions & 0 deletions test/__snapshots__/helpers.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Helpers should get correct loc range range 1`] = `
Object {
"end": Object {
"column": 8,
"line": 1,
},
"start": Object {
"column": 2,
"line": 1,
},
}
`;

exports[`Helpers should get correct loc range range 2`] = `
Object {
"end": Object {
"column": 26,
"line": 4,
},
"start": Object {
"column": 19,
"line": 4,
},
}
`;
1 change: 1 addition & 0 deletions test/fixtures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const getCli = (lintCodeBlocks = false) =>
extends: ['plugin:mdx/recommended'],
plugins: ['react', 'unicorn', 'prettier'],
rules: {
'react/no-unescaped-entities': 1,
'unicorn/prefer-spread': 2,
},
overrides: lintCodeBlocks
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/292.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Header

paragraph <div className="abc">content</div>

- - <div kind="docs-packages-vuetify-preset" story="page">
Vuetify preset
</div>
: some extra text describing the preset
29 changes: 28 additions & 1 deletion test/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path'

import { arrayify } from 'eslint-mdx'
import { arrayify, getLinesFromCode, getLocFromRange } from 'eslint-mdx'
import { getGlobals, getShortLang, requirePkg } from 'eslint-plugin-mdx'

describe('Helpers', () => {
Expand All @@ -18,6 +18,33 @@ describe('Helpers', () => {
expect(getShortLang('4.Markdown', { markdown: 'mkdn' })).toBe('mkdn')
})

it('should get correct loc range range', () => {
const code = `
# Header
- jsx in list <div>
<a href="link">content</a>
</div>
`.trim()
const lines = getLinesFromCode(code)
const headerWord = 'Header'
const contentWord = 'content'
const headerWordIndex = code.indexOf(headerWord)
const contentWordIndex = code.indexOf(contentWord)
expect(
getLocFromRange(lines, [
headerWordIndex,
headerWordIndex + headerWord.length,
]),
).toMatchSnapshot()
expect(
getLocFromRange(lines, [
contentWordIndex,
contentWordIndex + contentWord.length,
]),
).toMatchSnapshot()
})

it('should resolve globals correctly', () => {
expect(getGlobals({})).toEqual({})
expect(getGlobals(['a', 'b'])).toEqual({
Expand Down

0 comments on commit 618e5ec

Please sign in to comment.