391 lines
9.2 KiB
JavaScript
391 lines
9.2 KiB
JavaScript
"use strict";
|
|
|
|
function isExportDeclaration(node) {
|
|
if (node) {
|
|
switch (node.type) {
|
|
case "ExportDeclaration":
|
|
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 getLast(arr) {
|
|
if (arr.length > 0) {
|
|
return arr[arr.length - 1];
|
|
}
|
|
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) {
|
|
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 isNextLineEmpty(text, node) {
|
|
let oldIdx = null;
|
|
let idx = locEnd(node);
|
|
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 getNextNonSpaceNonCommentCharacter(text, node) {
|
|
let oldIdx = null;
|
|
let idx = locEnd(node);
|
|
while (idx !== oldIdx) {
|
|
oldIdx = idx;
|
|
idx = skipSpaces(text, idx);
|
|
idx = skipInlineComment(text, idx);
|
|
idx = skipTrailingComment(text, idx);
|
|
idx = skipNewline(text, idx);
|
|
}
|
|
return text.charAt(idx);
|
|
}
|
|
|
|
function hasSpaces(text, index, opts) {
|
|
opts = opts || {};
|
|
const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts);
|
|
return idx !== index;
|
|
}
|
|
|
|
function locStart(node) {
|
|
if (node.decorators && node.decorators.length > 0) {
|
|
return locStart(node.decorators[0]);
|
|
}
|
|
if (node.range) {
|
|
return node.range[0];
|
|
}
|
|
if (typeof node.start === "number") {
|
|
return node.start;
|
|
}
|
|
if (node.source) {
|
|
return lineColumnToIndex(node.source.start, node.source.input.css) - 1;
|
|
}
|
|
}
|
|
|
|
function locEnd(node) {
|
|
if (node.range) {
|
|
return node.range[1];
|
|
}
|
|
if (typeof node.end === "number") {
|
|
return node.end;
|
|
}
|
|
if (node.source) {
|
|
return lineColumnToIndex(node.source.end, node.source.input.css);
|
|
}
|
|
}
|
|
|
|
// Super inefficient, needs to be cached.
|
|
function lineColumnToIndex(lineColumn, text) {
|
|
let index = 0;
|
|
for (let i = 0; i < lineColumn.line - 1; ++i) {
|
|
index = text.indexOf("\n", index) + 1;
|
|
if (index === -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
return index + lineColumn.column;
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
// Tests if an expression starts with `{`, or (if forbidFunctionAndClass holds) `function` or `class`.
|
|
// Will be overzealous if there's already necessary grouping parentheses.
|
|
function startsWithNoLookaheadToken(node, forbidFunctionAndClass) {
|
|
node = getLeftMost(node);
|
|
switch (node.type) {
|
|
case "FunctionExpression":
|
|
case "ClassExpression":
|
|
return forbidFunctionAndClass;
|
|
case "ObjectExpression":
|
|
return true;
|
|
case "MemberExpression":
|
|
return startsWithNoLookaheadToken(node.object, forbidFunctionAndClass);
|
|
case "TaggedTemplateExpression":
|
|
if (node.tag.type === "FunctionExpression") {
|
|
// IIFEs are always already parenthesized
|
|
return false;
|
|
}
|
|
return startsWithNoLookaheadToken(node.tag, forbidFunctionAndClass);
|
|
case "CallExpression":
|
|
if (node.callee.type === "FunctionExpression") {
|
|
// IIFEs are always already parenthesized
|
|
return false;
|
|
}
|
|
return startsWithNoLookaheadToken(node.callee, forbidFunctionAndClass);
|
|
case "ConditionalExpression":
|
|
return startsWithNoLookaheadToken(node.test, forbidFunctionAndClass);
|
|
case "UpdateExpression":
|
|
return (
|
|
!node.prefix &&
|
|
startsWithNoLookaheadToken(node.argument, forbidFunctionAndClass)
|
|
);
|
|
case "BindExpression":
|
|
return (
|
|
node.object &&
|
|
startsWithNoLookaheadToken(node.object, forbidFunctionAndClass)
|
|
);
|
|
case "SequenceExpression":
|
|
return startsWithNoLookaheadToken(
|
|
node.expressions[0],
|
|
forbidFunctionAndClass
|
|
);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getLeftMost(node) {
|
|
if (node.left) {
|
|
return getLeftMost(node.left);
|
|
} else {
|
|
return node;
|
|
}
|
|
}
|
|
|
|
function hasBlockComments(node) {
|
|
return node.comments && node.comments.some(isBlockComment);
|
|
}
|
|
|
|
function isBlockComment(comment) {
|
|
return comment.type === "Block" || comment.type === "CommentBlock";
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
module.exports = {
|
|
getPrecedence,
|
|
isExportDeclaration,
|
|
getParentExportDeclaration,
|
|
getPenultimate,
|
|
getLast,
|
|
getNextNonSpaceNonCommentCharacter,
|
|
skipWhitespace,
|
|
skipSpaces,
|
|
skipNewline,
|
|
isNextLineEmpty,
|
|
isPreviousLineEmpty,
|
|
hasNewline,
|
|
hasNewlineInRange,
|
|
hasSpaces,
|
|
locStart,
|
|
locEnd,
|
|
setLocStart,
|
|
setLocEnd,
|
|
startsWithNoLookaheadToken,
|
|
hasBlockComments,
|
|
isBlockComment,
|
|
getAlignmentSize
|
|
};
|