eslint-plugin-html
Version:
A ESLint plugin to lint and fix inline scripts contained in HTML files.
173 lines (151 loc) • 4.74 kB
JavaScript
const getSettings = require("./settings").getSettings
const getFileMode = require("./getFileMode")
const extract = require("./extract")
const { verifyWithSharedScopes } = require("./verifyWithSharedScopes")
const { remapMessages } = require("./remapMessages")
const pluginReference = require("./pluginReference")
const PREPARE_RULE_NAME = "__eslint-plugin-html-prepare"
const PREPARE_PLUGIN_NAME = "__eslint-plugin-html-prepare"
module.exports = { createVerifyWithFlatConfigPatch }
function createVerifyWithFlatConfigPatch(eslintModule, verifyWithFlatConfig) {
return function (textOrSourceCode, providedConfig, providedOptions) {
const callOriginalVerify = () =>
verifyWithFlatConfig.call(
this,
textOrSourceCode,
providedConfig,
providedOptions
)
if (!Object.values(providedConfig.plugins).includes(pluginReference)) {
return callOriginalVerify()
}
const pluginSettings = getSettings(providedConfig.settings || {})
const mode = getFileMode(pluginSettings, providedOptions.filename)
if (!mode) {
return callOriginalVerify()
}
let messages
;[messages, providedConfig] = verifyExternalHtmlPlugin(
eslintModule,
providedConfig,
callOriginalVerify
)
const extractResult = extract(
textOrSourceCode,
mode === "xml",
pluginSettings
)
if (pluginSettings.reportBadIndent) {
messages.push(
...extractResult.badIndentationLines.map((line) => ({
message: "Bad line indentation.",
line,
column: 1,
ruleId: "(html plugin)",
severity: pluginSettings.reportBadIndent,
}))
)
}
// Save code parts parsed source code so we don't have to parse it twice
const sourceCodes = new WeakMap()
const verifyCodePart = (codePart, { prepare, ignoreRules } = {}) => {
providedConfig.plugins[PREPARE_PLUGIN_NAME] = {
rules: {
[PREPARE_RULE_NAME]: {
create(context) {
sourceCodes.set(codePart, context.getSourceCode())
return {
Program(program) {
if (prepare) {
prepare(context, program)
}
},
}
},
},
},
}
const localMessages = verifyWithFlatConfig.call(
this,
sourceCodes.get(codePart) || String(codePart),
{
...providedConfig,
rules: Object.assign(
{ [`${PREPARE_PLUGIN_NAME}/${PREPARE_RULE_NAME}`]: "error" },
!ignoreRules && providedConfig.rules
),
},
ignoreRules
? {
...providedOptions,
reportUnusedDisableDirectives: false,
}
: providedOptions
)
messages.push(
...remapMessages(localMessages, extractResult.hasBOM, codePart)
)
}
const languageOptions = providedConfig.languageOptions || {}
const parserOptions = languageOptions.parserOptions || {}
const sourceType = languageOptions.sourceType || parserOptions.sourceType
if (sourceType === "module") {
for (const codePart of extractResult.code) {
verifyCodePart(codePart)
}
} else {
verifyWithSharedScopes(extractResult.code, verifyCodePart, parserOptions)
}
messages.sort((ma, mb) => ma.line - mb.line || ma.column - mb.column)
return messages
}
}
const externalHtmlPlugins = [
{ parser: "@html-eslint/parser", plugin: "@html-eslint/eslint-plugin" },
]
function tryRequire(name) {
try {
return require(name)
} catch {
return undefined
}
}
function findExternalHtmlPluginName(config) {
if (!config.languageOptions || !config.languageOptions.parser) {
return
}
for (const { parser, plugin } of externalHtmlPlugins) {
let parserModule = tryRequire(parser)
if (config.languageOptions.parser === parserModule) {
const pluginModule = tryRequire(plugin)
for (const [name, plugin] of Object.entries(config.plugins)) {
if (plugin === pluginModule) {
return name
}
}
}
}
}
function verifyExternalHtmlPlugin(eslintModule, config, callOriginalVerify) {
const htmlPluginName = findExternalHtmlPluginName(config)
if (!htmlPluginName) {
return [[], config]
}
const rules = {}
for (const name in config.rules) {
if (!name.startsWith(htmlPluginName + "/")) {
rules[name] = config.rules[name]
}
}
return [
callOriginalVerify(),
{
...config,
languageOptions: {
...config.languageOptions,
parser: eslintModule.require("espree"),
},
rules,
},
]
}