Files
test-repo/src/language-css/printer-postcss.js
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1134 lines
33 KiB
JavaScript
Raw Normal View History

2022-02-21 14:22:51 +08:00
import getLast from "../utils/get-last.js";
import {
2018-05-27 21:36:12 +10:00
printNumber,
printString,
hasNewline,
isFrontMatterNode,
2020-07-05 00:34:35 +08:00
isNextLineEmpty,
2020-12-17 14:46:59 +08:00
isNonEmptyArray,
2022-02-21 14:22:51 +08:00
} from "../common/util.js";
import {
join,
line,
hardline,
softline,
group,
fill,
indent,
dedent,
ifBreak,
breakParent,
} from "../document/builders.js";
import { removeLines, getDocParts } from "../document/utils.js";
2022-08-17 09:47:08 +08:00
import createGetVisitorKeys from "../utils/create-get-visitor-keys.js";
2022-08-30 10:30:47 +08:00
import UnexpectedNodeError from "../utils/unexpected-node-error.js";
2022-02-21 14:22:51 +08:00
import clean from "./clean.js";
import embed from "./embed.js";
import { insertPragma } from "./pragma.js";
2022-08-17 09:47:08 +08:00
import visitorKeys from "./visitor-keys.js";
2022-02-21 14:22:51 +08:00
import {
2018-05-27 21:36:12 +10:00
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,
2018-05-27 21:36:12 +10:00
isDetachedRulesetCallNode,
isTemplatePlaceholderNode,
isTemplatePropNode,
2018-05-27 21:36:12 +10:00
isPostcssSimpleVarNode,
isSCSSMapItemNode,
isInlineValueCommentNode,
isHashNode,
isLeftCurlyBraceNode,
isRightCurlyBraceNode,
isWordNode,
isColonNode,
2018-05-29 15:24:38 +03:00
isMediaAndSupportsKeywords,
2020-03-10 07:14:34 +08:00
isColorAdjusterFuncNode,
lastLineHasInlineComment,
isAtWordPlaceholderNode,
2021-10-19 15:31:53 +09:00
isConfigurationNode,
isParenGroupNode,
isVarFunctionNode,
2022-02-21 14:22:51 +08:00
} from "./utils/index.js";
import { locStart, locEnd } from "./loc.js";
2022-03-10 11:30:31 +08:00
import printUnit from "./utils/print-unit.js";
2022-02-21 14:22:51 +08:00
2022-08-30 10:30:47 +08:00
const getVisitorKeys = createGetVisitorKeys(visitorKeys);
2018-05-17 13:39:22 +03:00
function shouldPrintComma(options) {
2020-07-17 09:08:03 +08:00
return options.trailingComma === "es5" || options.trailingComma === "all";
2018-05-17 13:39:22 +03:00
}
function genericPrint(path, options, print) {
const { node } = path;
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();
2018-01-12 00:05:57 +03:00
return [
2022-08-30 11:00:12 +08:00
node.frontMatter ? [print("frontMatter"), hardline] : "",
nodes,
after ? ` ${after}` : "",
2022-08-30 11:00:12 +08:00
node.nodes.length > 0 ? hardline : "",
];
}
case "css-comment": {
2020-03-20 19:09:07 +03:00
const isInlineComment = node.inline || node.raws.inline;
const text = options.originalText.slice(locStart(node), locEnd(node));
2020-03-10 07:14:34 +08:00
2020-03-20 19:09:07 +03:00
return isInlineComment ? text.trimEnd() : text;
}
case "css-rule": {
return [
print("selector"),
node.important ? " !important" : "",
node.nodes
? [
2020-03-10 07:14:34 +08:00
node.selector &&
node.selector.type === "selector-unknown" &&
lastLineHasInlineComment(node.selector.value)
? line
2020-11-09 16:47:12 +08:00
: node.selector
? " "
: "",
2020-03-10 07:14:34 +08:00
"{",
node.nodes.length > 0
? indent([hardline, printNodeSequence(path, options, print)])
: "",
hardline,
"}",
2018-05-15 01:08:04 +03:00
isDetachedRulesetDeclarationNode(node) ? ";" : "",
]
: ";",
];
}
case "css-decl": {
const parentNode = path.getParentNode();
const { between: rawBetween } = node.raws;
const trimmedBetween = rawBetween.trim();
const isColon = trimmedBetween === ":";
2020-11-09 16:47:12 +08:00
const isValueAllSpace =
typeof node.value === "string" && /^ *$/.test(node.value);
2022-08-30 10:30:47 +08:00
let value = typeof node.value === "string" ? node.value : print("value");
2022-08-30 10:30:47 +08:00
value = hasComposesNode(node) ? removeLines(value) : value;
if (!isColon && lastLineHasInlineComment(trimmedBetween)) {
value = indent([hardline, dedent(value)]);
}
return [
node.raws.before.replace(/[\s;]/g, ""),
2018-04-26 13:53:52 +03:00
insideICSSRuleNode(path) ? node.prop : maybeToLowerCase(node.prop),
trimmedBetween.startsWith("//") ? " " : "",
trimmedBetween,
2020-11-09 16:47:12 +08:00
node.extend || isValueAllSpace ? "" : " ",
options.parser === "less" && node.extend && node.selector
? ["extend(", print("selector"), ")"]
2020-04-30 16:58:21 +08:00
: "",
value,
2018-02-06 18:09:42 +03:00
node.raws.important
? node.raws.important.replace(/\s*!\s*important/i, " !important")
2018-04-11 22:39:13 -06:00
: node.important
2018-11-07 09:12:25 +08:00
? " !important"
: "",
2018-02-06 18:09:42 +03:00
node.raws.scssDefault
? node.raws.scssDefault.replace(/\s*!default/i, " !default")
2018-04-11 22:39:13 -06:00
: node.scssDefault
2018-11-07 09:12:25 +08:00
? " !default"
: "",
2018-02-06 18:09:42 +03:00
node.raws.scssGlobal
? node.raws.scssGlobal.replace(/\s*!global/i, " !global")
2018-04-11 22:39:13 -06:00
: node.scssGlobal
2018-11-07 09:12:25 +08:00
? " !global"
: "",
node.nodes
? [
" {",
indent([softline, printNodeSequence(path, options, print)]),
softline,
"}",
]
: isTemplatePropNode(node) &&
!parentNode.raws.semicolon &&
options.originalText[locEnd(node) - 1] !== ";"
2018-11-07 09:12:25 +08:00
? ""
: options.__isHTMLStyleAttribute && isLastNode(path, node)
? ifBreak(";")
2018-11-07 09:12:25 +08:00
: ";",
];
}
case "css-atrule": {
const parentNode = path.getParentNode();
2020-03-10 07:14:34 +08:00
const isTemplatePlaceholderNodeWithoutSemiColon =
isTemplatePlaceholderNode(node) &&
!parentNode.raws.semicolon &&
options.originalText[locEnd(node) - 1] !== ";";
2020-03-10 07:14:34 +08:00
2020-11-09 16:47:12 +08:00
if (options.parser === "less") {
2020-03-10 07:14:34 +08:00
if (node.mixin) {
return [
print("selector"),
2020-03-10 07:14:34 +08:00
node.important ? " !important" : "",
isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";",
];
2020-03-10 07:14:34 +08:00
}
if (node.function) {
return [
2020-03-10 07:14:34 +08:00
node.name,
2022-08-30 10:30:47 +08:00
typeof node.params === "string" ? node.params : print("params"),
2020-03-10 07:14:34 +08:00
isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";",
];
2020-03-10 07:14:34 +08:00
}
if (node.variable) {
return [
2020-03-10 07:14:34 +08:00
"@",
node.name,
": ",
node.value ? print("value") : "",
2020-03-10 07:14:34 +08:00
node.raws.between.trim() ? node.raws.between.trim() + " " : "",
node.nodes
? [
2020-03-10 07:14:34 +08:00
"{",
indent([
node.nodes.length > 0 ? softline : "",
printNodeSequence(path, options, print),
]),
2020-03-10 07:14:34 +08:00
softline,
"}",
]
2020-03-10 07:14:34 +08:00
: "",
isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";",
];
2020-03-10 07:14:34 +08:00
}
}
2020-11-09 16:47:12 +08:00
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
2018-04-26 13:53:52 +03:00
// variable declarations will be parsed as at-rules with names ending
// with a colon, so keep the original case then.
2018-05-15 01:08:04 +03:00
isDetachedRulesetCallNode(node) || node.name.endsWith(":")
? node.name
: maybeToLowerCase(node.name),
2018-04-26 13:53:52 +03:00
node.params
? [
isDetachedRulesetCallNode(node)
? ""
2020-03-31 17:10:38 +03:00
: isTemplatePlaceholderNode(node)
? node.raws.afterName === ""
? ""
: node.name.endsWith(":")
? " "
: /^\s*\n\s*\n/.test(node.raws.afterName)
? [hardline, hardline]
2020-03-31 17:10:38 +03:00
: /^\s*\n/.test(node.raws.afterName)
? hardline
: " "
2018-11-07 09:12:25 +08:00
: " ",
2022-08-30 10:30:47 +08:00
typeof node.params === "string" ? node.params : print("params"),
]
: "",
node.selector ? indent([" ", print("selector")]) : "",
node.value
? group([
" ",
print("value"),
2021-01-11 14:26:20 +08:00
isSCSSControlDirectiveNode(node, options)
? hasParensAroundNode(node)
? " "
: line
: "",
])
2018-04-11 22:39:13 -06:00
: node.name === "else"
2018-11-07 09:12:25 +08:00
? " "
: "",
node.nodes
? [
2020-11-09 16:47:12 +08:00
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,
"}",
]
2020-11-09 16:47:12 +08:00
: isTemplatePlaceholderNodeWithoutSemiColon ||
isImportUnknownValueEndsWithSemiColon
2018-11-07 09:12:25 +08:00
? ""
: ";",
];
}
// postcss-media-query-parser
case "media-query-list": {
const parts = [];
path.each((childPath) => {
const { node } = childPath;
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")),
2018-02-22 12:00:08 +03:00
isLastNode(path, node) ? "" : ",",
];
}
case "media-type": {
return adjustNumbers(adjustStrings(node.value, options));
}
case "media-feature-expression": {
if (!node.nodes) {
return node.value;
}
return ["(", ...path.map(print, "nodes"), ")"];
}
case "media-feature": {
2017-09-03 21:22:28 +02:00
return maybeToLowerCase(
adjustStrings(node.value.replace(/ +/g, " "), options)
2017-09-03 21:22:28 +02:00
);
}
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(
2021-05-15 19:37:17 +08:00
node.value.replace(/^url\(\s+/gi, "url(").replace(/\s+\)$/g, ")"),
options
);
}
case "media-unknown": {
2018-05-15 00:00:45 +03:00
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")
),
]);
}
2018-05-22 21:10:24 +03:00
case "selector-selector": {
return group(indent(path.map(print, "nodes")));
2018-05-22 21:10:24 +03:00
}
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.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();
2017-07-08 14:07:39 +02:00
const leading =
parentNode.type === "selector-selector" &&
parentNode.nodes[0] === node
2017-07-08 14:07:39 +02:00
? ""
: line;
2018-05-22 21:10:24 +03:00
return [leading, node.value, isLastNode(path, node) ? "" : " "];
}
2018-05-22 21:10:24 +03:00
const leading = node.value.trim().startsWith("(") ? line : "";
2017-09-03 21:22:28 +02:00
const value =
adjustNumbers(adjustStrings(node.value.trim(), options)) || line;
2018-05-22 21:10:24 +03:00
return [leading, value];
}
case "selector-universal": {
return [
node.namespace
? [node.namespace === true ? "" : node.namespace.trim(), "|"]
: "",
2018-06-25 12:57:41 +03:00
node.value,
];
}
case "selector-pseudo": {
return [
maybeToLowerCase(node.value),
2020-12-17 14:46:59 +08:00
isNonEmptyArray(node.nodes)
? ["(", join(", ", path.map(print, "nodes")), ")"]
: "",
];
}
case "selector-nesting": {
return node.value;
}
2018-05-15 00:00:45 +03:00
case "selector-unknown": {
const ruleAncestorNode = getAncestorNode(path, "css-rule");
// Nested SCSS property
if (ruleAncestorNode && ruleAncestorNode.isSCSSNesterProperty) {
return adjustNumbers(
adjustStrings(maybeToLowerCase(node.value), options)
);
}
2020-03-20 19:09:07 +03:00
// 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);
2020-03-20 19:09:07 +03:00
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 = locEnd(parentNode.open) + 1;
const end = locStart(parentNode.close);
const selector = options.originalText.slice(start, end).trim();
return lastLineHasInlineComment(selector)
? [breakParent, selector]
: selector;
}
2018-05-15 00:00:45 +03:00
return node.value;
}
// postcss-values-parser
2018-05-22 21:10:24 +03:00
case "value-value":
case "value-root": {
2021-03-22 22:57:03 +08:00
return print("group");
}
2018-02-06 18:09:42 +03:00
case "value-comment": {
return options.originalText.slice(locStart(node), locEnd(node));
2018-02-06 18:09:42 +03:00
}
case "value-comma_group": {
const parentNode = path.getParentNode();
2018-05-29 15:24:38 +03:00
const parentParentNode = path.getParentNode(1);
2018-04-26 13:53:52 +03:00
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 =
2020-11-09 16:47:12 +08:00
atRuleAncestorNode &&
isSCSSControlDirectiveNode(atRuleAncestorNode, options);
const hasInlineComment = node.groups.some((node) =>
isInlineValueCommentNode(node)
);
const printed = path.map(print, "groups");
const parts = [];
2018-04-26 13:53:52 +03:00
const insideURLFunction = insideValueFunctionNode(path, "url");
2018-05-22 16:05:47 +03:00
let insideSCSSInterpolationInString = false;
let didBreak = false;
for (let i = 0; i < node.groups.length; ++i) {
parts.push(printed[i]);
2018-02-16 21:58:21 +03:00
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 SCSS @forward wildcard suffix
if (
insideAtRuleNode(path, "forward") &&
iNode.type === "value-word" &&
iNode.value &&
2021-10-29 14:46:50 +05:30
iPrevNode !== undefined &&
iPrevNode.type === "value-word" &&
iPrevNode.value === "as" &&
iNextNode.type === "value-operator" &&
iNextNode.value === "*"
) {
continue;
}
// Ignore after latest node (i.e. before semicolon)
2018-02-16 21:58:21 +03:00
if (!iNextNode) {
continue;
2020-03-31 17:10:38 +03:00
}
// styled.div` background: var(--${one}); `
if (
iNode.type === "value-word" &&
iNode.value.endsWith("-") &&
isAtWordPlaceholderNode(iNextNode)
2020-03-31 17:10:38 +03:00
) {
continue;
}
2018-05-22 16:05:47 +03:00
// Ignore spaces before/after string interpolation (i.e. `"#{my-fn("_")}"`)
2019-10-13 22:51:58 +03:00
const isStartSCSSInterpolationInString =
2018-05-22 16:05:47 +03:00
iNode.type === "value-string" && iNode.value.startsWith("#{");
2019-10-13 22:51:58 +03:00
const isEndingSCSSInterpolationInString =
2018-05-22 16:05:47 +03:00
insideSCSSInterpolationInString &&
iNextNode.type === "value-string" &&
iNextNode.value.endsWith("}");
if (
2019-10-13 22:51:58 +03:00
isStartSCSSInterpolationInString ||
isEndingSCSSInterpolationInString
2018-05-22 16:05:47 +03:00
) {
insideSCSSInterpolationInString = !insideSCSSInterpolationInString;
continue;
}
if (insideSCSSInterpolationInString) {
continue;
}
2018-05-22 21:10:24 +03:00
// Ignore colon (i.e. `:`)
if (isColonNode(iNode) || isColonNode(iNextNode)) {
continue;
}
// Ignore `@` in Less (i.e. `@@var;`)
2018-02-16 21:58:21 +03:00
if (iNode.type === "value-atword" && iNode.value === "") {
continue;
}
// Ignore `~` in Less (i.e. `content: ~"^//* some horrible but needed css hack";`)
2018-02-16 21:58:21 +03:00
if (iNode.value === "~") {
continue;
}
// Ignore escape `\`
if (
iNode.value &&
2019-11-17 18:06:14 +08:00
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;
}
2018-05-14 13:47:46 +03:00
// Ignore `\` (i.e. `$variable: \@small;`)
if (iNode.value === "\\") {
continue;
}
// Ignore `$$` (i.e. `background-color: $$(style)Color;`)
2018-05-15 01:08:04 +03:00
if (isPostcssSimpleVarNode(iNode, iNextNode)) {
2018-05-14 13:47:46 +03:00
continue;
}
2018-05-22 21:10:24 +03:00
// Ignore spaces after `#` and after `{` and before `}` in SCSS interpolation (i.e. `#{variable}`)
if (
2018-05-22 21:10:24 +03:00
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}`)
2018-05-22 21:10:24 +03:00
if (iNode.value === "--" && isHashNode(iNextNode)) {
continue;
}
2018-05-24 20:19:22 +03:00
// Formatting math operations
2018-02-16 21:58:21 +03:00
const isMathOperator = isMathOperatorNode(iNode);
const isNextMathOperator = isMathOperatorNode(iNextNode);
2018-05-24 20:19:22 +03:00
// 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;
}
2018-02-16 21:58:21 +03:00
// 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;
}
2018-05-24 20:19:22 +03:00
// 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;
}
2018-02-16 21:58:21 +03:00
2018-05-29 14:03:09 -04:00
// 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.
2018-05-29 15:24:38 +03:00
const isColorAdjusterNode =
(isAdditionNode(iNode) || isSubtractionNode(iNode)) &&
i === 0 &&
(iNextNode.type === "value-number" || iNextNode.isHex) &&
2019-11-09 01:14:31 +01:00
parentParentNode &&
isColorAdjusterFuncNode(parentParentNode) &&
2018-05-29 15:24:38 +03:00
!hasEmptyRawBefore(iNextNode);
2018-05-24 20:19:22 +03:00
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") &&
2018-05-29 15:24:38 +03:00
!isColorAdjusterNode &&
2018-05-24 20:19:22 +03:00
((isDivisionNode(iNextNode) && !requireSpaceBeforeOperator) ||
(isDivisionNode(iNode) && !requireSpaceAfterOperator) ||
(isAdditionNode(iNextNode) && !requireSpaceBeforeOperator) ||
(isAdditionNode(iNode) && !requireSpaceAfterOperator) ||
isSubtractionNode(iNextNode) ||
isSubtractionNode(iNode)) &&
(hasEmptyRawBefore(iNextNode) ||
(isMathOperator &&
(!iPrevNode || (iPrevNode && isMathOperatorNode(iPrevNode)))))
) {
continue;
2018-02-16 21:58:21 +03:00
}
2018-05-22 21:10:24 +03:00
// 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);
2018-05-22 21:10:24 +03:00
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);
2018-05-22 21:10:24 +03:00
didBreak = true;
} else {
parts.push(" ");
}
2018-05-22 21:10:24 +03:00
continue;
}
2018-05-22 21:10:24 +03:00
// 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;
}
2018-05-22 21:10:24 +03:00
if (
isAtWordPlaceholderNode(iNode) &&
isAtWordPlaceholderNode(iNextNode) &&
locEnd(iNode) === locStart(iNextNode)
) {
continue;
}
2021-10-19 15:31:53 +09:00
if (
isAtWordPlaceholderNode(iNode) &&
isParenGroupNode(iNextNode) &&
locEnd(iNode) === locStart(iNextNode.open)
2021-10-19 15:31:53 +09:00
) {
parts.push(softline);
continue;
}
if (iNode.value === "with" && isParenGroupNode(iNextNode)) {
2021-10-19 15:31:53 +09:00
parts.push(" ");
continue;
}
if (
iNode.value?.endsWith("#") &&
iNextNode.value === "{" &&
isParenGroupNode(iNextNode.group)
) {
continue;
}
2018-05-22 21:10:24 +03:00
// 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
2018-05-17 13:39:22 +03:00
if (insideURLFunctionInImportAtRuleNode(path)) {
return group(fill(parts));
}
2018-05-17 13:39:22 +03:00
return group(indent(fill(parts)));
}
case "value-paren_group": {
const parentNode = path.getParentNode();
2022-08-30 10:30:47 +08:00
const printedGroups = path.map(() => {
const child = path.node;
2022-08-30 10:30:47 +08:00
return typeof child === "string" ? child : print();
}, "groups");
if (
2018-04-26 13:53:52 +03:00
parentNode &&
2018-05-15 01:08:04 +03:00
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 [
node.open ? print("open") : "",
2022-08-30 10:30:47 +08:00
join(",", printedGroups),
node.close ? print("close") : "",
];
}
if (!node.open) {
2022-08-30 10:30:47 +08:00
return group(indent(fill(join([",", line], printedGroups))));
}
2020-11-09 16:47:12 +08:00
const isSCSSMapItem = isSCSSMapItemNode(path, options);
2017-10-21 21:35:22 -04:00
const lastItem = getLast(node.groups);
const isLastItemComment = lastItem && lastItem.type === "value-comment";
const isKey = isKeyInValuePairNode(node, parentNode);
2021-10-19 15:31:53 +09:00
const isConfiguration = isConfigurationNode(node, parentNode);
const isVarFunction = isVarFunctionNode(parentNode);
2021-10-19 15:31:53 +09:00
const shouldBreak = isConfiguration || (isSCSSMapItem && !isKey);
const shouldDedent = isConfiguration || isKey;
const printed = group(
[
node.open ? print("open") : "",
indent([
softline,
join(
[line],
path.map((childPath, index) => {
const child = childPath.node;
const isLast = index === node.groups.length - 1;
const hasComma = () =>
Boolean(
child.source &&
options.originalText
.slice(locStart(child), locStart(node.close))
.trimEnd()
.endsWith(",")
);
const shouldPrintComma =
!isLast || (isVarFunction && hasComma());
const printed = [print(), shouldPrintComma ? "," : ""];
2018-05-17 13:39:22 +03:00
// Key/Value pair in open paren already indented
if (
isKeyValuePairNode(child) &&
child.type === "value-comma_group" &&
child.groups &&
child.groups[0].type !== "value-paren_group" &&
child.groups[2] &&
child.groups[2].type === "value-paren_group"
) {
const parts = getDocParts(printed[0].contents.contents);
parts[1] = group(parts[1]);
return group(dedent(printed));
}
2018-05-17 13:39:22 +03:00
if (
!isLast &&
child.type === "value-comma_group" &&
2022-03-29 20:44:28 +08:00
isNonEmptyArray(child.groups)
) {
2022-03-29 20:44:28 +08:00
const last = getLast(child.groups);
if (
// `value-paren_group` missing location info
last.source &&
isNextLineEmpty(options.originalText, last, locEnd)
) {
printed.push(hardline);
}
}
return printed;
}, "groups")
),
]),
2018-05-17 13:39:22 +03:00
ifBreak(
!isLastItemComment &&
2020-11-09 16:47:12 +08:00
options.parser === "scss" &&
2018-11-07 09:12:25 +08:00
isSCSSMapItem &&
shouldPrintComma(options)
2018-05-17 13:39:22 +03:00
? ","
: ""
),
softline,
node.close ? print("close") : "",
],
2018-05-17 13:39:22 +03:00
{
2021-10-19 15:31:53 +09:00
shouldBreak,
2018-05-17 13:39:22 +03:00
}
);
2021-10-19 15:31:53 +09:00
return shouldDedent ? dedent(printed) : printed;
}
case "value-func": {
return [
node.value,
2018-05-22 21:10:24 +03:00
insideAtRuleNode(path, "supports") && isMediaAndSupportsKeywords(node)
? " "
: "",
print("group"),
];
}
case "value-paren": {
return node.value;
}
case "value-number": {
2022-03-08 23:36:14 +01:00
return [printCssNumber(node.value), printUnit(node.unit)];
}
case "value-operator": {
return node.value;
}
case "value-word": {
if ((node.isColor && node.isHex) || isWideKeywords(node.value)) {
return node.value.toLowerCase();
}
2018-05-15 01:08:04 +03:00
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 [
2018-04-26 13:53:52 +03:00
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) === "\\") ||
2018-05-22 21:10:24 +03:00
// Don't add spaces on `:` in `url` function (i.e. `url(fbglyph: cross-outline, fig-white)`)
insideValueFunctionNode(path, "url")
? ""
: line,
];
}
case "value-string": {
2018-05-27 21:36:12 +10:00
return printString(
node.raws.quote + node.value + node.raws.quote,
options
);
}
case "value-atword": {
return ["@", node.value];
}
2018-03-07 21:15:06 +03:00
case "value-unicode-range": {
return node.value;
}
2018-05-15 00:00:45 +03:00
case "value-unknown": {
return node.value;
}
2022-08-30 10:30:47 +08:00
case "value-comma": // Handled in `value-comma_group`
default:
2017-09-03 22:40:44 +02:00
/* istanbul ignore next */
2022-08-30 10:30:47 +08:00
throw new UnexpectedNodeError(node, "PostCSS");
}
}
function printNodeSequence(path, options, print) {
const parts = [];
path.each((pathChild, i, nodes) => {
const prevNode = nodes[i - 1];
2017-06-11 00:45:59 +10:00
if (
prevNode &&
prevNode.type === "css-comment" &&
prevNode.text.trim() === "prettier-ignore"
) {
const childNode = pathChild.getValue();
parts.push(
options.originalText.slice(locStart(childNode), locEnd(childNode))
2017-06-11 00:45:59 +10:00
);
} else {
parts.push(print());
2017-06-11 00:45:59 +10:00
}
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;
}
2021-05-18 02:44:21 +08:00
const STRING_REGEX = /(["'])(?:(?!\1)[^\\]|\\.)*\1/gs;
2020-04-10 21:20:40 +08:00
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;
2019-11-17 18:36:21 +08:00
const ADJUST_NUMBERS_REGEX = new RegExp(
2017-09-03 21:22:28 +02:00
STRING_REGEX.source +
2020-01-14 18:58:20 +08:00
"|" +
2017-09-03 21:22:28 +02:00
`(${WORD_PART_REGEX.source})?` +
`(${NUMBER_REGEX.source})` +
`(${STANDARD_UNIT_REGEX.source})?`,
2017-08-15 17:25:57 +02:00
"g"
);
2017-08-14 08:57:16 +02:00
function adjustStrings(value, options) {
2018-05-27 21:36:12 +10:00
return value.replace(STRING_REGEX, (match) => printString(match, options));
2017-08-14 08:57:16 +02:00
}
function quoteAttributeValue(value, options) {
const quote = options.singleQuote ? "'" : '"';
return value.includes('"') || value.includes("'")
? value
: quote + value + quote;
}
2017-08-15 17:25:57 +02:00
function adjustNumbers(value) {
return value.replace(
2017-09-03 21:22:28 +02:00
ADJUST_NUMBERS_REGEX,
(match, quote, wordPart, number, unit) =>
!wordPart && number
2020-02-20 23:54:15 +01:00
? printCssNumber(number) + maybeToLowerCase(unit || "")
2017-09-03 21:22:28 +02:00
: match
2017-08-15 17:25:57 +02:00
);
}
2018-05-27 21:36:12 +10:00
function printCssNumber(rawNumber) {
2017-08-15 17:25:57 +02:00
return (
2018-05-27 21:36:12 +10:00
printNumber(rawNumber)
2017-08-15 17:25:57 +02:00
// Remove trailing `.0`.
.replace(/\.0(?=$|e)/, "")
);
}
2022-02-21 14:22:51 +08:00
const printer = {
2017-12-26 12:23:50 +11:00
print: genericPrint,
2018-07-03 09:54:40 +08:00
embed,
2018-06-14 09:51:18 +08:00
insertPragma,
massageAstNode: clean,
2022-08-30 10:30:47 +08:00
getVisitorKeys,
2017-12-26 12:23:50 +11:00
};
2022-02-21 14:22:51 +08:00
export default printer;