| "use strict"; |
| |
| const _ = require("lodash"); |
| const declarationValueIndex = require("../../utils/declarationValueIndex"); |
| const isSingleLineString = require("../../utils/isSingleLineString"); |
| const isStandardSyntaxFunction = require("../../utils/isStandardSyntaxFunction"); |
| const report = require("../../utils/report"); |
| const ruleMessages = require("../../utils/ruleMessages"); |
| const validateOptions = require("../../utils/validateOptions"); |
| const valueParser = require("postcss-value-parser"); |
| |
| const ruleName = "function-parentheses-newline-inside"; |
| |
| const messages = ruleMessages(ruleName, { |
| expectedOpening: 'Expected newline after "("', |
| expectedClosing: 'Expected newline before ")"', |
| expectedOpeningMultiLine: |
| 'Expected newline after "(" in a multi-line function', |
| rejectedOpeningMultiLine: |
| 'Unexpected whitespace after "(" in a multi-line function', |
| expectedClosingMultiLine: |
| 'Expected newline before ")" in a multi-line function', |
| rejectedClosingMultiLine: |
| 'Unexpected whitespace before ")" in a multi-line function' |
| }); |
| |
| const rule = function(expectation, options, context) { |
| return (root, result) => { |
| const validOptions = validateOptions(result, ruleName, { |
| actual: expectation, |
| possible: ["always", "always-multi-line", "never-multi-line"] |
| }); |
| |
| if (!validOptions) { |
| return; |
| } |
| |
| root.walkDecls(decl => { |
| if (decl.value.indexOf("(") === -1) { |
| return; |
| } |
| |
| let hasFixed = false; |
| const declValue = _.get(decl, "raws.value.raw", decl.value); |
| const parsedValue = valueParser(declValue); |
| |
| parsedValue.walk(valueNode => { |
| if (valueNode.type !== "function") { |
| return; |
| } |
| |
| if (!isStandardSyntaxFunction(valueNode)) { |
| return; |
| } |
| |
| const functionString = valueParser.stringify(valueNode); |
| const isMultiLine = !isSingleLineString(functionString); |
| |
| function containsNewline(str) { |
| return str.indexOf("\n") !== -1; |
| } |
| |
| // Check opening ... |
| |
| const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1; |
| const checkBefore = getCheckBefore(valueNode); |
| |
| if (expectation === "always" && !containsNewline(checkBefore)) { |
| if (context.fix) { |
| hasFixed = true; |
| fixBeforeForAlways(valueNode, context.newline); |
| } else { |
| complain(messages.expectedOpening, openingIndex); |
| } |
| } |
| |
| if ( |
| isMultiLine && |
| expectation === "always-multi-line" && |
| !containsNewline(checkBefore) |
| ) { |
| if (context.fix) { |
| hasFixed = true; |
| fixBeforeForAlways(valueNode, context.newline); |
| } else { |
| complain(messages.expectedOpeningMultiLine, openingIndex); |
| } |
| } |
| |
| if ( |
| isMultiLine && |
| expectation === "never-multi-line" && |
| checkBefore !== "" |
| ) { |
| if (context.fix) { |
| hasFixed = true; |
| fixBeforeForNever(valueNode); |
| } else { |
| complain(messages.rejectedOpeningMultiLine, openingIndex); |
| } |
| } |
| |
| // Check closing ... |
| |
| const closingIndex = valueNode.sourceIndex + functionString.length - 2; |
| const checkAfter = getCheckAfter(valueNode); |
| |
| if (expectation === "always" && !containsNewline(checkAfter)) { |
| if (context.fix) { |
| hasFixed = true; |
| fixAfterForAlways(valueNode, context.newline); |
| } else { |
| complain(messages.expectedClosing, closingIndex); |
| } |
| } |
| |
| if ( |
| isMultiLine && |
| expectation === "always-multi-line" && |
| !containsNewline(checkAfter) |
| ) { |
| if (context.fix) { |
| hasFixed = true; |
| fixAfterForAlways(valueNode, context.newline); |
| } else { |
| complain(messages.expectedClosingMultiLine, closingIndex); |
| } |
| } |
| |
| if ( |
| isMultiLine && |
| expectation === "never-multi-line" && |
| checkAfter !== "" |
| ) { |
| if (context.fix) { |
| hasFixed = true; |
| fixAfterForNever(valueNode); |
| } else { |
| complain(messages.rejectedClosingMultiLine, closingIndex); |
| } |
| } |
| }); |
| |
| if (hasFixed) { |
| if (!decl.raws.value) { |
| decl.value = parsedValue.toString(); |
| } else { |
| decl.raws.value.raw = parsedValue.toString(); |
| } |
| } |
| |
| function complain(message, offset) { |
| report({ |
| ruleName, |
| result, |
| message, |
| node: decl, |
| index: declarationValueIndex(decl) + offset |
| }); |
| } |
| }); |
| }; |
| }; |
| |
| function getCheckBefore(valueNode) { |
| let before = valueNode.before; |
| |
| for (const node of valueNode.nodes) { |
| if (node.type === "comment") { |
| continue; |
| } |
| |
| if (node.type === "space") { |
| before += node.value; |
| continue; |
| } |
| |
| break; |
| } |
| |
| return before; |
| } |
| |
| function getCheckAfter(valueNode) { |
| let after = ""; |
| |
| for (const node of valueNode.nodes.slice().reverse()) { |
| if (node.type === "comment") { |
| continue; |
| } |
| |
| if (node.type === "space") { |
| after = node.value + after; |
| continue; |
| } |
| |
| break; |
| } |
| |
| after += valueNode.after; |
| |
| return after; |
| } |
| |
| function fixBeforeForAlways(valueNode, newline) { |
| let target; |
| |
| for (const node of valueNode.nodes) { |
| if (node.type === "comment") { |
| continue; |
| } |
| |
| if (node.type === "space") { |
| target = node; |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (target) { |
| target.value = newline + target.value; |
| } else { |
| valueNode.before = newline + valueNode.before; |
| } |
| } |
| |
| function fixBeforeForNever(valueNode) { |
| valueNode.before = ""; |
| |
| for (const node of valueNode.nodes) { |
| if (node.type === "comment") { |
| continue; |
| } |
| |
| if (node.type === "space") { |
| node.value = ""; |
| continue; |
| } |
| |
| break; |
| } |
| } |
| |
| function fixAfterForAlways(valueNode, newline) { |
| valueNode.after = newline + valueNode.after; |
| } |
| |
| function fixAfterForNever(valueNode) { |
| valueNode.after = ""; |
| |
| for (const node of valueNode.nodes.slice().reverse()) { |
| if (node.type === "comment") { |
| continue; |
| } |
| |
| if (node.type === "space") { |
| node.value = ""; |
| continue; |
| } |
| |
| break; |
| } |
| } |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| module.exports = rule; |