123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- /**
- * @fileoverview Disallow trailing spaces at the end of lines.
- * @author Nodeca Team <https://github.com/nodeca>
- */
- "use strict";
- //------------------------------------------------------------------------------
- // Requirements
- //------------------------------------------------------------------------------
- const astUtils = require("./utils/ast-utils");
- //------------------------------------------------------------------------------
- // Rule Definition
- //------------------------------------------------------------------------------
- /** @type {import('../shared/types').Rule} */
- module.exports = {
- meta: {
- type: "layout",
- docs: {
- description: "Disallow trailing whitespace at the end of lines",
- recommended: false,
- url: "https://eslint.org/docs/rules/no-trailing-spaces"
- },
- fixable: "whitespace",
- schema: [
- {
- type: "object",
- properties: {
- skipBlankLines: {
- type: "boolean",
- default: false
- },
- ignoreComments: {
- type: "boolean",
- default: false
- }
- },
- additionalProperties: false
- }
- ],
- messages: {
- trailingSpace: "Trailing spaces not allowed."
- }
- },
- create(context) {
- const sourceCode = context.getSourceCode();
- const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]",
- SKIP_BLANK = `^${BLANK_CLASS}*$`,
- NONBLANK = `${BLANK_CLASS}+$`;
- const options = context.options[0] || {},
- skipBlankLines = options.skipBlankLines || false,
- ignoreComments = options.ignoreComments || false;
- /**
- * Report the error message
- * @param {ASTNode} node node to report
- * @param {int[]} location range information
- * @param {int[]} fixRange Range based on the whole program
- * @returns {void}
- */
- function report(node, location, fixRange) {
- /*
- * Passing node is a bit dirty, because message data will contain big
- * text in `source`. But... who cares :) ?
- * One more kludge will not make worse the bloody wizardry of this
- * plugin.
- */
- context.report({
- node,
- loc: location,
- messageId: "trailingSpace",
- fix(fixer) {
- return fixer.removeRange(fixRange);
- }
- });
- }
- /**
- * Given a list of comment nodes, return the line numbers for those comments.
- * @param {Array} comments An array of comment nodes.
- * @returns {number[]} An array of line numbers containing comments.
- */
- function getCommentLineNumbers(comments) {
- const lines = new Set();
- comments.forEach(comment => {
- const endLine = comment.type === "Block"
- ? comment.loc.end.line - 1
- : comment.loc.end.line;
- for (let i = comment.loc.start.line; i <= endLine; i++) {
- lines.add(i);
- }
- });
- return lines;
- }
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
- return {
- Program: function checkTrailingSpaces(node) {
- /*
- * Let's hack. Since Espree does not return whitespace nodes,
- * fetch the source code and do matching via regexps.
- */
- const re = new RegExp(NONBLANK, "u"),
- skipMatch = new RegExp(SKIP_BLANK, "u"),
- lines = sourceCode.lines,
- linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher()),
- comments = sourceCode.getAllComments(),
- commentLineNumbers = getCommentLineNumbers(comments);
- let totalLength = 0,
- fixRange = [];
- for (let i = 0, ii = lines.length; i < ii; i++) {
- const lineNumber = i + 1;
- /*
- * Always add linebreak length to line length to accommodate for line break (\n or \r\n)
- * Because during the fix time they also reserve one spot in the array.
- * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF)
- */
- const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1;
- const lineLength = lines[i].length + linebreakLength;
- const matches = re.exec(lines[i]);
- if (matches) {
- const location = {
- start: {
- line: lineNumber,
- column: matches.index
- },
- end: {
- line: lineNumber,
- column: lineLength - linebreakLength
- }
- };
- const rangeStart = totalLength + location.start.column;
- const rangeEnd = totalLength + location.end.column;
- const containingNode = sourceCode.getNodeByRangeIndex(rangeStart);
- if (containingNode && containingNode.type === "TemplateElement" &&
- rangeStart > containingNode.parent.range[0] &&
- rangeEnd < containingNode.parent.range[1]) {
- totalLength += lineLength;
- continue;
- }
- /*
- * If the line has only whitespace, and skipBlankLines
- * is true, don't report it
- */
- if (skipBlankLines && skipMatch.test(lines[i])) {
- totalLength += lineLength;
- continue;
- }
- fixRange = [rangeStart, rangeEnd];
- if (!ignoreComments || !commentLineNumbers.has(lineNumber)) {
- report(node, location, fixRange);
- }
- }
- totalLength += lineLength;
- }
- }
- };
- }
- };
|