UNPKG

6.58 kBJavaScriptView Raw
1"use strict"
2
3const path = require("path")
4const semver = require("semver")
5const extract = require("./extract")
6const oneLine = require("./utils").oneLine
7const getSettings = require("./settings").getSettings
8
9const BOM = "\uFEFF"
10
11// Disclaimer:
12//
13// This is not a long term viable solution. ESLint needs to improve its processor API to
14// provide access to the configuration before actually preprocess files, but it's not
15// planed yet. This solution is quite ugly but shouldn't alter eslint process.
16//
17// Related github issues:
18// https://github.com/eslint/eslint/issues/3422
19// https://github.com/eslint/eslint/issues/4153
20
21const needleV3 = path.join("lib", "eslint.js")
22const needleV4 = path.join("lib", "linter.js")
23
24iterateESLintModules(patch)
25
26function getModulesFromRequire() {
27 const version = require("eslint/package.json").version
28
29 const eslint = semver.satisfies(version, ">= 4")
30 ? require("eslint/lib/linter").prototype
31 : require("eslint/lib/eslint")
32
33 return {
34 version,
35 eslint,
36 SourceCodeFixer: require("eslint/lib/util/source-code-fixer"),
37 }
38}
39
40function getModulesFromCache(key) {
41 if (!key.endsWith(needleV3) && !key.endsWith(needleV4)) return
42
43 const module = require.cache[key]
44 if (!module || !module.exports) return
45
46 const version = require(path.join(key, "..", "..", "package.json")).version
47
48 const SourceCodeFixer =
49 require.cache[path.join(key, "..", "util", "source-code-fixer.js")]
50
51 if (!SourceCodeFixer || !SourceCodeFixer.exports) return
52
53 const eslint = semver.satisfies(version, ">= 4")
54 ? module.exports.prototype
55 : module.exports
56 if (typeof eslint.verify !== "function") return
57
58 return {
59 version,
60 eslint,
61 SourceCodeFixer: SourceCodeFixer.exports,
62 }
63}
64
65function iterateESLintModules(fn) {
66 if (!require.cache || Object.keys(require.cache).length === 0) {
67 // Jest is replacing the node "require" function, and "require.cache" isn't available here.
68 fn(getModulesFromRequire())
69 return
70 }
71
72 let found = false
73
74 for (const key in require.cache) {
75 const modules = getModulesFromCache(key)
76 if (modules) {
77 fn(modules)
78 found = true
79 }
80 }
81
82 if (!found) {
83 throw new Error(
84 oneLine`
85 eslint-plugin-html error: It seems that eslint is not loaded.
86 If you think it is a bug, please file a report at
87 https://github.com/BenoitZugmeyer/eslint-plugin-html/issues
88 `
89 )
90 }
91}
92
93function patch(modules) {
94 const eslint = modules.eslint
95 const SourceCodeFixer = modules.SourceCodeFixer
96
97 const sourceCodeForMessages = new WeakMap()
98
99 const verify = eslint.verify
100 eslint.verify = function(
101 textOrSourceCode,
102 config,
103 filenameOrOptions,
104 saveState
105 ) {
106 const localVerify = (code) =>
107 verify.call(this, code, config, filenameOrOptions, saveState)
108
109 let messages
110 const filename =
111 typeof filenameOrOptions === "object"
112 ? filenameOrOptions.filename
113 : filenameOrOptions
114 const extension = path.extname(filename || "")
115
116 const pluginSettings = getSettings(config.settings || {})
117 const isHTML = pluginSettings.htmlExtensions.indexOf(extension) >= 0
118 const isXML =
119 !isHTML && pluginSettings.xmlExtensions.indexOf(extension) >= 0
120
121 if (typeof textOrSourceCode === "string" && (isHTML || isXML)) {
122 const currentInfos = extract(
123 textOrSourceCode,
124 pluginSettings.indent,
125 isXML,
126 pluginSettings.isJavaScriptMIMEType
127 )
128
129 messages = []
130
131 currentInfos.code.forEach((code) => {
132 messages.push.apply(
133 messages,
134 remapMessages(
135 localVerify(String(code)),
136 textOrSourceCode.startsWith(BOM),
137 code,
138 pluginSettings.reportBadIndent,
139 currentInfos.badIndentationLines
140 )
141 )
142 })
143
144 sourceCodeForMessages.set(messages, textOrSourceCode)
145 }
146 else {
147 messages = localVerify(textOrSourceCode)
148 }
149
150 return messages
151 }
152
153 const applyFixes = SourceCodeFixer.applyFixes
154 SourceCodeFixer.applyFixes = function(sourceCode, messages, shouldFix) {
155 const originalSourceCode = sourceCodeForMessages.get(messages)
156 if (originalSourceCode) {
157 const hasBOM = originalSourceCode.startsWith(BOM)
158 sourceCode = semver.satisfies(modules.version, ">= 4.6.0")
159 ? originalSourceCode
160 : {
161 text: hasBOM ? originalSourceCode.slice(1) : originalSourceCode,
162 hasBOM,
163 }
164 }
165 return applyFixes.call(this, sourceCode, messages, shouldFix)
166 }
167}
168
169function remapMessages(
170 messages,
171 hasBOM,
172 code,
173 reportBadIndent,
174 badIndentationLines
175) {
176 const newMessages = []
177 const bomOffset = hasBOM ? -1 : 0
178
179 for (const message of messages) {
180 const location = code.originalLocation({
181 line: message.line,
182 // eslint-plugin-eslint-comments is raising message with column=0 to bypass ESLint ignore
183 // comments. Since messages are already ignored at this time, just reset the column to a valid
184 // number. See https://github.com/BenoitZugmeyer/eslint-plugin-html/issues/70
185 column: message.column || 1,
186 })
187
188 // Ignore messages if they were in transformed code
189 if (location) {
190 Object.assign(message, location)
191 message.source = code.getOriginalLine(location.line)
192
193 // Map fix range
194 if (message.fix && message.fix.range) {
195 message.fix.range = [
196 code.originalIndex(message.fix.range[0]) + bomOffset,
197 // The range end is exclusive, meaning it should replace all characters with indexes from
198 // start to end - 1. We have to get the original index of the last targeted character.
199 code.originalIndex(message.fix.range[1] - 1) + 1 + bomOffset,
200 ]
201 }
202
203 // Map end location
204 if (message.endLine && message.endColumn) {
205 const endLocation = code.originalLocation({
206 line: message.endLine,
207 column: message.endColumn,
208 })
209 if (endLocation) {
210 message.endLine = endLocation.line
211 message.endColumn = endLocation.column
212 }
213 }
214
215 newMessages.push(message)
216 }
217 }
218
219 if (reportBadIndent) {
220 badIndentationLines.forEach((line) => {
221 newMessages.push({
222 message: "Bad line indentation.",
223 line,
224 column: 1,
225 ruleId: "(html plugin)",
226 severity: reportBadIndent === true ? 2 : reportBadIndent,
227 })
228 })
229 }
230
231 newMessages.sort((ma, mb) => {
232 return ma.line - mb.line || ma.column - mb.column
233 })
234
235 return newMessages
236}