Skip to content

Fix wrong handling of CRLF in <script> #146

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 1 commit into from
Feb 22, 2022
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
10 changes: 10 additions & 0 deletions src/common/lines-and-columns.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sortedLastIndex from "lodash/sortedLastIndex"
import type { Location } from "../ast"
import type { LocationCalculator } from "./location-calculator"
/**
* A class for getting lines and columns location.
*/
Expand All @@ -24,4 +25,13 @@ export class LinesAndColumns {
const column = index - (line === 1 ? 0 : this.ltOffsets[line - 2])
return { line, column }
}

public createOffsetLocationCalculator(offset: number): LocationCalculator {
return {
getFixOffset() {
return offset
},
getLocFromIndex: this.getLocFromIndex.bind(this),
}
}
}
16 changes: 9 additions & 7 deletions src/html/tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export class Tokenizer {
public readonly lineTerminators: number[]
private readonly parserOptions: ParserOptions
private lastCodePoint: number
private lastCodePointRaw: number
private offset: number
private column: number
private line: number
Expand Down Expand Up @@ -221,7 +222,7 @@ export class Tokenizer {
this.gaps = []
this.lineTerminators = []
this.parserOptions = parserOptions || {}
this.lastCodePoint = NULL
this.lastCodePoint = this.lastCodePointRaw = NULL
this.offset = -1
this.column = -1
this.line = 1
Expand Down Expand Up @@ -307,14 +308,14 @@ export class Tokenizer {
*/
private consumeNextCodePoint(): number {
if (this.offset >= this.text.length) {
this.lastCodePoint = EOF
this.lastCodePoint = this.lastCodePointRaw = EOF
return EOF
}

this.offset += this.lastCodePoint >= 0x10000 ? 2 : 1
if (this.offset >= this.text.length) {
this.advanceLocation()
this.lastCodePoint = EOF
this.lastCodePoint = this.lastCodePointRaw = EOF
return EOF
}

Expand All @@ -334,18 +335,19 @@ export class Tokenizer {
}

// Skip LF to convert CRLF → LF.
if (this.lastCodePoint === CARRIAGE_RETURN && cp === LINE_FEED) {
this.lastCodePoint = LINE_FEED
if (this.lastCodePointRaw === CARRIAGE_RETURN && cp === LINE_FEED) {
this.lastCodePoint = this.lastCodePointRaw = LINE_FEED
this.gaps.push(this.offset)
return this.consumeNextCodePoint()
}

// Update locations.
this.advanceLocation()
this.lastCodePoint = cp
this.lastCodePoint = this.lastCodePointRaw = cp

// To convert CRLF → LF.
if (cp === CARRIAGE_RETURN) {
this.lastCodePoint = LINE_FEED
return LINE_FEED
}

Expand All @@ -356,7 +358,7 @@ export class Tokenizer {
* Advance the current line and column.
*/
private advanceLocation(): void {
if (this.lastCodePoint === LINE_FEED) {
if (this.lastCodePointRaw === LINE_FEED) {
this.lineTerminators.push(this.offset)
this.line += 1
this.column = 0
Expand Down
13 changes: 9 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,15 @@ export function parseForESLint(
},
)
} else {
result = parseScriptElement(scripts[0], locationCalculator, {
...options,
parser: scriptParser,
})
result = parseScriptElement(
scripts[0],
code,
new LinesAndColumns(tokenizer.lineTerminators),
{
...options,
parser: scriptParser,
},
)
}

if (options.vueFeatures?.styleCSSVariableInjection ?? true) {
Expand Down
27 changes: 6 additions & 21 deletions src/script-setup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,15 @@ function parseScript(
* Parse the source code of the given `<script setup>` and `<script>` elements.
* @param scriptSetupElement The `<script setup>` element to parse.
* @param nodes The `<script>` elements to parse.
* @param code The source code of SFC.
* @param sfcCode The source code of SFC.
* @param linesAndColumns The lines and columns location calculator.
* @param parserOptions The parser options.
* @returns The result of parsing.
*/
export function parseScriptSetupElements(
scriptSetupElement: VElement,
scriptElement: VElement,
code: string,
sfcCode: string,
linesAndColumns: LinesAndColumns,
originalParserOptions: ParserOptions,
): ESLintExtendedProgram {
Expand All @@ -207,16 +207,15 @@ export function parseScriptSetupElements(
const scriptSetupModuleCodeBlocks = getScriptSetupModuleCodeBlocks(
scriptSetupElement,
scriptElement,
code,
sfcCode,
linesAndColumns,
parserOptions,
)
if (!scriptSetupModuleCodeBlocks) {
return parseScriptFragment(
"",
simpleOffsetLocationCalculator(
linesAndColumns.createOffsetLocationCalculator(
scriptSetupElement.startTag.range[1],
linesAndColumns,
),
parserOptions,
)
Expand Down Expand Up @@ -492,10 +491,8 @@ function getScriptSetupCodeBlocks(
scriptSetupEndOffset,
)

const offsetLocationCalculator = simpleOffsetLocationCalculator(
scriptSetupStartOffset,
linesAndColumns,
)
const offsetLocationCalculator =
linesAndColumns.createOffsetLocationCalculator(scriptSetupStartOffset)

const result = parseScript(
scriptCode,
Expand Down Expand Up @@ -925,15 +922,3 @@ function remapLocationAndTokens(

fixLocations(result, locationCalculator)
}

function simpleOffsetLocationCalculator(
offset: number,
linesAndColumns: LinesAndColumns,
): LocationCalculator {
return {
getFixOffset() {
return offset
},
getLocFromIndex: linesAndColumns.getLocFromIndex.bind(linesAndColumns),
}
}
25 changes: 17 additions & 8 deletions src/script/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
getScriptSetupParserOptions,
} from "../script-setup/parser-options"
import { isScriptSetupElement } from "../common/ast-utils"
import type { LinesAndColumns } from "../common/lines-and-columns"

// [1] = aliases.
// [2] = delimiter.
Expand Down Expand Up @@ -563,13 +564,15 @@ export function parseScript(
/**
* Parse the source code of the given `<script>` element.
* @param node The `<script>` element to parse.
* @param globalLocationCalculator The location calculator for fixLocations.
* @param sfcCode The source code of SFC.
* @param linesAndColumns The lines and columns location calculator.
* @param parserOptions The parser options.
* @returns The result of parsing.
*/
export function parseScriptElement(
node: VElement,
globalLocationCalculator: LocationCalculatorForHtml,
sfcCode: string,
linesAndColumns: LinesAndColumns,
originalParserOptions: ParserOptions,
): ESLintExtendedProgram {
const parserOptions: ParserOptions = isScriptSetupElement(node)
Expand All @@ -580,13 +583,19 @@ export function parseScriptElement(
originalParserOptions.ecmaVersion || DEFAULT_ECMA_VERSION,
}

const text = node.children[0]
const { code, offset } =
text != null && text.type === "VText"
? { code: text.value, offset: text.range[0] }
: { code: "", offset: node.startTag.range[1] }
let code: string
let offset: number
const textNode = node.children[0]
if (textNode != null && textNode.type === "VText") {
const [scriptStartOffset, scriptEndOffset] = textNode.range
code = sfcCode.slice(scriptStartOffset, scriptEndOffset)
offset = scriptStartOffset
} else {
code = ""
offset = node.startTag.range[1]
}
const locationCalculator =
globalLocationCalculator.getSubCalculatorAfter(offset)
linesAndColumns.createOffsetLocationCalculator(offset)
const result = parseScriptFragment(code, locationCalculator, parserOptions)

// Needs the tokens of start/end tags for `lines-around-*` rules to work
Expand Down
56 changes: 56 additions & 0 deletions test/crlf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const assert = require("assert")
const parser = require("../src")

describe("About CRLF tests", () => {
it("should not contain CR in `<script>` contents.", () => {
const parsed = parser.parseForESLint(
`<script>\r
export default {\r
computed: {\r
/**\r
* @description TEST\r
* @param {string} arg - Lorem\r
* @return {string} - Some Description\r
*/\r
isForTestingLint (arg) {\r
return arg;\r
},\r
},\r
};\r
</script>\r
`,
{
sourceType: "module",
},
)
const script = parsed.services
.getDocumentFragment()
.children.find(
(child) => child.type === "VElement" && child.name === "script",
)
assert.ok(!script.children[0].value.includes("\r"))
})
it("should contain CRLF in script comment.", async () => {
const parsed = parser.parseForESLint(
`<script>\r
export default {\r
computed: {\r
/**\r
* @description TEST\r
* @param {string} arg - Lorem\r
* @return {string} - Some Description\r
*/\r
isForTestingLint (arg) {\r
return arg;\r
},\r
},\r
};\r
</script>\r
`,
{
sourceType: "module",
},
)
assert.ok(parsed.ast.comments[0].value.includes("\r\n"))
})
})