725 lines
18 KiB
JavaScript
725 lines
18 KiB
JavaScript
"use strict";
|
|
|
|
const stringWidth = require("string-width");
|
|
const escapeStringRegexp = require("escape-string-regexp");
|
|
const getLast = require("../utils/get-last");
|
|
|
|
// eslint-disable-next-line no-control-regex
|
|
const notAsciiRegex = /[^\x20-\x7F]/;
|
|
|
|
function isExportDeclaration(node) {
|
|
if (node) {
|
|
switch (node.type) {
|
|
case "ExportDefaultDeclaration":
|
|
case "ExportDefaultSpecifier":
|
|
case "DeclareExportDeclaration":
|
|
case "ExportNamedDeclaration":
|
|
case "ExportAllDeclaration":
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getParentExportDeclaration(path) {
|
|
const parentNode = path.getParentNode();
|
|
if (path.getName() === "declaration" && isExportDeclaration(parentNode)) {
|
|
return parentNode;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getPenultimate(arr) {
|
|
if (arr.length > 1) {
|
|
return arr[arr.length - 2];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function skip(chars) {
|
|
return (text, index, opts) => {
|
|
const backwards = opts && opts.backwards;
|
|
|
|
// Allow `skip` functions to be threaded together without having
|
|
// to check for failures (did someone say monads?).
|
|
if (index === false) {
|
|
return false;
|
|
}
|
|
|
|
const length = text.length;
|
|
let cursor = index;
|
|
while (cursor >= 0 && cursor < length) {
|
|
const c = text.charAt(cursor);
|
|
if (chars instanceof RegExp) {
|
|
if (!chars.test(c)) {
|
|
return cursor;
|
|
}
|
|
} else if (chars.indexOf(c) === -1) {
|
|
return cursor;
|
|
}
|
|
|
|
backwards ? cursor-- : cursor++;
|
|
}
|
|
|
|
if (cursor === -1 || cursor === length) {
|
|
// If we reached the beginning or end of the file, return the
|
|
// out-of-bounds cursor. It's up to the caller to handle this
|
|
// correctly. We don't want to indicate `false` though if it
|
|
// actually skipped valid characters.
|
|
return cursor;
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
const skipWhitespace = skip(/\s/);
|
|
const skipSpaces = skip(" \t");
|
|
const skipToLineEnd = skip(",; \t");
|
|
const skipEverythingButNewLine = skip(/[^\r\n]/);
|
|
|
|
function skipInlineComment(text, index) {
|
|
if (index === false) {
|
|
return false;
|
|
}
|
|
|
|
if (text.charAt(index) === "/" && text.charAt(index + 1) === "*") {
|
|
for (let i = index + 2; i < text.length; ++i) {
|
|
if (text.charAt(i) === "*" && text.charAt(i + 1) === "/") {
|
|
return i + 2;
|
|
}
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
function skipTrailingComment(text, index) {
|
|
if (index === false) {
|
|
return false;
|
|
}
|
|
|
|
if (text.charAt(index) === "/" && text.charAt(index + 1) === "/") {
|
|
return skipEverythingButNewLine(text, index);
|
|
}
|
|
return index;
|
|
}
|
|
|
|
// This one doesn't use the above helper function because it wants to
|
|
// test \r\n in order and `skip` doesn't support ordering and we only
|
|
// want to skip one newline. It's simple to implement.
|
|
function skipNewline(text, index, opts) {
|
|
const backwards = opts && opts.backwards;
|
|
if (index === false) {
|
|
return false;
|
|
}
|
|
|
|
const atIndex = text.charAt(index);
|
|
if (backwards) {
|
|
if (text.charAt(index - 1) === "\r" && atIndex === "\n") {
|
|
return index - 2;
|
|
}
|
|
if (
|
|
atIndex === "\n" ||
|
|
atIndex === "\r" ||
|
|
atIndex === "\u2028" ||
|
|
atIndex === "\u2029"
|
|
) {
|
|
return index - 1;
|
|
}
|
|
} else {
|
|
if (atIndex === "\r" && text.charAt(index + 1) === "\n") {
|
|
return index + 2;
|
|
}
|
|
if (
|
|
atIndex === "\n" ||
|
|
atIndex === "\r" ||
|
|
atIndex === "\u2028" ||
|
|
atIndex === "\u2029"
|
|
) {
|
|
return index + 1;
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
function hasNewline(text, index, opts) {
|
|
opts = opts || {};
|
|
const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts);
|
|
const idx2 = skipNewline(text, idx, opts);
|
|
return idx !== idx2;
|
|
}
|
|
|
|
function hasNewlineInRange(text, start, end) {
|
|
for (let i = start; i < end; ++i) {
|
|
if (text.charAt(i) === "\n") {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Note: this function doesn't ignore leading comments unlike isNextLineEmpty
|
|
function isPreviousLineEmpty(text, node, locStart) {
|
|
let idx = locStart(node) - 1;
|
|
idx = skipSpaces(text, idx, { backwards: true });
|
|
idx = skipNewline(text, idx, { backwards: true });
|
|
idx = skipSpaces(text, idx, { backwards: true });
|
|
const idx2 = skipNewline(text, idx, { backwards: true });
|
|
return idx !== idx2;
|
|
}
|
|
|
|
function isNextLineEmptyAfterIndex(text, index) {
|
|
let oldIdx = null;
|
|
let idx = index;
|
|
while (idx !== oldIdx) {
|
|
// We need to skip all the potential trailing inline comments
|
|
oldIdx = idx;
|
|
idx = skipToLineEnd(text, idx);
|
|
idx = skipInlineComment(text, idx);
|
|
idx = skipSpaces(text, idx);
|
|
}
|
|
idx = skipTrailingComment(text, idx);
|
|
idx = skipNewline(text, idx);
|
|
return hasNewline(text, idx);
|
|
}
|
|
|
|
function isNextLineEmpty(text, node, locEnd) {
|
|
return isNextLineEmptyAfterIndex(text, locEnd(node));
|
|
}
|
|
|
|
function getNextNonSpaceNonCommentCharacterIndexWithStartIndex(text, idx) {
|
|
let oldIdx = null;
|
|
while (idx !== oldIdx) {
|
|
oldIdx = idx;
|
|
idx = skipSpaces(text, idx);
|
|
idx = skipInlineComment(text, idx);
|
|
idx = skipTrailingComment(text, idx);
|
|
idx = skipNewline(text, idx);
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
function getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) {
|
|
return getNextNonSpaceNonCommentCharacterIndexWithStartIndex(
|
|
text,
|
|
locEnd(node)
|
|
);
|
|
}
|
|
|
|
function getNextNonSpaceNonCommentCharacter(text, node, locEnd) {
|
|
return text.charAt(
|
|
getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd)
|
|
);
|
|
}
|
|
|
|
function hasSpaces(text, index, opts) {
|
|
opts = opts || {};
|
|
const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts);
|
|
return idx !== index;
|
|
}
|
|
|
|
function setLocStart(node, index) {
|
|
if (node.range) {
|
|
node.range[0] = index;
|
|
} else {
|
|
node.start = index;
|
|
}
|
|
}
|
|
|
|
function setLocEnd(node, index) {
|
|
if (node.range) {
|
|
node.range[1] = index;
|
|
} else {
|
|
node.end = index;
|
|
}
|
|
}
|
|
|
|
const PRECEDENCE = {};
|
|
[
|
|
["|>"],
|
|
["||", "??"],
|
|
["&&"],
|
|
["|"],
|
|
["^"],
|
|
["&"],
|
|
["==", "===", "!=", "!=="],
|
|
["<", ">", "<=", ">=", "in", "instanceof"],
|
|
[">>", "<<", ">>>"],
|
|
["+", "-"],
|
|
["*", "/", "%"],
|
|
["**"]
|
|
].forEach((tier, i) => {
|
|
tier.forEach(op => {
|
|
PRECEDENCE[op] = i;
|
|
});
|
|
});
|
|
|
|
function getPrecedence(op) {
|
|
return PRECEDENCE[op];
|
|
}
|
|
|
|
const equalityOperators = {
|
|
"==": true,
|
|
"!=": true,
|
|
"===": true,
|
|
"!==": true
|
|
};
|
|
const multiplicativeOperators = {
|
|
"*": true,
|
|
"/": true,
|
|
"%": true
|
|
};
|
|
const bitshiftOperators = {
|
|
">>": true,
|
|
">>>": true,
|
|
"<<": true
|
|
};
|
|
|
|
function shouldFlatten(parentOp, nodeOp) {
|
|
if (getPrecedence(nodeOp) !== getPrecedence(parentOp)) {
|
|
return false;
|
|
}
|
|
|
|
// ** is right-associative
|
|
// x ** y ** z --> x ** (y ** z)
|
|
if (parentOp === "**") {
|
|
return false;
|
|
}
|
|
|
|
// x == y == z --> (x == y) == z
|
|
if (equalityOperators[parentOp] && equalityOperators[nodeOp]) {
|
|
return false;
|
|
}
|
|
|
|
// x * y % z --> (x * y) % z
|
|
if (
|
|
(nodeOp === "%" && multiplicativeOperators[parentOp]) ||
|
|
(parentOp === "%" && multiplicativeOperators[nodeOp])
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// x * y / z --> (x * y) / z
|
|
// x / y * z --> (x / y) * z
|
|
if (
|
|
nodeOp !== parentOp &&
|
|
multiplicativeOperators[nodeOp] &&
|
|
multiplicativeOperators[parentOp]
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// x << y << z --> (x << y) << z
|
|
if (bitshiftOperators[parentOp] && bitshiftOperators[nodeOp]) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function isBitwiseOperator(operator) {
|
|
return (
|
|
!!bitshiftOperators[operator] ||
|
|
operator === "|" ||
|
|
operator === "^" ||
|
|
operator === "&"
|
|
);
|
|
}
|
|
|
|
// Tests if an expression starts with `{`, or (if forbidFunctionClassAndDoExpr
|
|
// holds) `function`, `class`, or `do {}`. Will be overzealous if there's
|
|
// already necessary grouping parentheses.
|
|
function startsWithNoLookaheadToken(node, forbidFunctionClassAndDoExpr) {
|
|
node = getLeftMost(node);
|
|
switch (node.type) {
|
|
case "FunctionExpression":
|
|
case "ClassExpression":
|
|
case "DoExpression":
|
|
return forbidFunctionClassAndDoExpr;
|
|
case "ObjectExpression":
|
|
return true;
|
|
case "MemberExpression":
|
|
return startsWithNoLookaheadToken(
|
|
node.object,
|
|
forbidFunctionClassAndDoExpr
|
|
);
|
|
case "TaggedTemplateExpression":
|
|
if (node.tag.type === "FunctionExpression") {
|
|
// IIFEs are always already parenthesized
|
|
return false;
|
|
}
|
|
return startsWithNoLookaheadToken(node.tag, forbidFunctionClassAndDoExpr);
|
|
case "CallExpression":
|
|
if (node.callee.type === "FunctionExpression") {
|
|
// IIFEs are always already parenthesized
|
|
return false;
|
|
}
|
|
return startsWithNoLookaheadToken(
|
|
node.callee,
|
|
forbidFunctionClassAndDoExpr
|
|
);
|
|
case "ConditionalExpression":
|
|
return startsWithNoLookaheadToken(
|
|
node.test,
|
|
forbidFunctionClassAndDoExpr
|
|
);
|
|
case "UpdateExpression":
|
|
return (
|
|
!node.prefix &&
|
|
startsWithNoLookaheadToken(node.argument, forbidFunctionClassAndDoExpr)
|
|
);
|
|
case "BindExpression":
|
|
return (
|
|
node.object &&
|
|
startsWithNoLookaheadToken(node.object, forbidFunctionClassAndDoExpr)
|
|
);
|
|
case "SequenceExpression":
|
|
return startsWithNoLookaheadToken(
|
|
node.expressions[0],
|
|
forbidFunctionClassAndDoExpr
|
|
);
|
|
case "TSAsExpression":
|
|
return startsWithNoLookaheadToken(
|
|
node.expression,
|
|
forbidFunctionClassAndDoExpr
|
|
);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getLeftMost(node) {
|
|
if (node.left) {
|
|
return getLeftMost(node.left);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function getAlignmentSize(value, tabWidth, startIndex) {
|
|
startIndex = startIndex || 0;
|
|
|
|
let size = 0;
|
|
for (let i = startIndex; i < value.length; ++i) {
|
|
if (value[i] === "\t") {
|
|
// Tabs behave in a way that they are aligned to the nearest
|
|
// multiple of tabWidth:
|
|
// 0 -> 4, 1 -> 4, 2 -> 4, 3 -> 4
|
|
// 4 -> 8, 5 -> 8, 6 -> 8, 7 -> 8 ...
|
|
size = size + tabWidth - (size % tabWidth);
|
|
} else {
|
|
size++;
|
|
}
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
function getIndentSize(value, tabWidth) {
|
|
const lastNewlineIndex = value.lastIndexOf("\n");
|
|
if (lastNewlineIndex === -1) {
|
|
return 0;
|
|
}
|
|
|
|
return getAlignmentSize(
|
|
// All the leading whitespaces
|
|
value.slice(lastNewlineIndex + 1).match(/^[ \t]*/)[0],
|
|
tabWidth
|
|
);
|
|
}
|
|
|
|
function getPreferredQuote(raw, preferredQuote) {
|
|
// `rawContent` is the string exactly like it appeared in the input source
|
|
// code, without its enclosing quotes.
|
|
const rawContent = raw.slice(1, -1);
|
|
|
|
const double = { quote: '"', regex: /"/g };
|
|
const single = { quote: "'", regex: /'/g };
|
|
|
|
const preferred = preferredQuote === "'" ? single : double;
|
|
const alternate = preferred === single ? double : single;
|
|
|
|
let result = preferred.quote;
|
|
|
|
// If `rawContent` contains at least one of the quote preferred for enclosing
|
|
// the string, we might want to enclose with the alternate quote instead, to
|
|
// minimize the number of escaped quotes.
|
|
if (
|
|
rawContent.includes(preferred.quote) ||
|
|
rawContent.includes(alternate.quote)
|
|
) {
|
|
const numPreferredQuotes = (rawContent.match(preferred.regex) || []).length;
|
|
const numAlternateQuotes = (rawContent.match(alternate.regex) || []).length;
|
|
|
|
result =
|
|
numPreferredQuotes > numAlternateQuotes
|
|
? alternate.quote
|
|
: preferred.quote;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function printString(raw, options, isDirectiveLiteral) {
|
|
// `rawContent` is the string exactly like it appeared in the input source
|
|
// code, without its enclosing quotes.
|
|
const rawContent = raw.slice(1, -1);
|
|
|
|
// Check for the alternate quote, to determine if we're allowed to swap
|
|
// the quotes on a DirectiveLiteral.
|
|
const canChangeDirectiveQuotes =
|
|
!rawContent.includes('"') && !rawContent.includes("'");
|
|
|
|
const enclosingQuote =
|
|
options.parser === "json"
|
|
? '"'
|
|
: options.__isInHtmlAttribute
|
|
? "'"
|
|
: getPreferredQuote(raw, options.singleQuote ? "'" : '"');
|
|
|
|
// Directives are exact code unit sequences, which means that you can't
|
|
// change the escape sequences they use.
|
|
// See https://github.com/prettier/prettier/issues/1555
|
|
// and https://tc39.github.io/ecma262/#directive-prologue
|
|
if (isDirectiveLiteral) {
|
|
if (canChangeDirectiveQuotes) {
|
|
return enclosingQuote + rawContent + enclosingQuote;
|
|
}
|
|
return raw;
|
|
}
|
|
|
|
// It might sound unnecessary to use `makeString` even if the string already
|
|
// is enclosed with `enclosingQuote`, but it isn't. The string could contain
|
|
// unnecessary escapes (such as in `"\'"`). Always using `makeString` makes
|
|
// sure that we consistently output the minimum amount of escaped quotes.
|
|
return makeString(
|
|
rawContent,
|
|
enclosingQuote,
|
|
!(
|
|
options.parser === "css" ||
|
|
options.parser === "less" ||
|
|
options.parser === "scss" ||
|
|
options.parentParser === "html" ||
|
|
options.parentParser === "vue" ||
|
|
options.parentParser === "angular" ||
|
|
options.parentParser === "lwc"
|
|
)
|
|
);
|
|
}
|
|
|
|
function makeString(rawContent, enclosingQuote, unescapeUnnecessaryEscapes) {
|
|
const otherQuote = enclosingQuote === '"' ? "'" : '"';
|
|
|
|
// Matches _any_ escape and unescaped quotes (both single and double).
|
|
const regex = /\\([\s\S])|(['"])/g;
|
|
|
|
// Escape and unescape single and double quotes as needed to be able to
|
|
// enclose `rawContent` with `enclosingQuote`.
|
|
const newContent = rawContent.replace(regex, (match, escaped, quote) => {
|
|
// If we matched an escape, and the escaped character is a quote of the
|
|
// other type than we intend to enclose the string with, there's no need for
|
|
// it to be escaped, so return it _without_ the backslash.
|
|
if (escaped === otherQuote) {
|
|
return escaped;
|
|
}
|
|
|
|
// If we matched an unescaped quote and it is of the _same_ type as we
|
|
// intend to enclose the string with, it must be escaped, so return it with
|
|
// a backslash.
|
|
if (quote === enclosingQuote) {
|
|
return "\\" + quote;
|
|
}
|
|
|
|
if (quote) {
|
|
return quote;
|
|
}
|
|
|
|
// Unescape any unnecessarily escaped character.
|
|
// Adapted from https://github.com/eslint/eslint/blob/de0b4ad7bd820ade41b1f606008bea68683dc11a/lib/rules/no-useless-escape.js#L27
|
|
return unescapeUnnecessaryEscapes &&
|
|
/^[^\\nrvtbfux\r\n\u2028\u2029"'0-7]$/.test(escaped)
|
|
? escaped
|
|
: "\\" + escaped;
|
|
});
|
|
|
|
return enclosingQuote + newContent + enclosingQuote;
|
|
}
|
|
|
|
function printNumber(rawNumber) {
|
|
return (
|
|
rawNumber
|
|
.toLowerCase()
|
|
// Remove unnecessary plus and zeroes from scientific notation.
|
|
.replace(/^([+-]?[\d.]+e)(?:\+|(-))?0*(\d)/, "$1$2$3")
|
|
// Remove unnecessary scientific notation (1e0).
|
|
.replace(/^([+-]?[\d.]+)e[+-]?0+$/, "$1")
|
|
// Make sure numbers always start with a digit.
|
|
.replace(/^([+-])?\./, "$10.")
|
|
// Remove extraneous trailing decimal zeroes.
|
|
.replace(/(\.\d+?)0+(?=e|$)/, "$1")
|
|
// Remove trailing dot.
|
|
.replace(/\.(?=e|$)/, "")
|
|
);
|
|
}
|
|
|
|
function getMaxContinuousCount(str, target) {
|
|
const results = str.match(
|
|
new RegExp(`(${escapeStringRegexp(target)})+`, "g")
|
|
);
|
|
|
|
if (results === null) {
|
|
return 0;
|
|
}
|
|
|
|
return results.reduce(
|
|
(maxCount, result) => Math.max(maxCount, result.length / target.length),
|
|
0
|
|
);
|
|
}
|
|
|
|
function getStringWidth(text) {
|
|
if (!text) {
|
|
return 0;
|
|
}
|
|
|
|
// shortcut to avoid needless string `RegExp`s, replacements, and allocations within `string-width`
|
|
if (!notAsciiRegex.test(text)) {
|
|
return text.length;
|
|
}
|
|
|
|
return stringWidth(text);
|
|
}
|
|
|
|
function hasIgnoreComment(path) {
|
|
const node = path.getValue();
|
|
return hasNodeIgnoreComment(node);
|
|
}
|
|
|
|
function hasNodeIgnoreComment(node) {
|
|
return (
|
|
node &&
|
|
node.comments &&
|
|
node.comments.length > 0 &&
|
|
node.comments.some(comment => comment.value.trim() === "prettier-ignore")
|
|
);
|
|
}
|
|
|
|
function matchAncestorTypes(path, types, index) {
|
|
index = index || 0;
|
|
types = types.slice();
|
|
while (types.length) {
|
|
const parent = path.getParentNode(index);
|
|
const type = types.shift();
|
|
if (!parent || parent.type !== type) {
|
|
return false;
|
|
}
|
|
index++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function addCommentHelper(node, comment) {
|
|
const comments = node.comments || (node.comments = []);
|
|
comments.push(comment);
|
|
comment.printed = false;
|
|
|
|
// For some reason, TypeScript parses `// x` inside of JSXText as a comment
|
|
// We already "print" it via the raw text, we don't need to re-print it as a
|
|
// comment
|
|
if (node.type === "JSXText") {
|
|
comment.printed = true;
|
|
}
|
|
}
|
|
|
|
function addLeadingComment(node, comment) {
|
|
comment.leading = true;
|
|
comment.trailing = false;
|
|
addCommentHelper(node, comment);
|
|
}
|
|
|
|
function addDanglingComment(node, comment) {
|
|
comment.leading = false;
|
|
comment.trailing = false;
|
|
addCommentHelper(node, comment);
|
|
}
|
|
|
|
function addTrailingComment(node, comment) {
|
|
comment.leading = false;
|
|
comment.trailing = true;
|
|
addCommentHelper(node, comment);
|
|
}
|
|
|
|
function isWithinParentArrayProperty(path, propertyName) {
|
|
const node = path.getValue();
|
|
const parent = path.getParentNode();
|
|
|
|
if (parent == null) {
|
|
return false;
|
|
}
|
|
|
|
if (!Array.isArray(parent[propertyName])) {
|
|
return false;
|
|
}
|
|
|
|
const key = path.getName();
|
|
return parent[propertyName][key] === node;
|
|
}
|
|
|
|
function replaceEndOfLineWith(text, replacement) {
|
|
const parts = [];
|
|
for (const part of text.split("\n")) {
|
|
if (parts.length !== 0) {
|
|
parts.push(replacement);
|
|
}
|
|
parts.push(part);
|
|
}
|
|
return parts;
|
|
}
|
|
|
|
module.exports = {
|
|
replaceEndOfLineWith,
|
|
getStringWidth,
|
|
getMaxContinuousCount,
|
|
getPrecedence,
|
|
shouldFlatten,
|
|
isBitwiseOperator,
|
|
isExportDeclaration,
|
|
getParentExportDeclaration,
|
|
getPenultimate,
|
|
getLast,
|
|
getNextNonSpaceNonCommentCharacterIndexWithStartIndex,
|
|
getNextNonSpaceNonCommentCharacterIndex,
|
|
getNextNonSpaceNonCommentCharacter,
|
|
skip,
|
|
skipWhitespace,
|
|
skipSpaces,
|
|
skipToLineEnd,
|
|
skipEverythingButNewLine,
|
|
skipInlineComment,
|
|
skipTrailingComment,
|
|
skipNewline,
|
|
isNextLineEmptyAfterIndex,
|
|
isNextLineEmpty,
|
|
isPreviousLineEmpty,
|
|
hasNewline,
|
|
hasNewlineInRange,
|
|
hasSpaces,
|
|
setLocStart,
|
|
setLocEnd,
|
|
startsWithNoLookaheadToken,
|
|
getAlignmentSize,
|
|
getIndentSize,
|
|
getPreferredQuote,
|
|
printString,
|
|
printNumber,
|
|
hasIgnoreComment,
|
|
hasNodeIgnoreComment,
|
|
makeString,
|
|
matchAncestorTypes,
|
|
addLeadingComment,
|
|
addDanglingComment,
|
|
addTrailingComment,
|
|
isWithinParentArrayProperty
|
|
};
|