@html-eslint/eslint-plugin
Version:
ESLint plugin for HTML
150 lines (143 loc) • 4.17 kB
JavaScript
/**
* @import {
* CommentContent,
* Text
* } from "@html-eslint/types"
* @import {RuleModule} from "../types"
*/
const { parseTemplateLiteral } = require("./utils/template-literal");
const { RULE_CATEGORY } = require("../constants");
const {
getTemplateTokens,
codeToLines,
isRangesOverlap,
} = require("./utils/node");
const {
shouldCheckTaggedTemplateExpression,
shouldCheckTemplateLiteral,
} = require("./utils/settings");
const { getSourceCode } = require("./utils/source-code");
const { getRuleUrl } = require("./utils/rule");
const MESSAGE_IDS = {
TRAILING_SPACE: "trailingSpace",
};
/** @type {RuleModule<[]>} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "Disallow trailing whitespace at the end of lines",
recommended: false,
category: RULE_CATEGORY.STYLE,
url: getRuleUrl("no-trailing-spaces"),
},
fixable: true,
schema: [],
messages: {
[MESSAGE_IDS.TRAILING_SPACE]: "Trailing spaces not allowed",
},
},
create(context) {
const sourceCode = getSourceCode(context);
/**
* @param {string} source
* @param {string[]} lines
* @param {Object} offset
* @param {number} offset.range
* @param {number} offset.line
* @param {number} offset.column
* @param {(CommentContent | Text)["parts"][number][]} tokens
*/
function check(source, lines, offset, tokens) {
const lineBreaks = source.match(/\r\n|[\r\n\u2028\u2029]/gu);
lines.forEach((line, index) => {
const lineNumber = index + offset.line;
const match = line.match(/[ \t\u00a0\u2000-\u200b\u3000]+$/);
const lineBreakLength =
lineBreaks && lineBreaks[index] ? lineBreaks[index].length : 1;
const lineLength = line.length + lineBreakLength;
const columnOffset = index === 0 ? offset.column : 0;
if (match) {
if (typeof match.index === "number" && match.index > 0) {
const loc = {
start: {
line: lineNumber,
column: match.index + columnOffset,
},
end: {
line: lineNumber,
column: lineLength - lineBreakLength + columnOffset,
},
};
const start = sourceCode.getIndexFromLoc(loc.start);
const end = sourceCode.getIndexFromLoc(loc.end);
if (
tokens.some((token) => isRangesOverlap(token.range, [start, end]))
) {
return;
}
context.report({
messageId: MESSAGE_IDS.TRAILING_SPACE,
loc,
fix(fixer) {
return fixer.removeRange([start, end]);
},
});
}
}
});
}
return {
Document() {
check(
sourceCode.getText(),
sourceCode.getLines(),
{
range: 0,
line: 1,
column: 0,
},
[]
);
},
TaggedTemplateExpression(node) {
if (shouldCheckTaggedTemplateExpression(node, context)) {
const { html, tokens } = parseTemplateLiteral(
node.quasi,
getSourceCode(context)
);
const lines = codeToLines(html);
check(
html,
lines,
{
range: node.quasi.range[0] + 1,
line: node.quasi.loc.start.line,
column: node.quasi.loc.start.column + 1,
},
getTemplateTokens(tokens)
);
}
},
TemplateLiteral(node) {
if (shouldCheckTemplateLiteral(node, context)) {
const { html, tokens } = parseTemplateLiteral(
node,
getSourceCode(context)
);
const lines = codeToLines(html);
check(
html,
lines,
{
range: node.range[0] + 1,
line: node.loc.start.line,
column: node.loc.start.column + 1,
},
getTemplateTokens(tokens)
);
}
},
};
},
};