1 | "use strict"
|
2 |
|
3 | const path = require("path")
|
4 | const semver = require("semver")
|
5 | const extract = require("./extract")
|
6 | const oneLine = require("./utils").oneLine
|
7 | const getSettings = require("./settings").getSettings
|
8 |
|
9 | const BOM = "\uFEFF"
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | const needleV3 = path.join("lib", "eslint.js")
|
22 | const needleV4 = path.join("lib", "linter.js")
|
23 |
|
24 | iterateESLintModules(patch)
|
25 |
|
26 | function 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 |
|
40 | function 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 |
|
65 | function iterateESLintModules(fn) {
|
66 | if (!require.cache || Object.keys(require.cache).length === 0) {
|
67 |
|
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 |
|
93 | function 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 |
|
169 | function 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 |
|
183 |
|
184 |
|
185 | column: message.column || 1,
|
186 | })
|
187 |
|
188 |
|
189 | if (location) {
|
190 | Object.assign(message, location)
|
191 | message.source = code.getOriginalLine(location.line)
|
192 |
|
193 |
|
194 | if (message.fix && message.fix.range) {
|
195 | message.fix.range = [
|
196 | code.originalIndex(message.fix.range[0]) + bomOffset,
|
197 |
|
198 |
|
199 | code.originalIndex(message.fix.range[1] - 1) + 1 + bomOffset,
|
200 | ]
|
201 | }
|
202 |
|
203 |
|
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 | }
|