This repository has been archived on 2020-11-02. You can view files and clone it, but cannot push or open issues or pull requests.
2020-11-01 22:46:04 +00:00

260 lines
5.4 KiB
JavaScript

// @ts-nocheck
'use strict';
const isOnlyWhitespace = require('../../utils/isOnlyWhitespace');
const optionsMatches = require('../../utils/optionsMatches');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const styleSearch = require('style-search');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'no-eol-whitespace';
const messages = ruleMessages(ruleName, {
rejected: 'Unexpected whitespace at end of line',
});
const whitespacesToReject = new Set([' ', '\t']);
function fixString(str) {
return str.replace(/[ \t]+$/, '');
}
function findErrorStartIndex(
lastEOLIndex,
string,
{ ignoreEmptyLines, isRootFirst } = {
ignoreEmptyLines: false,
isRootFirst: false,
},
) {
const eolWhitespaceIndex = lastEOLIndex - 1;
// If the character before newline is not whitespace, ignore
if (!whitespacesToReject.has(string[eolWhitespaceIndex])) {
return -1;
}
if (ignoreEmptyLines) {
// If there is only whitespace between the previous newline and
// this newline, ignore
const beforeNewlineIndex = string.lastIndexOf('\n', eolWhitespaceIndex);
if (beforeNewlineIndex >= 0 || isRootFirst) {
const line = string.substring(beforeNewlineIndex, eolWhitespaceIndex);
if (isOnlyWhitespace(line)) {
return -1;
}
}
}
return eolWhitespaceIndex;
}
function rule(on, options, context) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: on,
},
{
optional: true,
actual: options,
possible: {
ignore: ['empty-lines'],
},
},
);
if (!validOptions) {
return;
}
const ignoreEmptyLines = optionsMatches(options, 'ignore', 'empty-lines');
if (context.fix) {
fix(root);
}
const rootString = context.fix ? root.toString() : root.source.input.css;
const reportFromIndex = (index) => {
report({
message: messages.rejected,
node: root,
index,
result,
ruleName,
});
};
eachEolWhitespace(rootString, reportFromIndex, true);
const errorIndex = findErrorStartIndex(rootString.length, rootString, {
ignoreEmptyLines,
isRootFirst: true,
});
if (errorIndex > -1) {
reportFromIndex(errorIndex);
}
/**
* Iterate each whitespace at the end of each line of the given string.
* @param {string} string the source code string
* @param {Function} callback callback the whitespace index at the end of each line.
* @param {boolean} isRootFirst set `true` if the given string is the first token of the root.
* @returns {void}
*/
function eachEolWhitespace(string, callback, isRootFirst) {
styleSearch(
{
source: string,
target: ['\n', '\r'],
comments: 'check',
},
(match) => {
const errorIndex = findErrorStartIndex(match.startIndex, string, {
ignoreEmptyLines,
isRootFirst,
});
if (errorIndex > -1) {
callback(errorIndex);
}
},
);
}
function fix(root) {
let isRootFirst = true;
root.walk((node) => {
fixText(
node.raws.before,
(fixed) => {
node.raws.before = fixed;
},
isRootFirst,
);
isRootFirst = false;
// AtRule
fixText(node.raws.afterName, (fixed) => {
node.raws.afterName = fixed;
});
if (node.raws.params) {
fixText(node.raws.params.raw, (fixed) => {
node.raws.params.raw = fixed;
});
} else {
fixText(node.params, (fixed) => {
node.params = fixed;
});
}
// Rule
if (node.raws.selector) {
fixText(node.raws.selector.raw, (fixed) => {
node.raws.selector.raw = fixed;
});
} else {
fixText(node.selector, (fixed) => {
node.selector = fixed;
});
}
// AtRule or Rule or Decl
fixText(node.raws.between, (fixed) => {
node.raws.between = fixed;
});
// Decl
if (node.raws.value) {
fixText(node.raws.value.raw, (fixed) => {
node.raws.value.raw = fixed;
});
} else {
fixText(node.value, (fixed) => {
node.value = fixed;
});
}
// Comment
fixText(node.raws.left, (fixed) => {
node.raws.left = fixed;
});
if (node.raws.inline) {
node.raws.right = fixString(node.raws.right);
} else {
fixText(node.raws.right, (fixed) => {
node.raws.right = fixed;
});
}
fixText(node.text, (fixed) => {
node.text = fixed;
});
fixText(node.raws.after, (fixed) => {
node.raws.after = fixed;
});
});
fixText(
root.raws.after,
(fixed) => {
root.raws.after = fixed;
},
isRootFirst,
);
if (typeof root.raws.after === 'string') {
const lastEOL = Math.max(
root.raws.after.lastIndexOf('\n'),
root.raws.after.lastIndexOf('\r'),
);
if (lastEOL !== root.raws.after.length - 1) {
root.raws.after =
root.raws.after.slice(0, lastEOL + 1) + fixString(root.raws.after.slice(lastEOL + 1));
}
}
}
function fixText(value, fix, isRootFirst) {
if (!value) {
return;
}
let fixed = '';
let lastIndex = 0;
eachEolWhitespace(
value,
(index) => {
const newlineIndex = index + 1;
fixed += fixString(value.slice(lastIndex, newlineIndex));
lastIndex = newlineIndex;
},
isRootFirst,
);
if (lastIndex) {
fixed += value.slice(lastIndex);
fix(fixed);
}
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;