Skip to content

Commit 7576255

Browse files
committed
Fix: the problem of lines-around-directive (fixes #2)
- `lines-around-directive ` compares lines between `"use strict"` token and the previous token. If the previous token is nothing, it checks the line of `"use strict"` token. - This parser parses `<script>` element, so the 1st line is not `line === 1`. - To solve this problem, I added the `<script>` tag tokens. As the result, `lines-around-directive` rule gets always comparing between `"use strict"` token and the previous token.
1 parent 30834a3 commit 7576255

File tree

4 files changed

+164
-11
lines changed

4 files changed

+164
-11
lines changed

index.js

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,99 @@ const SAXParser = require("parse5").SAXParser
1717
// Helpers
1818
//------------------------------------------------------------------------------
1919

20+
const LINE_TERMINATORS = /\r\n|\r|\n|\u2028|\u2029/g
21+
22+
/**
23+
* Calculates the end location.
24+
*
25+
* @param {string} raw - The text of the target token.
26+
* @param {number} startLine - The start line of the target token.
27+
* @param {number} startColumn - The start column of the target token.
28+
* @returns {{line: number, column: number}} The end location.
29+
* @private
30+
*/
31+
function calcLocEnd(raw, startLine, startColumn) {
32+
const lines = raw.split(LINE_TERMINATORS)
33+
const line = startLine + lines.length - 1
34+
const column = (lines.length === 1)
35+
? startColumn + raw.length
36+
: lines[lines.length - 1].length
37+
38+
return {line, column}
39+
}
40+
41+
/**
42+
* Creates the token with the given parameters.
43+
*
44+
* @param {string} value - The token value to create.
45+
* @param {string} text - The whole text.
46+
* @param {object} location - The location object of `parse5` module.
47+
* @returns {object} The created token object.
48+
* @private
49+
*/
50+
function createToken(value, text, location) {
51+
const type = "Punctuator"
52+
const start = location.startOffset
53+
const end = location.endOffset
54+
const line = location.line
55+
const column = location.col - 1
56+
const range = [start, end]
57+
const raw = text.slice(start, end)
58+
const loc = {
59+
start: {line, column},
60+
end: calcLocEnd(raw, line, column),
61+
}
62+
63+
return {type, value, raw, start, end, range, loc}
64+
}
65+
2066
/**
2167
* Extracts the text of the 1st script element in the given text.
2268
*
2369
* @param {string} originalText - The whole text to extract.
2470
* @returns {{text: string, offset: number}} The information of the 1st script.
71+
* @private
2572
*/
2673
function extractFirstScript(originalText) {
2774
const parser = new SAXParser({locationInfo: true})
28-
let inScript = false
75+
let inTemplate = 0
76+
let startToken = null
77+
let endToken = null
2978
let text = ""
3079
let offset = 0
3180

32-
parser.on("startTag", (name, attrs, selfClosing) => {
33-
if (name === "script" && !selfClosing) {
34-
inScript = true
81+
parser.on("startTag", (name, attrs, selfClosing, location) => {
82+
if (selfClosing) {
83+
return
84+
}
85+
if (name === "template") {
86+
inTemplate += 1
87+
}
88+
if (inTemplate === 0 && name === "script") {
89+
startToken = createToken("<script>", originalText, location)
3590
}
3691
})
37-
parser.on("endTag", (name) => {
38-
if (name === "script") {
39-
inScript = false
92+
parser.on("endTag", (name, location) => {
93+
if (inTemplate > 0 && name === "template") {
94+
inTemplate -= 1
95+
}
96+
if (startToken != null && name === "script") {
97+
endToken = createToken("</script>", originalText, location)
98+
parser.stop()
4099
}
41100
})
42101
parser.on("text", (scriptText, location) => {
43-
if (inScript && text === "") {
44-
const lineTerminators = "\n".repeat(location.line - 1)
45-
const spaces = " ".repeat(location.startOffset - location.line + 1)
102+
if (startToken != null) {
103+
const countLines = location.line - 1
104+
const lineTerminators = "\n".repeat(countLines)
105+
const spaces = " ".repeat(location.startOffset - countLines)
46106
text = `${spaces}${lineTerminators}${scriptText}`
47107
offset = location.startOffset
48108
}
49109
})
50110
parser.end(originalText)
51111

52-
return {text, offset}
112+
return {startToken, endToken, text, offset}
53113
}
54114

55115
//------------------------------------------------------------------------------
@@ -77,6 +137,12 @@ module.exports.parse = function parse(text, options) {
77137
const ast = espree.parse(script.text, options)
78138

79139
ast.start = script.offset
140+
if (script.startToken) {
141+
ast.tokens.unshift(script.startToken)
142+
}
143+
if (script.endToken) {
144+
ast.tokens.push(script.endToken)
145+
}
80146

81147
return ast
82148
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<p>{{ greeting }} World!</p>
3+
</template>
4+
5+
<script>
6+
7+
"use strict"
8+
</script>
9+
10+
<style scoped>
11+
p {
12+
font-size: 2em;
13+
text-align: center;
14+
}
15+
</style>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<template>
2+
<p>{{ greeting }} World!</p>
3+
</template>
4+
5+
<script>
6+
"use strict"
7+
</script>
8+
9+
<style scoped>
10+
p {
11+
font-size: 2em;
12+
text-align: center;
13+
}
14+
</style>

test/index.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,61 @@ describe("About fixtures/notvue.js", () => {
117117
assert(actual === expected)
118118
})
119119
})
120+
121+
describe("About fixtures/lines-around-directive.vue", () => {
122+
beforeEach(() => {
123+
fs.copySync(ORIGINAL_FIXTURE_DIR, FIXTURE_DIR)
124+
})
125+
afterEach(() => {
126+
fs.removeSync(FIXTURE_DIR)
127+
})
128+
129+
it("should notify no 'lines-around-directive' error (never)", () => {
130+
const cli = new CLIEngine({
131+
cwd: FIXTURE_DIR,
132+
envs: ["es6", "node"],
133+
parser: PARSER_PATH,
134+
rules: {"lines-around-directive": ["error", "never"]},
135+
useEslintrc: false,
136+
})
137+
const report = cli.executeOnFiles(["lines-around-directive.vue"])
138+
const messages = report.results[0].messages
139+
140+
assert(messages.length === 0)
141+
})
142+
143+
it("should notify a 'lines-around-directive' error (always)", () => {
144+
const cli = new CLIEngine({
145+
cwd: FIXTURE_DIR,
146+
envs: ["es6", "node"],
147+
parser: PARSER_PATH,
148+
rules: {"lines-around-directive": ["error", "always"]},
149+
useEslintrc: false,
150+
})
151+
const report = cli.executeOnFiles(["lines-around-directive.vue"])
152+
const messages = report.results[0].messages
153+
154+
assert(messages.length === 1)
155+
assert(messages[0].ruleId === "lines-around-directive")
156+
assert(messages[0].line === 6)
157+
assert(messages[0].column === 1)
158+
assert(messages[0].source === "\"use strict\"")
159+
})
160+
161+
it("should fix 'lines-around-directive' errors with --fix option", () => {
162+
const cli = new CLIEngine({
163+
cwd: FIXTURE_DIR,
164+
envs: ["es6", "node"],
165+
fix: true,
166+
parser: PARSER_PATH,
167+
rules: {"lines-around-directive": ["error", "always"]},
168+
useEslintrc: false,
169+
})
170+
CLIEngine.outputFixes(cli.executeOnFiles(["lines-around-directive.vue"]))
171+
172+
const actual = fs.readFileSync(path.join(FIXTURE_DIR, "lines-around-directive.vue"), "utf8")
173+
const expected = fs.readFileSync(path.join(FIXTURE_DIR, "lines-around-directive-always.vue.fixed"), "utf8")
174+
175+
assert(actual === expected)
176+
})
177+
})

0 commit comments

Comments
 (0)