prettier/src/main/range-util.js

216 lines
5.2 KiB
JavaScript

"use strict";
const comments = require("./comments");
function findSiblingAncestors(startNodeAndParents, endNodeAndParents, opts) {
let resultStartNode = startNodeAndParents.node;
let resultEndNode = endNodeAndParents.node;
if (resultStartNode === resultEndNode) {
return {
startNode: resultStartNode,
endNode: resultEndNode
};
}
for (const endParent of endNodeAndParents.parentNodes) {
if (
endParent.type !== "Program" &&
endParent.type !== "File" &&
opts.locStart(endParent) >= opts.locStart(startNodeAndParents.node)
) {
resultEndNode = endParent;
} else {
break;
}
}
for (const startParent of startNodeAndParents.parentNodes) {
if (
startParent.type !== "Program" &&
startParent.type !== "File" &&
opts.locEnd(startParent) <= opts.locEnd(endNodeAndParents.node)
) {
resultStartNode = startParent;
} else {
break;
}
}
return {
startNode: resultStartNode,
endNode: resultEndNode
};
}
function findNodeAtOffset(node, offset, options, predicate, parentNodes) {
predicate = predicate || (() => true);
parentNodes = parentNodes || [];
const start = options.locStart(node, options.locStart);
const end = options.locEnd(node, options.locEnd);
if (start <= offset && offset <= end) {
for (const childNode of comments.getSortedChildNodes(node, options)) {
const childResult = findNodeAtOffset(
childNode,
offset,
options,
predicate,
[node].concat(parentNodes)
);
if (childResult) {
return childResult;
}
}
if (predicate(node)) {
return {
node: node,
parentNodes: parentNodes
};
}
}
}
// See https://www.ecma-international.org/ecma-262/5.1/#sec-A.5
function isSourceElement(opts, node) {
if (node == null) {
return false;
}
// JS and JS like to avoid repetitions
const jsSourceElements = [
"FunctionDeclaration",
"BlockStatement",
"BreakStatement",
"ContinueStatement",
"DebuggerStatement",
"DoWhileStatement",
"EmptyStatement",
"ExpressionStatement",
"ForInStatement",
"ForStatement",
"IfStatement",
"LabeledStatement",
"ReturnStatement",
"SwitchStatement",
"ThrowStatement",
"TryStatement",
"VariableDeclaration",
"WhileStatement",
"WithStatement",
"ClassDeclaration", // ES 2015
"ImportDeclaration", // Module
"ExportDefaultDeclaration", // Module
"ExportNamedDeclaration", // Module
"ExportAllDeclaration", // Module
"TypeAlias", // Flow
"InterfaceDeclaration", // Flow, TypeScript
"TypeAliasDeclaration", // TypeScript
"ExportAssignment", // TypeScript
"ExportDeclaration" // TypeScript
];
const jsonSourceElements = [
"ObjectExpression",
"ArrayExpression",
"StringLiteral",
"NumericLiteral",
"BooleanLiteral",
"NullLiteral"
];
const graphqlSourceElements = [
"OperationDefinition",
"FragmentDefinition",
"VariableDefinition",
"TypeExtensionDefinition",
"ObjectTypeDefinition",
"FieldDefinition",
"DirectiveDefinition",
"EnumTypeDefinition",
"EnumValueDefinition",
"InputValueDefinition",
"InputObjectTypeDefinition",
"SchemaDefinition",
"OperationTypeDefinition",
"InterfaceTypeDefinition",
"UnionTypeDefinition",
"ScalarTypeDefinition"
];
switch (opts.parser) {
case "flow":
case "babel":
case "typescript":
return jsSourceElements.indexOf(node.type) > -1;
case "json":
return jsonSourceElements.indexOf(node.type) > -1;
case "graphql":
return graphqlSourceElements.indexOf(node.kind) > -1;
case "vue":
return node.tag !== "root";
}
return false;
}
function calculateRange(text, opts, ast) {
// Contract the range so that it has non-whitespace characters at its endpoints.
// This ensures we can format a range that doesn't end on a node.
const rangeStringOrig = text.slice(opts.rangeStart, opts.rangeEnd);
const startNonWhitespace = Math.max(
opts.rangeStart + rangeStringOrig.search(/\S/),
opts.rangeStart
);
let endNonWhitespace;
for (
endNonWhitespace = opts.rangeEnd;
endNonWhitespace > opts.rangeStart;
--endNonWhitespace
) {
if (text[endNonWhitespace - 1].match(/\S/)) {
break;
}
}
const startNodeAndParents = findNodeAtOffset(
ast,
startNonWhitespace,
opts,
node => isSourceElement(opts, node)
);
const endNodeAndParents = findNodeAtOffset(
ast,
endNonWhitespace,
opts,
node => isSourceElement(opts, node)
);
if (!startNodeAndParents || !endNodeAndParents) {
return {
rangeStart: 0,
rangeEnd: 0
};
}
const siblingAncestors = findSiblingAncestors(
startNodeAndParents,
endNodeAndParents,
opts
);
const { startNode, endNode } = siblingAncestors;
const rangeStart = Math.min(
opts.locStart(startNode, opts.locStart),
opts.locStart(endNode, opts.locStart)
);
const rangeEnd = Math.max(
opts.locEnd(startNode, opts.locEnd),
opts.locEnd(endNode, opts.locEnd)
);
return {
rangeStart: rangeStart,
rangeEnd: rangeEnd
};
}
module.exports = {
calculateRange,
findNodeAtOffset
};