From 7a8dc8206562b87594660f04479059737a779a07 Mon Sep 17 00:00:00 2001 From: Evilebot Tnawi Date: Tue, 22 May 2018 21:10:24 +0300 Subject: [PATCH] refactor: printer (#4501) --- src/language-css/printer-postcss.js | 308 +++++++++++++--------------- src/language-css/utils.js | 94 ++++++++- 2 files changed, 230 insertions(+), 172 deletions(-) diff --git a/src/language-css/printer-postcss.js b/src/language-css/printer-postcss.js index 92c024f0..1989adff 100644 --- a/src/language-css/printer-postcss.js +++ b/src/language-css/printer-postcss.js @@ -39,19 +39,30 @@ const isSCSSControlDirectiveNode = utils.isSCSSControlDirectiveNode; const isDetachedRulesetDeclarationNode = utils.isDetachedRulesetDeclarationNode; const isRelationalOperatorNode = utils.isRelationalOperatorNode; const isEqualityOperatorNode = utils.isEqualityOperatorNode; +const isMultiplicationNode = utils.isMultiplicationNode; +const isDivisionNode = utils.isDivisionNode; +const isAdditionNode = utils.isAdditionNode; const isMathOperatorNode = utils.isMathOperatorNode; const isEachKeywordNode = utils.isEachKeywordNode; const isParenGroupNode = utils.isParenGroupNode; const isForKeywordNode = utils.isForKeywordNode; const isURLFunctionNode = utils.isURLFunctionNode; const isIfElseKeywordNode = utils.isIfElseKeywordNode; -const hasLessExtendValueNode = utils.hasLessExtendValueNode; -const hasComposesValueNode = utils.hasComposesValueNode; -const hasParensAroundValueNode = utils.hasParensAroundValueNode; +const hasLessExtendNode = utils.hasLessExtendNode; +const hasComposesNode = utils.hasComposesNode; +const hasParensAroundNode = utils.hasParensAroundNode; +const hasEmptyRawBefore = utils.hasEmptyRawBefore; const isKeyValuePairNode = utils.isKeyValuePairNode; const isDetachedRulesetCallNode = utils.isDetachedRulesetCallNode; const isPostcssSimpleVarNode = utils.isPostcssSimpleVarNode; const isSCSSMapItemNode = utils.isSCSSMapItemNode; +const isInlineValueCommentNode = utils.isInlineValueCommentNode; +const isHashNode = utils.isHashNode; +const isLeftCurlyBraceNode = utils.isLeftCurlyBraceNode; +const isRightCurlyBraceNode = utils.isRightCurlyBraceNode; +const isWordNode = utils.isWordNode; +const isColonNode = utils.isColonNode; +const isMediaAndSupportsKeywords = utils.isMediaAndSupportsKeywords; function shouldPrintComma(options) { switch (options.trailingComma) { @@ -135,8 +146,8 @@ function genericPrint(path, options, print) { // it will put a space after `:` and break it. Ideally we should parse // less files with less, but we can hardcode this to work with scss as // well. - hasLessExtendValueNode(node) ? "" : " ", - hasComposesValueNode(node) + hasLessExtendNode(node) ? "" : " ", + hasComposesNode(node) ? removeLines(path.call(print, "value")) : path.call(print, "value"), node.raws.important @@ -190,7 +201,7 @@ function genericPrint(path, options, print) { " ", path.call(print, "value"), isSCSSControlDirectiveNode(node) - ? hasParensAroundValueNode(node) + ? hasParensAroundNode(node) ? " " : line : "" @@ -268,25 +279,26 @@ function genericPrint(path, options, print) { } // postcss-selector-parser case "selector-root": { - const atRuleAncestorNode = getAncestorNode(path, "css-atrule"); - const insideAtRuleNode = - atRuleAncestorNode && - ["extend", "custom-selector", "nest"].indexOf( - atRuleAncestorNode.name - ) !== -1; - return group( concat([ - atRuleAncestorNode && atRuleAncestorNode.name === "custom-selector" - ? concat([atRuleAncestorNode.customSelector, line]) + insideAtRuleNode(path, "custom-selector") + ? concat([getAncestorNode(path, "css-atrule").customSelector, line]) : "", join( - concat([",", insideAtRuleNode ? line : hardline]), + concat([ + ",", + insideAtRuleNode(path, ["extend", "custom-selector", "nest"]) + ? line + : hardline + ]), path.map(print, "nodes") ) ]) ); } + case "selector-selector": { + return group(indent(concat(path.map(print, "nodes")))); + } case "selector-comment": { return node.value; } @@ -349,11 +361,14 @@ function genericPrint(path, options, print) { parentNode.nodes[0] === node ? "" : line; + return concat([leading, node.value, isLastNode(path, node) ? "" : " "]); } + const leading = node.value.trim().startsWith("(") ? line : ""; const value = adjustNumbers(adjustStrings(node.value.trim(), options)) || line; + return concat([leading, value]); } case "selector-universal": { @@ -364,9 +379,6 @@ function genericPrint(path, options, print) { adjustNumbers(node.value) ]); } - case "selector-selector": { - return group(indent(concat(path.map(print, "nodes")))); - } case "selector-pseudo": { return concat([ maybeToLowerCase(node.value), @@ -391,6 +403,7 @@ function genericPrint(path, options, print) { return node.value; } // postcss-values-parser + case "value-value": case "value-root": { return path.call(print, "group"); } @@ -458,8 +471,8 @@ function genericPrint(path, options, print) { continue; } - // Ignore colon - if (iNode.value === ":") { + // Ignore colon (i.e. `:`) + if (isColonNode(iNode) || isColonNode(iNextNode)) { continue; } @@ -483,166 +496,142 @@ function genericPrint(path, options, print) { continue; } + // Ignore spaces after `#` and after `{` and before `}` in SCSS interpolation (i.e. `#{variable}`) if ( - (iPrevNode && - iPrevNode.type === "value-comment" && - iPrevNode.inline) || - (iNextNode.type === "value-comment" && iNextNode.inline) - ) { - continue; - } - - const isHash = iNode.type === "value-word" && iNode.value === "#"; - const isLeftCurlyBrace = - iNode.type === "value-word" && iNode.value === "{"; - const isNextLeftCurlyBrace = - iNextNode.type === "value-word" && iNextNode.value === "{"; - const isRightCurlyBrace = - iNode.type === "value-word" && iNode.value === "}"; - const isNextRightCurlyBrace = - iNextNode.type === "value-word" && iNextNode.value === "}"; - - // Ignore interpolation in SCSS (i.e. `#{variable}`) - if ( - isHash || - isLeftCurlyBrace || - isNextRightCurlyBrace || - (isNextLeftCurlyBrace && - iNextNode.raws && - iNextNode.raws.before === "") || - (isRightCurlyBrace && iNextNode.raws && iNextNode.raws.before === "") + isHashNode(iNode) || + isLeftCurlyBraceNode(iNode) || + isRightCurlyBraceNode(iNextNode) || + (isLeftCurlyBraceNode(iNextNode) && hasEmptyRawBefore(iNextNode)) || + (isRightCurlyBraceNode(iNode) && hasEmptyRawBefore(iNextNode)) ) { continue; } // Ignore css variables and interpolation in SCSS (i.e. `--#{$var}`) - if (iNode.value === "--" && iNextNode.value === "#") { + if (iNode.value === "--" && isHashNode(iNextNode)) { continue; } - const isNextHash = - iNextNode.type === "value-word" && iNextNode.value === "#"; - const isMathOperator = isMathOperatorNode(iNode); const isNextMathOperator = isMathOperatorNode(iNextNode); - const isMultiplication = - !isNextHash && isMathOperator && iNode.value === "*"; - const isNextMultiplication = - !isRightCurlyBrace && isNextMathOperator && iNextNode.value === "*"; + // Formating math operations + if (isMathOperator || isNextMathOperator) { + const isMultiplication = + !isHashNode(iNextNode) && isMultiplicationNode(iNode); + const isNextMultiplication = + !isRightCurlyBraceNode(iNode) && isMultiplicationNode(iNextNode); + const isDivision = !isHashNode(iNextNode) && isDivisionNode(iNode); + const isNextDivision = + !isRightCurlyBraceNode(iNode) && isDivisionNode(iNextNode); + const isAddition = !isHashNode(iNextNode) && isAdditionNode(iNode); + const isNextAddition = + !isRightCurlyBraceNode(iNode) && isAdditionNode(iNextNode); - const isDivision = !isNextHash && isMathOperator && iNode.value === "/"; - const isNextDivision = - !isRightCurlyBrace && isNextMathOperator && iNextNode.value === "/"; - - const isAddition = !isNextHash && isMathOperator && iNode.value === "+"; - const isNextAddition = - !isRightCurlyBrace && isNextMathOperator && iNextNode.value === "+"; - - const isPrevFunction = iPrevNode && iPrevNode.type === "value-func"; - const isFunction = iNode.type === "value-func"; - const isNextFunction = iNextNode.type === "value-func"; - const isNextNextFunction = - iNextNextNode && iNextNextNode.type === "value-func"; - - const isPrevWord = - iPrevNode && - ["value-word", "value-atword"].indexOf(iPrevNode.type) !== -1; - const isWord = - ["value-word", "value-atword"].indexOf(iNode.type) !== -1; - const isNextWord = - ["value-word", "value-atword"].indexOf(iNextNode.type) !== -1; - const isNextNextWord = - iNextNextNode && - ["value-word", "value-atword"].indexOf(iNextNextNode.type) !== -1; - - // Math operators - const insideCalcFunction = insideValueFunctionNode(path, "calc"); - - const hasSpaceBeforeOperator = - isNextNextFunction || isNextNextWord || isFunction || isWord; - - const hasSpaceAfterOperator = - isNextFunction || isNextWord || isPrevFunction || isPrevWord; - - if ( - (isMathOperator || isNextMathOperator) && - // Multiplication - !isMultiplication && - !isNextMultiplication && - // Division - !(isNextDivision && (hasSpaceBeforeOperator || insideCalcFunction)) && - !(isDivision && (hasSpaceAfterOperator || insideCalcFunction)) && - // Addition - !(isNextAddition && hasSpaceBeforeOperator) && - !(isAddition && hasSpaceAfterOperator) - ) { - const isNextParenGroup = isParenGroupNode(iNextNode); - const isNextValueNumber = iNextNode.type === "value-number"; + const hasSpaceBeforeOperator = + (iNextNextNode && iNextNextNode.type === "value-func") || + (iNextNextNode && isWordNode(iNextNextNode)) || + iNode.type === "value-func" || + isWordNode(iNode); + const hasSpaceAfterOperator = + iNextNode.type === "value-func" || + isWordNode(iNextNode) || + (iPrevNode && iPrevNode.type === "value-func") || + (iPrevNode && isWordNode(iPrevNode)); + const insideCalcFunction = insideValueFunctionNode(path, "calc"); if ( - (iNextNode.raws && iNextNode.raws.before === "") || - (isMathOperator && - (isNextParenGroup || - isNextWord || - isNextValueNumber || - isMathOperatorNode(iNextNode)) && - (!iPrevNode || (iPrevNode && isMathOperatorNode(iPrevNode)))) + // Multiplication + !isMultiplication && + !isNextMultiplication && + // Division + !( + isNextDivision && + (hasSpaceBeforeOperator || insideCalcFunction) + ) && + !(isDivision && (hasSpaceAfterOperator || insideCalcFunction)) && + // Addition + !(isNextAddition && hasSpaceBeforeOperator) && + !(isAddition && hasSpaceAfterOperator) ) { - continue; + if ( + hasEmptyRawBefore(iNextNode) || + (isMathOperator && + (isParenGroupNode(iNextNode) || + isWordNode(iNextNode) || + iNextNode.type === "value-number" || + isMathOperatorNode(iNextNode)) && + (!iPrevNode || (iPrevNode && isMathOperatorNode(iPrevNode)))) + ) { + continue; + } } } - const isEqualityOperator = - isControlDirective && isEqualityOperatorNode(iNode); - const isRelationalOperator = - isControlDirective && isRelationalOperatorNode(iNode); - const isNextEqualityOperator = - isControlDirective && isEqualityOperatorNode(iNextNode); - const isNextRelationalOperator = - isControlDirective && isRelationalOperatorNode(iNextNode); - const isNextIfElseKeyword = - isControlDirective && isIfElseKeywordNode(iNextNode); - const isEachKeyword = isControlDirective && isEachKeywordNode(iNode); - const isNextEachKeyword = - isControlDirective && isEachKeywordNode(iNextNode); - const isForKeyword = - atRuleAncestorNode && - atRuleAncestorNode.name === "for" && - isForKeywordNode(iNode); - const isNextForKeyword = - isControlDirective && isForKeywordNode(iNextNode); - const IsNextColon = iNextNode.value === ":"; + // Ignore inline comment, they already contain newline at end (i.e. `// Comment`) + // Add `hardline` after inline comment (i.e. `// comment\n foo: bar;`) + const isInlineComment = isInlineValueCommentNode(iNode); + if ( + (iPrevNode && isInlineValueCommentNode(iPrevNode)) || + isInlineComment || + isInlineValueCommentNode(iNextNode) + ) { + if (isInlineComment) { + parts.push(hardline); + } + + continue; + } + + // Handle keywords in SCSS control directive + if ( + isControlDirective && + (isEqualityOperatorNode(iNextNode) || + isRelationalOperatorNode(iNextNode) || + isIfElseKeywordNode(iNextNode) || + isEachKeywordNode(iNode) || + isForKeywordNode(iNode)) + ) { + parts.push(" "); + + continue; + } + + // At-rule `namespace` should be in one line + if ( + atRuleAncestorNode && + atRuleAncestorNode.name.toLowerCase() === "namespace" + ) { + parts.push(" "); + + continue; + } + + // Formatting `grid` property if (isGridValue) { if (iNode.source.start.line !== iNextNode.source.start.line) { parts.push(hardline); + didBreak = true; } else { parts.push(" "); } - } else if (iNode.type === "value-comment" && iNode.inline) { - parts.push(hardline); - } else if ( - isNextMathOperator || - isNextEqualityOperator || - isNextRelationalOperator || - isNextIfElseKeyword || - isForKeyword || - isEachKeyword || - (atRuleAncestorNode && - atRuleAncestorNode.name.toLowerCase() === "namespace") - ) { - parts.push(" "); - } else if ( - !IsNextColon || - isEqualityOperator || - isRelationalOperator || - isNextForKeyword || - isNextEachKeyword - ) { - parts.push(line); + + continue; } + + // Add `space` before next math operation + // Note: `grip` property have `/` delimiter and it is not math operation, so + // `grid` property handles above + if (isNextMathOperator) { + parts.push(" "); + + continue; + } + + // Be default all values go through `line` + parts.push(line); } if (didBreak) { @@ -693,6 +682,7 @@ function genericPrint(path, options, print) { } res.push(printed[i]); } + return group(indent(fill(res))); } @@ -745,17 +735,12 @@ function genericPrint(path, options, print) { } ); } - case "value-value": { - return path.call(print, "group"); - } case "value-func": { - const insideAtRuleSupportsNode = insideAtRuleNode(path, "supports"); - const isKeyword = - ["not", "and", "or"].indexOf(node.value.toLowerCase()) !== -1; - return concat([ node.value, - insideAtRuleSupportsNode && isKeyword ? " " : "", + insideAtRuleNode(path, "supports") && isMediaAndSupportsKeywords(node) + ? " " + : "", path.call(print, "group") ]); } @@ -778,6 +763,7 @@ function genericPrint(path, options, print) { case "value-colon": { return concat([ node.value, + // Don't add spaces on `:` in `url` function (i.e. `url(fbglyph: cross-outline, fig-white)`) insideValueFunctionNode(path, "url") ? "" : line ]); } diff --git a/src/language-css/utils.js b/src/language-css/utils.js index 29304c19..73ef6796 100644 --- a/src/language-css/utils.js +++ b/src/language-css/utils.js @@ -90,11 +90,13 @@ function insideICSSRuleNode(path) { ); } -function insideAtRuleNode(path, atRuleName) { +function insideAtRuleNode(path, atRuleNameOrAtRuleNames) { + const atRuleNames = [].concat(atRuleNameOrAtRuleNames); const atRuleAncestorNode = getAncestorNode(path, "css-atrule"); return ( - atRuleAncestorNode && atRuleAncestorNode.name.toLowerCase() === atRuleName + atRuleAncestorNode && + atRuleNames.indexOf(atRuleAncestorNode.name.toLowerCase()) !== -1 ); } @@ -163,10 +165,33 @@ function isEachKeywordNode(node) { return node.type === "value-word" && node.value === "in"; } +function isMultiplicationNode(node) { + return node.type === "value-operator" && node.value === "*"; +} + +function isDivisionNode(node) { + return node.type === "value-operator" && node.value === "/"; +} + +function isAdditionNode(node) { + return node.type === "value-operator" && node.value === "+"; +} + +function isSubtractionNode(node) { + return node.type === "value-operator" && node.value === "-"; +} + +function isModuloNode(node) { + return node.type === "value-operator" && node.value === "%"; +} + function isMathOperatorNode(node) { return ( - node.type === "value-operator" && - ["+", "-", "/", "*", "%"].indexOf(node.value) !== -1 + isMultiplicationNode(node) || + isDivisionNode(node) || + isAdditionNode(node) || + isSubtractionNode(node) || + isModuloNode(node) ); } @@ -214,7 +239,7 @@ function isPostcssSimpleVarNode(currentNode, nextNode) { ); } -function hasLessExtendValueNode(node) { +function hasLessExtendNode(node) { return ( node.value && node.value.type === "value-root" && @@ -226,7 +251,7 @@ function hasLessExtendValueNode(node) { ); } -function hasComposesValueNode(node) { +function hasComposesNode(node) { return ( node.value && node.value.type === "value-root" && @@ -236,7 +261,7 @@ function hasComposesValueNode(node) { ); } -function hasParensAroundValueNode(node) { +function hasParensAroundNode(node) { return ( node.value && node.value.group && @@ -247,6 +272,10 @@ function hasParensAroundValueNode(node) { ); } +function hasEmptyRawBefore(node) { + return node.raws && node.raws.before === ""; +} + function isKeyValuePairNode(node) { return ( node.type === "value-comma_group" && @@ -303,6 +332,36 @@ function isSCSSMapItemNode(path) { return false; } +function isInlineValueCommentNode(node) { + return node.type === "value-comment" && node.inline; +} + +function isHashNode(node) { + return node.type === "value-word" && node.value === "#"; +} + +function isLeftCurlyBraceNode(node) { + return node.type === "value-word" && node.value === "{"; +} + +function isRightCurlyBraceNode(node) { + return node.type === "value-word" && node.value === "}"; +} + +function isWordNode(node) { + return ["value-word", "value-atword"].indexOf(node.type) !== -1; +} + +function isColonNode(node) { + return node.type === "value-colon"; +} + +function isMediaAndSupportsKeywords(node) { + return ( + node.value && ["not", "and", "or"].indexOf(node.value.toLowerCase()) !== -1 + ); +} + module.exports = { getAncestorCounter, getAncestorNode, @@ -321,19 +380,32 @@ module.exports = { isDetachedRulesetDeclarationNode, isRelationalOperatorNode, isEqualityOperatorNode, + isMultiplicationNode, + isDivisionNode, + isAdditionNode, + isSubtractionNode, + isModuloNode, isMathOperatorNode, isEachKeywordNode, isParenGroupNode, isForKeywordNode, isURLFunctionNode, isIfElseKeywordNode, - hasLessExtendValueNode, - hasComposesValueNode, - hasParensAroundValueNode, + hasLessExtendNode, + hasComposesNode, + hasParensAroundNode, + hasEmptyRawBefore, isSCSSNestedPropertyNode, isDetachedRulesetCallNode, isPostcssSimpleVarNode, isKeyValuePairNode, isKeyValuePairInParenGroupNode, - isSCSSMapItemNode + isSCSSMapItemNode, + isInlineValueCommentNode, + isHashNode, + isLeftCurlyBraceNode, + isRightCurlyBraceNode, + isWordNode, + isColonNode, + isMediaAndSupportsKeywords };