@yusufkandemir/eslint-plugin-lodash-template
Version:
ESLint plugin for John Resig-style micro template, Lodash's template, Underscore's template and EJS.
167 lines (153 loc) • 4.87 kB
JavaScript
const utils = require("../utils");
const { getSourceCode } = require("eslint-compat-utils");
/**
* get the value info.
* @param {Token} valueToken The value token.
* @returns {object} the value info.
*/
function calcValueInfo(valueToken) {
const text = valueToken.htmlValue;
const firstChar = text[0];
const quote =
firstChar === "'" || firstChar === '"' ? firstChar : undefined;
const content = quote ? text.slice(1, -1) : text;
return {
quote,
content,
};
}
const QUOTE_CHAR_TESTS = {
double(valueInfo) {
return valueInfo.quote === '"';
},
single(valueInfo) {
return valueInfo.quote === "'";
},
either(valueInfo) {
return valueInfo.quote === '"' || valueInfo.quote === "'";
},
"prefer-double"(valueInfo) {
if (valueInfo.content.indexOf('"') < 0) {
return valueInfo.quote === '"';
}
return valueInfo.quote === "'";
},
};
const GET_FIX_QUOTE_CHARS = {
double() {
return '"';
},
single() {
return "'";
},
either() {
return '"';
},
"prefer-double"(valueInfo) {
if (valueInfo.content.indexOf('"') < 0) {
return '"';
}
return "'";
},
};
const GET_MESSAGE_ID = {
double() {
return "expectedDoubleQuotes";
},
single() {
return "expectedSingleQuotes";
},
either() {
return "expectedQuotes";
},
"prefer-double"(valueInfo) {
if (valueInfo.content.indexOf('"') < 0) {
return "expectedDoubleQuotes";
}
return "expectedSingleQuotes";
},
};
module.exports = {
meta: {
docs: {
description:
"enforce quotes style of HTML attributes. (ex. :ok: `<div class=\"abc\">` :ng: `<div class='abc'>` `<div class=abc>`)",
category: "recommended-with-html",
url: "https://ota-meshi.github.io/eslint-plugin-lodash-template/rules/attribute-value-quote.html",
},
fixable: "code",
messages: {
expectedDoubleQuotes: "Expected to be enclosed by double quotes.",
expectedSingleQuotes: "Expected to be enclosed by single quotes.",
expectedQuotes: "Expected to be enclosed by quotes.",
},
schema: [
{
enum: ["double", "single", "either", "prefer-double"],
},
],
type: "suggestion",
},
create(context) {
const sourceCode = getSourceCode(context);
if (!sourceCode.parserServices.getMicroTemplateService) {
return {};
}
if (!utils.isHtmlFile(context.getFilename())) {
return {};
}
const microTemplateService =
sourceCode.parserServices.getMicroTemplateService();
const option = context.options[0] || "prefer-double";
const quoteCharTest = QUOTE_CHAR_TESTS[option];
/**
* Define the function which fixes the problem.
* @param {Token} valueToken The value token.
* @param {object} valueInfo The value info.
* @returns {function} The defined function.
*/
function defineFix(valueToken, valueInfo) {
const text = valueToken.htmlValue;
if (text !== valueToken.value) {
// can not fix
return undefined;
}
const quoteChar = GET_FIX_QUOTE_CHARS[option](valueInfo);
const quotePattern = quoteChar === '"' ? /"/gu : /'/gu;
const quoteEscaped = quoteChar === '"' ? """ : "'";
return (fixer) => {
const replacement =
quoteChar +
valueInfo.content.replace(quotePattern, quoteEscaped) +
quoteChar;
return fixer.replaceText(valueToken, replacement);
};
}
/** Process for HTMLAttribute */
function processHTMLAttribute(node) {
const valueToken = node.valueToken;
if (!valueToken) {
return;
}
const valueInfo = calcValueInfo(valueToken);
if (!quoteCharTest(valueInfo)) {
context.report({
node: valueToken,
messageId: GET_MESSAGE_ID[option](valueInfo),
fix: defineFix(valueToken, valueInfo),
});
}
}
return {
"Program:exit"() {
microTemplateService.traverseDocumentNodes({
HTMLStartTag(node) {
node.ignoredAttributes.forEach(processHTMLAttribute);
},
HTMLAttribute: processHTMLAttribute,
});
},
};
},
};
;