UNPKG

prettierx

Version:

prettierX - a less opinionated fork of the Prettier code formatter

1,118 lines (1,023 loc) 33.4 kB
"use strict"; const getLast = require("../utils/get-last"); const { printNumber, printString, hasNewline, isFrontMatterNode, isNextLineEmpty, isNonEmptyArray, } = require("../common/util"); const { builders: { join, line, hardline, softline, group, fill, indent, dedent, ifBreak, breakParent, }, utils: { removeLines, getDocParts }, } = require("../document"); const clean = require("./clean"); const embed = require("./embed"); const { insertPragma } = require("./pragma"); const { getAncestorNode, getPropOfDeclNode, maybeToLowerCase, insideValueFunctionNode, insideICSSRuleNode, insideAtRuleNode, insideURLFunctionInImportAtRuleNode, isKeyframeAtRuleKeywords, isWideKeywords, isLastNode, isSCSSControlDirectiveNode, isDetachedRulesetDeclarationNode, isRelationalOperatorNode, isEqualityOperatorNode, isMultiplicationNode, isDivisionNode, isAdditionNode, isSubtractionNode, isMathOperatorNode, isEachKeywordNode, isForKeywordNode, isURLFunctionNode, isIfElseKeywordNode, hasComposesNode, hasParensAroundNode, hasEmptyRawBefore, isKeyValuePairNode, isKeyInValuePairNode, isDetachedRulesetCallNode, isTemplatePlaceholderNode, isTemplatePropNode, isPostcssSimpleVarNode, isSCSSMapItemNode, isInlineValueCommentNode, isHashNode, isLeftCurlyBraceNode, isRightCurlyBraceNode, isWordNode, isColonNode, isMediaAndSupportsKeywords, isColorAdjusterFuncNode, lastLineHasInlineComment, isAtWordPlaceholderNode, } = require("./utils"); const { locStart, locEnd } = require("./loc"); function shouldPrintComma(options) { return options.trailingComma === "es5" || options.trailingComma === "all"; } function genericPrint(path, options, print) { const node = path.getValue(); /* istanbul ignore if */ if (!node) { return ""; } if (typeof node === "string") { return node; } switch (node.type) { case "front-matter": return [node.raw, hardline]; case "css-root": { const nodes = printNodeSequence(path, options, print); const after = node.raws.after.trim(); return [ nodes, after ? ` ${after}` : "", getDocParts(nodes).length > 0 ? hardline : "", ]; } case "css-comment": { const isInlineComment = node.inline || node.raws.inline; const text = options.originalText.slice(locStart(node), locEnd(node)); return isInlineComment ? text.trimEnd() : text; } case "css-rule": { return [ print("selector"), node.important ? " !important" : "", node.nodes ? [ node.selector && node.selector.type === "selector-unknown" && lastLineHasInlineComment(node.selector.value) ? line : node.selector ? " " : "", "{", node.nodes.length > 0 ? indent([hardline, printNodeSequence(path, options, print)]) : "", hardline, "}", isDetachedRulesetDeclarationNode(node) ? ";" : "", ] : ";", ]; } case "css-decl": { const parentNode = path.getParentNode(); const { between: rawBetween } = node.raws; const trimmedBetween = rawBetween.trim(); const isColon = trimmedBetween === ":"; const isValueAllSpace = typeof node.value === "string" && /^ *$/.test(node.value); let value = hasComposesNode(node) ? removeLines(print("value")) : print("value"); if (!isColon && lastLineHasInlineComment(trimmedBetween)) { value = indent([hardline, dedent(value)]); } return [ node.raws.before.replace(/[\s;]/g, ""), insideICSSRuleNode(path) ? node.prop : maybeToLowerCase(node.prop), trimmedBetween.startsWith("//") ? " " : "", trimmedBetween, node.extend || isValueAllSpace ? "" : " ", options.parser === "less" && node.extend && node.selector ? ["extend(", print("selector"), ")"] : "", value, node.raws.important ? node.raws.important.replace(/\s*!\s*important/i, " !important") : node.important ? " !important" : "", node.raws.scssDefault ? node.raws.scssDefault.replace(/\s*!default/i, " !default") : node.scssDefault ? " !default" : "", node.raws.scssGlobal ? node.raws.scssGlobal.replace(/\s*!global/i, " !global") : node.scssGlobal ? " !global" : "", node.nodes ? [ " {", indent([softline, printNodeSequence(path, options, print)]), softline, "}", ] : isTemplatePropNode(node) && !parentNode.raws.semicolon && options.originalText[locEnd(node) - 1] !== ";" ? "" : options.__isHTMLStyleAttribute && isLastNode(path, node) ? ifBreak(";") : ";", ]; } case "css-atrule": { const parentNode = path.getParentNode(); const isTemplatePlaceholderNodeWithoutSemiColon = isTemplatePlaceholderNode(node) && !parentNode.raws.semicolon && options.originalText[locEnd(node) - 1] !== ";"; if (options.parser === "less") { if (node.mixin) { return [ print("selector"), node.important ? " !important" : "", isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";", ]; } if (node.function) { return [ node.name, print("params"), isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";", ]; } if (node.variable) { return [ "@", node.name, ": ", node.value ? print("value") : "", node.raws.between.trim() ? node.raws.between.trim() + " " : "", node.nodes ? [ "{", indent([ node.nodes.length > 0 ? softline : "", printNodeSequence(path, options, print), ]), softline, "}", ] : "", isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";", ]; } } const isImportUnknownValueEndsWithSemiColon = node.name === "import" && node.params && node.params.type === "value-unknown" && node.params.value.endsWith(";"); return [ "@", // If a Less file ends up being parsed with the SCSS parser, Less // variable declarations will be parsed as at-rules with names ending // with a colon, so keep the original case then. isDetachedRulesetCallNode(node) || node.name.endsWith(":") ? node.name : maybeToLowerCase(node.name), node.params ? [ isDetachedRulesetCallNode(node) ? "" : isTemplatePlaceholderNode(node) ? node.raws.afterName === "" ? "" : node.name.endsWith(":") ? " " : /^\s*\n\s*\n/.test(node.raws.afterName) ? [hardline, hardline] : /^\s*\n/.test(node.raws.afterName) ? hardline : " " : " ", print("params"), ] : "", node.selector ? indent([" ", print("selector")]) : "", node.value ? group( // [prettierx merge update from prettier@2.3.2 ...] [ " ", path.call(print, "value"), isSCSSControlDirectiveNode(node, options) ? hasParensAroundNode(node) ? " " : line : "", ] ) : node.name === "else" ? " " : "", node.nodes ? [ isSCSSControlDirectiveNode(node, options) ? "" : (node.selector && !node.selector.nodes && typeof node.selector.value === "string" && lastLineHasInlineComment(node.selector.value)) || (!node.selector && typeof node.params === "string" && lastLineHasInlineComment(node.params)) ? line : " ", "{", indent([ node.nodes.length > 0 ? softline : "", printNodeSequence(path, options, print), ]), softline, "}", ] : isTemplatePlaceholderNodeWithoutSemiColon || isImportUnknownValueEndsWithSemiColon ? "" : ";", ]; } // postcss-media-query-parser case "media-query-list": { const parts = []; path.each((childPath) => { const node = childPath.getValue(); if (node.type === "media-query" && node.value === "") { return; } parts.push(print()); }, "nodes"); return group(indent(join(line, parts))); } case "media-query": { return [ join(" ", path.map(print, "nodes")), isLastNode(path, node) ? "" : ",", ]; } case "media-type": { return adjustNumbers(adjustStrings(node.value, options)); } case "media-feature-expression": { // prettierx: cssParenSpacing option support (...) const parenSpace = options.cssParenSpacing ? " " : ""; if (!node.nodes) { return node.value; } // [prettierx merge update from prettier@2.3.2] cssParenSpacing option support (...) return [ // [prettierx merge update from prettier@2.3.2] (...) "(", parenSpace, ...path.map(print, "nodes"), parenSpace, ")", ]; } case "media-feature": { return maybeToLowerCase( adjustStrings(node.value.replace(/ +/g, " "), options) ); } case "media-colon": { return [node.value, " "]; } case "media-value": { return adjustNumbers(adjustStrings(node.value, options)); } case "media-keyword": { return adjustStrings(node.value, options); } case "media-url": { return adjustStrings( node.value.replace(/^url\(\s+/gi, "url(").replace(/\s+\)$/g, ")"), options ); } case "media-unknown": { return node.value; } // postcss-selector-parser case "selector-root": { return group([ insideAtRuleNode(path, "custom-selector") ? [getAncestorNode(path, "css-atrule").customSelector, line] : "", join( [ ",", insideAtRuleNode(path, ["extend", "custom-selector", "nest"]) ? line : hardline, ], path.map(print, "nodes") ), ]); } case "selector-selector": { return group(indent(path.map(print, "nodes"))); } case "selector-comment": { return node.value; } case "selector-string": { return adjustStrings(node.value, options); } case "selector-tag": { const parentNode = path.getParentNode(); const index = parentNode && parentNode.nodes.indexOf(node); const prevNode = index && parentNode.nodes[index - 1]; return [ node.namespace ? [node.namespace === true ? "" : node.namespace.trim(), "|"] : "", prevNode.type === "selector-nesting" ? node.value : adjustNumbers( isKeyframeAtRuleKeywords(path, node.value) ? node.value.toLowerCase() : node.value ), ]; } case "selector-id": { return ["#", node.value]; } case "selector-class": { return [".", adjustNumbers(adjustStrings(node.value, options))]; } case "selector-attribute": { return [ "[", node.namespace ? [node.namespace === true ? "" : node.namespace.trim(), "|"] : "", node.attribute.trim(), node.operator ? node.operator : "", node.value ? quoteAttributeValue( adjustStrings(node.value.trim(), options), options ) : "", node.insensitive ? " i" : "", "]", ]; } case "selector-combinator": { if ( node.value === "+" || node.value === ">" || node.value === "~" || node.value === ">>>" ) { const parentNode = path.getParentNode(); const leading = parentNode.type === "selector-selector" && parentNode.nodes[0] === node ? "" : line; return [leading, node.value, isLastNode(path, node) ? "" : " "]; } const leading = node.value.trim().startsWith("(") ? line : ""; const value = adjustNumbers(adjustStrings(node.value.trim(), options)) || line; return [leading, value]; } case "selector-universal": { return [ node.namespace ? [node.namespace === true ? "" : node.namespace.trim(), "|"] : "", node.value, ]; } case "selector-pseudo": { // prettierx: cssParenSpacing option support (...) const parenSpace = options.cssParenSpacing ? " " : ""; return [ maybeToLowerCase(node.value), // [prettierx merge update from prettier@2.3.2 ...] isNonEmptyArray(node.nodes) ? [ // prettierx: cssParenSpacing option support (...) "(", parenSpace, join(", ", path.map(print, "nodes")), parenSpace, ")", ] : "", ]; } case "selector-nesting": { return node.value; } case "selector-unknown": { const ruleAncestorNode = getAncestorNode(path, "css-rule"); // Nested SCSS property if (ruleAncestorNode && ruleAncestorNode.isSCSSNesterProperty) { return adjustNumbers( adjustStrings(maybeToLowerCase(node.value), options) ); } // originalText has to be used for Less, see replaceQuotesInInlineComments in loc.js const parentNode = path.getParentNode(); if (parentNode.raws && parentNode.raws.selector) { const start = locStart(parentNode); const end = start + parentNode.raws.selector.length; return options.originalText.slice(start, end).trim(); } // Same reason above const grandParent = path.getParentNode(1); if ( parentNode.type === "value-paren_group" && grandParent && grandParent.type === "value-func" && grandParent.value === "selector" ) { const start = locStart(parentNode.open) + 1; const end = locEnd(parentNode.close) - 1; const selector = options.originalText.slice(start, end).trim(); return lastLineHasInlineComment(selector) ? [breakParent, selector] : selector; } return node.value; } // postcss-values-parser case "value-value": case "value-root": { return print("group"); } case "value-comment": { return options.originalText.slice(locStart(node), locEnd(node)); } case "value-comma_group": { const parentNode = path.getParentNode(); const parentParentNode = path.getParentNode(1); const declAncestorProp = getPropOfDeclNode(path); const isGridValue = declAncestorProp && parentNode.type === "value-value" && (declAncestorProp === "grid" || declAncestorProp.startsWith("grid-template")); const atRuleAncestorNode = getAncestorNode(path, "css-atrule"); const isControlDirective = atRuleAncestorNode && isSCSSControlDirectiveNode(atRuleAncestorNode, options); const hasInlineComment = node.groups.some((node) => isInlineValueCommentNode(node) ); const printed = path.map(print, "groups"); const parts = []; const insideURLFunction = insideValueFunctionNode(path, "url"); let insideSCSSInterpolationInString = false; let didBreak = false; for (let i = 0; i < node.groups.length; ++i) { parts.push(printed[i]); const iPrevNode = node.groups[i - 1]; const iNode = node.groups[i]; const iNextNode = node.groups[i + 1]; const iNextNextNode = node.groups[i + 2]; if (insideURLFunction) { if ( (iNextNode && isAdditionNode(iNextNode)) || isAdditionNode(iNode) ) { parts.push(" "); } continue; } // Ignore after latest node (i.e. before semicolon) if (!iNextNode) { continue; } // styled.div` background: var(--${one}); ` if ( iNode.type === "value-word" && iNode.value.endsWith("-") && isAtWordPlaceholderNode(iNextNode) ) { continue; } // Ignore spaces before/after string interpolation (i.e. `"#{my-fn("_")}"`) const isStartSCSSInterpolationInString = iNode.type === "value-string" && iNode.value.startsWith("#{"); const isEndingSCSSInterpolationInString = insideSCSSInterpolationInString && iNextNode.type === "value-string" && iNextNode.value.endsWith("}"); if ( isStartSCSSInterpolationInString || isEndingSCSSInterpolationInString ) { insideSCSSInterpolationInString = !insideSCSSInterpolationInString; continue; } if (insideSCSSInterpolationInString) { continue; } // Ignore colon (i.e. `:`) if (isColonNode(iNode) || isColonNode(iNextNode)) { continue; } // Ignore `@` in Less (i.e. `@@var;`) if (iNode.type === "value-atword" && iNode.value === "") { continue; } // Ignore `~` in Less (i.e. `content: ~"^//* some horrible but needed css hack";`) if (iNode.value === "~") { continue; } // Ignore escape `\` if ( iNode.value && iNode.value.includes("\\") && iNextNode && iNextNode.type !== "value-comment" ) { continue; } // Ignore escaped `/` if ( iPrevNode && iPrevNode.value && iPrevNode.value.indexOf("\\") === iPrevNode.value.length - 1 && iNode.type === "value-operator" && iNode.value === "/" ) { continue; } // Ignore `\` (i.e. `$variable: \@small;`) if (iNode.value === "\\") { continue; } // Ignore `$$` (i.e. `background-color: $$(style)Color;`) if (isPostcssSimpleVarNode(iNode, iNextNode)) { continue; } // Ignore spaces after `#` and after `{` and before `}` in SCSS interpolation (i.e. `#{variable}`) if ( 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 === "--" && isHashNode(iNextNode)) { continue; } // Formatting math operations const isMathOperator = isMathOperatorNode(iNode); const isNextMathOperator = isMathOperatorNode(iNextNode); // Print spaces before and after math operators beside SCSS interpolation as is // (i.e. `#{$var}+5`, `#{$var} +5`, `#{$var}+ 5`, `#{$var} + 5`) // (i.e. `5+#{$var}`, `5 +#{$var}`, `5+ #{$var}`, `5 + #{$var}`) if ( ((isMathOperator && isHashNode(iNextNode)) || (isNextMathOperator && isRightCurlyBraceNode(iNode))) && hasEmptyRawBefore(iNextNode) ) { continue; } // absolute paths are only parsed as one token if they are part of url(/abs/path) call // but if you have custom -fb-url(/abs/path/) then it is parsed as "division /" and rest // of the path. We don't want to put a space after that first division in this case. if (!iPrevNode && isDivisionNode(iNode)) { continue; } // Print spaces before and after addition and subtraction math operators as is in `calc` function // due to the fact that it is not valid syntax // (i.e. `calc(1px+1px)`, `calc(1px+ 1px)`, `calc(1px +1px)`, `calc(1px + 1px)`) if ( insideValueFunctionNode(path, "calc") && (isAdditionNode(iNode) || isAdditionNode(iNextNode) || isSubtractionNode(iNode) || isSubtractionNode(iNextNode)) && hasEmptyRawBefore(iNextNode) ) { continue; } // Print spaces after `+` and `-` in color adjuster functions as is (e.g. `color(red l(+ 20%))`) // Adjusters with signed numbers (e.g. `color(red l(+20%))`) output as-is. const isColorAdjusterNode = (isAdditionNode(iNode) || isSubtractionNode(iNode)) && i === 0 && (iNextNode.type === "value-number" || iNextNode.isHex) && parentParentNode && isColorAdjusterFuncNode(parentParentNode) && !hasEmptyRawBefore(iNextNode); const requireSpaceBeforeOperator = (iNextNextNode && iNextNextNode.type === "value-func") || (iNextNextNode && isWordNode(iNextNextNode)) || iNode.type === "value-func" || isWordNode(iNode); const requireSpaceAfterOperator = iNextNode.type === "value-func" || isWordNode(iNextNode) || (iPrevNode && iPrevNode.type === "value-func") || (iPrevNode && isWordNode(iPrevNode)); // Formatting `/`, `+`, `-` sign if ( !(isMultiplicationNode(iNextNode) || isMultiplicationNode(iNode)) && !insideValueFunctionNode(path, "calc") && !isColorAdjusterNode && ((isDivisionNode(iNextNode) && !requireSpaceBeforeOperator) || (isDivisionNode(iNode) && !requireSpaceAfterOperator) || (isAdditionNode(iNextNode) && !requireSpaceBeforeOperator) || (isAdditionNode(iNode) && !requireSpaceAfterOperator) || isSubtractionNode(iNextNode) || isSubtractionNode(iNode)) && (hasEmptyRawBefore(iNextNode) || (isMathOperator && (!iPrevNode || (iPrevNode && isMathOperatorNode(iPrevNode))))) ) { continue; } // Add `hardline` after inline comment (i.e. `// comment\n foo: bar;`) if (isInlineValueCommentNode(iNode)) { if (parentNode.type === "value-paren_group") { parts.push(dedent(hardline)); continue; } 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 && iNextNode.source && iNode.source.start.line !== iNextNode.source.start.line ) { parts.push(hardline); didBreak = true; } else { parts.push(" "); } 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; } // allow function(returns-list($list)...) if (iNextNode && iNextNode.value === "...") { continue; } if ( isAtWordPlaceholderNode(iNode) && isAtWordPlaceholderNode(iNextNode) && locEnd(iNode) === locStart(iNextNode) ) { continue; } // Be default all values go through `line` parts.push(line); } if (hasInlineComment) { parts.push(breakParent); } if (didBreak) { parts.unshift(hardline); } if (isControlDirective) { return group(indent(parts)); } // Indent is not needed for import url when url is very long // and node has two groups // when type is value-comma_group // example @import url("verylongurl") projection,tv if (insideURLFunctionInImportAtRuleNode(path)) { return group(fill(parts)); } return group(indent(fill(parts))); } case "value-paren_group": { const parentNode = path.getParentNode(); // prettierx: cssParenSpacing option support (...) const parenSpace = options.cssParenSpacing ? " " : ""; const parenLine = options.cssParenSpacing ? line : softline; if ( parentNode && isURLFunctionNode(parentNode) && (node.groups.length === 1 || (node.groups.length > 0 && node.groups[0].type === "value-comma_group" && node.groups[0].groups.length > 0 && node.groups[0].groups[0].type === "value-word" && node.groups[0].groups[0].value.startsWith("data:"))) ) { return [ // [prettierx merge update from prettier@2.3.2] cssParenSpacing option support (...) ...(node.open ? [print("open"), parenSpace] : [""]), join(",", path.map(print, "groups")), ...(node.close ? [parenSpace, print("close")] : [""]), ]; } if (!node.open) { const printed = path.map(print, "groups"); const res = []; for (let i = 0; i < printed.length; i++) { if (i !== 0) { res.push([",", line]); } res.push(printed[i]); } return group(indent(fill(res))); } // prettierx: cssParenSpacing option support (...) if (node.groups.length === 0) { // [prettierx merge update from prettier@2.3.2 ...] return group([ node.open ? path.call(print, "open") : "", node.close ? path.call(print, "close") : "", ]); } const isSCSSMapItem = isSCSSMapItemNode(path, options); const lastItem = getLast(node.groups); const isLastItemComment = lastItem && lastItem.type === "value-comment"; const isKey = isKeyInValuePairNode(node, parentNode); const printed = group( [ node.open ? print("open") : "", indent([ // [prettierx merge update from prettier@2.3.2] cssParenSpacing option support (...) parenLine, join( [",", line], path.map((childPath) => { const node = childPath.getValue(); const printed = print(); // Key/Value pair in open paren already indented if ( isKeyValuePairNode(node) && node.type === "value-comma_group" && node.groups && node.groups[0].type !== "value-paren_group" && node.groups[2] && node.groups[2].type === "value-paren_group" ) { const parts = getDocParts(printed.contents.contents); parts[1] = group(parts[1]); return group(dedent(printed)); } return printed; }, "groups") ), ]), ifBreak( !isLastItemComment && options.parser === "scss" && isSCSSMapItem && shouldPrintComma(options) ? "," : "" ), // prettierx: cssParenSpacing option support (...) parenLine, node.close ? print("close") : "", ], { shouldBreak: isSCSSMapItem && !isKey, } ); return isKey ? dedent(printed) : printed; } case "value-func": { return [ node.value, insideAtRuleNode(path, "supports") && isMediaAndSupportsKeywords(node) ? " " : "", print("group"), ]; } case "value-paren": { return node.value; } case "value-number": { return [printCssNumber(node.value), maybeToLowerCase(node.unit)]; } case "value-operator": { return node.value; } case "value-word": { if ((node.isColor && node.isHex) || isWideKeywords(node.value)) { return node.value.toLowerCase(); } return node.value; } case "value-colon": { const parentNode = path.getParentNode(); const index = parentNode && parentNode.groups.indexOf(node); const prevNode = index && parentNode.groups[index - 1]; return [ node.value, // Don't add spaces on escaped colon `:`, e.g: grid-template-rows: [row-1-00\:00] auto; (prevNode && typeof prevNode.value === "string" && getLast(prevNode.value) === "\\") || // Don't add spaces on `:` in `url` function (i.e. `url(fbglyph: cross-outline, fig-white)`) insideValueFunctionNode(path, "url") ? "" : line, ]; } // TODO: confirm this code is dead /* istanbul ignore next */ case "value-comma": { return [node.value, " "]; } case "value-string": { return printString( node.raws.quote + node.value + node.raws.quote, options ); } case "value-atword": { return ["@", node.value]; } case "value-unicode-range": { return node.value; } case "value-unknown": { return node.value; } default: /* istanbul ignore next */ throw new Error(`Unknown postcss type ${JSON.stringify(node.type)}`); } } function printNodeSequence(path, options, print) { const parts = []; path.each((pathChild, i, nodes) => { const prevNode = nodes[i - 1]; if ( prevNode && prevNode.type === "css-comment" && prevNode.text.trim() === "prettier-ignore" ) { const childNode = pathChild.getValue(); parts.push( options.originalText.slice(locStart(childNode), locEnd(childNode)) ); } else { parts.push(print()); } if (i !== nodes.length - 1) { if ( (nodes[i + 1].type === "css-comment" && !hasNewline(options.originalText, locStart(nodes[i + 1]), { backwards: true, }) && !isFrontMatterNode(nodes[i])) || (nodes[i + 1].type === "css-atrule" && nodes[i + 1].name === "else" && nodes[i].type !== "css-comment") ) { parts.push(" "); } else { parts.push(options.__isHTMLStyleAttribute ? line : hardline); if ( isNextLineEmpty(options.originalText, pathChild.getValue(), locEnd) && !isFrontMatterNode(nodes[i]) ) { parts.push(hardline); } } } }, "nodes"); return parts; } const STRING_REGEX = /(["'])(?:(?!\1)[^\\]|\\.)*\1/gs; const NUMBER_REGEX = /(?:\d*\.\d+|\d+\.?)(?:[Ee][+-]?\d+)?/g; const STANDARD_UNIT_REGEX = /[A-Za-z]+/g; const WORD_PART_REGEX = /[$@]?[A-Z_a-z\u0080-\uFFFF][\w\u0080-\uFFFF-]*/g; const ADJUST_NUMBERS_REGEX = new RegExp( STRING_REGEX.source + "|" + `(${WORD_PART_REGEX.source})?` + `(${NUMBER_REGEX.source})` + `(${STANDARD_UNIT_REGEX.source})?`, "g" ); function adjustStrings(value, options) { return value.replace(STRING_REGEX, (match) => printString(match, options)); } function quoteAttributeValue(value, options) { const quote = options.singleQuote ? "'" : '"'; return value.includes('"') || value.includes("'") ? value : quote + value + quote; } function adjustNumbers(value) { return value.replace( ADJUST_NUMBERS_REGEX, (match, quote, wordPart, number, unit) => !wordPart && number ? printCssNumber(number) + maybeToLowerCase(unit || "") : match ); } function printCssNumber(rawNumber) { return ( printNumber(rawNumber) // Remove trailing `.0`. .replace(/\.0(?=$|e)/, "") ); } module.exports = { print: genericPrint, embed, insertPragma, massageAstNode: clean, };