"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;