diff options
Diffstat (limited to 'assets/node_modules/stylelint/lib/rules/no-eol-whitespace/index.js')
-rw-r--r-- | assets/node_modules/stylelint/lib/rules/no-eol-whitespace/index.js | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/assets/node_modules/stylelint/lib/rules/no-eol-whitespace/index.js b/assets/node_modules/stylelint/lib/rules/no-eol-whitespace/index.js new file mode 100644 index 0000000..1d90430 --- /dev/null +++ b/assets/node_modules/stylelint/lib/rules/no-eol-whitespace/index.js @@ -0,0 +1,257 @@ +'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; |