| "use strict"; |
| |
| const _ = require("lodash"); |
| const atRuleParamIndex = require("../../utils/atRuleParamIndex"); |
| const declarationValueIndex = require("../../utils/declarationValueIndex"); |
| const isWhitespace = require("../../utils/isWhitespace"); |
| const report = require("../../utils/report"); |
| const ruleMessages = require("../../utils/ruleMessages"); |
| const styleSearch = require("style-search"); |
| const validateOptions = require("../../utils/validateOptions"); |
| |
| const ruleName = "function-whitespace-after"; |
| |
| const messages = ruleMessages(ruleName, { |
| expected: 'Expected whitespace after ")"', |
| rejected: 'Unexpected whitespace after ")"' |
| }); |
| |
| const ACCEPTABLE_AFTER_CLOSING_PAREN = new Set([ |
| ")", |
| ",", |
| "}", |
| ":", |
| "/", |
| undefined |
| ]); |
| |
| const rule = function(expectation, options, context) { |
| return (root, result) => { |
| const validOptions = validateOptions(result, ruleName, { |
| actual: expectation, |
| possible: ["always", "never"] |
| }); |
| |
| if (!validOptions) { |
| return; |
| } |
| |
| function check(node, value, getIndex, fix) { |
| styleSearch( |
| { |
| source: value, |
| target: ")", |
| functionArguments: "only" |
| }, |
| match => { |
| checkClosingParen(value, match.startIndex + 1, node, getIndex, fix); |
| } |
| ); |
| } |
| |
| function checkClosingParen(source, index, node, getIndex, fix) { |
| const nextChar = source[index]; |
| |
| if (expectation === "always") { |
| // Allow for the next character to be a single empty space, |
| // another closing parenthesis, a comma, or the end of the value |
| if (nextChar === " ") { |
| return; |
| } |
| |
| if (nextChar === "\n") { |
| return; |
| } |
| |
| if (source.substr(index, 2) === "\r\n") { |
| return; |
| } |
| |
| if (ACCEPTABLE_AFTER_CLOSING_PAREN.has(nextChar)) { |
| return; |
| } |
| |
| if (fix) { |
| fix(index); |
| |
| return; |
| } |
| |
| report({ |
| message: messages.expected, |
| node, |
| index: getIndex(node) + index, |
| result, |
| ruleName |
| }); |
| } else if (expectation === "never") { |
| if (isWhitespace(nextChar)) { |
| if (fix) { |
| fix(index); |
| |
| return; |
| } |
| |
| report({ |
| message: messages.rejected, |
| node, |
| index: getIndex(node) + index, |
| result, |
| ruleName |
| }); |
| } |
| } |
| } |
| |
| function createFixer(value) { |
| let fixed = ""; |
| let lastIndex = 0; |
| let applyFix; |
| |
| if (expectation === "always") { |
| applyFix = index => { |
| fixed += value.slice(lastIndex, index) + " "; |
| lastIndex = index; |
| }; |
| } else if (expectation === "never") { |
| applyFix = index => { |
| let whitespaceEndIndex = index + 1; |
| |
| while ( |
| whitespaceEndIndex < value.length && |
| isWhitespace(value[whitespaceEndIndex]) |
| ) { |
| whitespaceEndIndex++; |
| } |
| fixed += value.slice(lastIndex, index); |
| lastIndex = whitespaceEndIndex; |
| }; |
| } |
| |
| return { |
| applyFix, |
| get hasFixed() { |
| return !!lastIndex; |
| }, |
| get fixed() { |
| return fixed + value.slice(lastIndex); |
| } |
| }; |
| } |
| |
| root.walkAtRules(/^import$/i, atRule => { |
| const param = _.get(atRule, "raws.params.raw", atRule.params); |
| const fixer = context.fix && createFixer(param); |
| |
| check(atRule, param, atRuleParamIndex, fixer && fixer.applyFix); |
| |
| if (fixer && fixer.hasFixed) { |
| if (atRule.raws.params) { |
| atRule.raws.params.raw = fixer.fixed; |
| } else { |
| atRule.params = fixer.fixed; |
| } |
| } |
| }); |
| root.walkDecls(decl => { |
| const value = _.get(decl, "raws.value.raw", decl.value); |
| const fixer = context.fix && createFixer(value); |
| |
| check(decl, value, declarationValueIndex, fixer && fixer.applyFix); |
| |
| if (fixer && fixer.hasFixed) { |
| if (decl.raws.value) { |
| decl.raws.value.raw = fixer.fixed; |
| } else { |
| decl.value = fixer.fixed; |
| } |
| } |
| }); |
| }; |
| }; |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| module.exports = rule; |