prettier/src/language-yaml/utils.js

341 lines
8.0 KiB
JavaScript

"use strict";
const { getLast } = require("../common/util");
function getAncestorCount(path, filter) {
let counter = 0;
const pathStackLength = path.stack.length - 1;
for (let i = 0; i < pathStackLength; i++) {
const value = path.stack[i];
if (isNode(value) && filter(value)) {
counter++;
}
}
return counter;
}
/**
* @param {any} value
* @param {string[]=} types
*/
function isNode(value, types) {
return (
value &&
typeof value.type === "string" &&
(!types || types.indexOf(value.type) !== -1)
);
}
function mapNode(node, callback, parent) {
return callback(
"children" in node
? Object.assign({}, node, {
children: node.children.map(childNode =>
mapNode(childNode, callback, node)
)
})
: node,
parent
);
}
function defineShortcut(x, key, getter) {
Object.defineProperty(x, key, {
get: getter,
enumerable: false
});
}
function isNextLineEmpty(node, text) {
let newlineCount = 0;
const textLength = text.length;
for (let i = node.position.end.offset - 1; i < textLength; i++) {
const char = text[i];
if (char === "\n") {
newlineCount++;
}
if (newlineCount === 1 && /\S/.test(char)) {
return false;
}
if (newlineCount === 2) {
return true;
}
}
return false;
}
function isLastDescendantNode(path) {
const node = path.getValue();
switch (node.type) {
case "tag":
case "anchor":
case "comment":
return false;
}
const pathStackLength = path.stack.length;
for (let i = 1; i < pathStackLength; i++) {
const item = path.stack[i];
const parentItem = path.stack[i - 1];
if (
Array.isArray(parentItem) &&
typeof item === "number" &&
item !== parentItem.length - 1
) {
return false;
}
}
return true;
}
function getLastDescendantNode(node) {
return "children" in node && node.children.length !== 0
? getLastDescendantNode(getLast(node.children))
: node;
}
function isPrettierIgnore(comment) {
return comment.value.trim() === "prettier-ignore";
}
function hasPrettierIgnore(path) {
const node = path.getValue();
if (node.type === "documentBody") {
const document = path.getParentNode();
return (
hasEndComments(document.head) &&
isPrettierIgnore(getLast(document.head.endComments))
);
}
return (
hasLeadingComments(node) && isPrettierIgnore(getLast(node.leadingComments))
);
}
function isEmptyNode(node) {
return (!node.children || node.children.length === 0) && !hasComments(node);
}
function hasComments(node) {
return (
hasLeadingComments(node) ||
hasMiddleComments(node) ||
hasIndicatorComment(node) ||
hasTrailingComment(node) ||
hasEndComments(node)
);
}
function hasLeadingComments(node) {
return node && node.leadingComments && node.leadingComments.length !== 0;
}
function hasMiddleComments(node) {
return node && node.middleComments && node.middleComments.length !== 0;
}
function hasIndicatorComment(node) {
return node && node.indicatorComment;
}
function hasTrailingComment(node) {
return node && node.trailingComment;
}
function hasEndComments(node) {
return node && node.endComments && node.endComments.length !== 0;
}
/**
* " a b c d e f " -> [" a b", "c d", "e f "]
*/
function splitWithSingleSpace(text) {
const parts = [];
let lastPart = undefined;
for (const part of text.split(/( +)/g)) {
if (part !== " ") {
if (lastPart === " ") {
parts.push(part);
} else {
parts.push((parts.pop() || "") + part);
}
} else if (lastPart === undefined) {
parts.unshift("");
}
lastPart = part;
}
if (lastPart === " ") {
parts.push((parts.pop() || "") + " ");
}
if (parts[0] === "") {
parts.shift();
parts.unshift(" " + (parts.shift() || ""));
}
return parts;
}
function getFlowScalarLineContents(nodeType, content, options) {
const rawLineContents = content
.split("\n")
.map((lineContent, index, lineContents) =>
index === 0 && index === lineContents.length - 1
? lineContent
: index !== 0 && index !== lineContents.length - 1
? lineContent.trim()
: index === 0
? lineContent.trimRight()
: lineContent.trimLeft()
);
if (options.proseWrap === "preserve") {
return rawLineContents.map(lineContent =>
lineContent.length === 0 ? [] : [lineContent]
);
}
return rawLineContents
.map(lineContent =>
lineContent.length === 0 ? [] : splitWithSingleSpace(lineContent)
)
.reduce(
(reduced, lineContentWords, index) =>
index !== 0 &&
rawLineContents[index - 1].length !== 0 &&
lineContentWords.length !== 0 &&
!// trailing backslash in quoteDouble should be preserved
(nodeType === "quoteDouble" && getLast(getLast(reduced)).endsWith("\\"))
? reduced.concat([reduced.pop().concat(lineContentWords)])
: reduced.concat([lineContentWords]),
[]
)
.map(lineContentWords =>
options.proseWrap === "never"
? [lineContentWords.join(" ")]
: lineContentWords
);
}
function getBlockValueLineContents(
node,
{ parentIndent, isLastDescendant, options }
) {
const content =
node.position.start.line === node.position.end.line
? ""
: options.originalText
.slice(node.position.start.offset, node.position.end.offset)
// exclude open line `>` or `|`
.match(/^[^\n]*?\n([\s\S]*)$/)[1];
const leadingSpaceCount =
node.indent === null
? (match => (match ? match[1].length : Infinity))(
content.match(/^( *)\S/m)
)
: node.indent - 1 + parentIndent;
const rawLineContents = content
.split("\n")
.map(lineContent => lineContent.slice(leadingSpaceCount));
if (options.proseWrap === "preserve" || node.type === "blockLiteral") {
return removeUnnecessaryTrailingNewlines(
rawLineContents.map(lineContent =>
lineContent.length === 0 ? [] : [lineContent]
)
);
}
return removeUnnecessaryTrailingNewlines(
rawLineContents
.map(lineContent =>
lineContent.length === 0 ? [] : splitWithSingleSpace(lineContent)
)
.reduce(
(reduced, lineContentWords, index) =>
index !== 0 &&
rawLineContents[index - 1].length !== 0 &&
lineContentWords.length !== 0 &&
!/^\s/.test(lineContentWords[0]) &&
!/^\s|\s$/.test(getLast(reduced))
? reduced.concat([reduced.pop().concat(lineContentWords)])
: reduced.concat([lineContentWords]),
[]
)
.map(lineContentWords =>
lineContentWords.reduce(
(reduced, word) =>
// disallow trailing spaces
reduced.length !== 0 && /\s$/.test(getLast(reduced))
? reduced.concat(reduced.pop() + " " + word)
: reduced.concat(word),
[]
)
)
.map(lineContentWords =>
options.proseWrap === "never"
? [lineContentWords.join(" ")]
: lineContentWords
)
);
function removeUnnecessaryTrailingNewlines(lineContents) {
if (node.chomping === "keep") {
return getLast(lineContents).length === 0
? lineContents.slice(0, -1)
: lineContents;
}
let trailingNewlineCount = 0;
for (let i = lineContents.length - 1; i >= 0; i--) {
if (lineContents[i].length === 0) {
trailingNewlineCount++;
} else {
break;
}
}
return trailingNewlineCount === 0
? lineContents
: trailingNewlineCount >= 2 && !isLastDescendant
? // next empty line
lineContents.slice(0, -(trailingNewlineCount - 1))
: lineContents.slice(0, -trailingNewlineCount);
}
}
module.exports = {
getLast,
getAncestorCount,
isNode,
isEmptyNode,
mapNode,
defineShortcut,
isNextLineEmpty,
isLastDescendantNode,
getBlockValueLineContents,
getFlowScalarLineContents,
getLastDescendantNode,
hasPrettierIgnore,
hasLeadingComments,
hasMiddleComments,
hasIndicatorComment,
hasTrailingComment,
hasEndComments
};