Co-authored-by: Chris Brody <chris.brody+brodybits@gmail.com>
189 lines
4.3 KiB
JavaScript
189 lines
4.3 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 = []) {
|
|
if (offset < options.locStart(node) || offset > options.locEnd(node)) {
|
|
return;
|
|
}
|
|
|
|
for (const childNode of comments.getSortedChildNodes(node, options)) {
|
|
const childResult = findNodeAtOffset(
|
|
childNode,
|
|
offset,
|
|
options,
|
|
predicate,
|
|
[node, ...parentNodes]
|
|
);
|
|
if (childResult) {
|
|
return childResult;
|
|
}
|
|
}
|
|
|
|
if (!predicate || predicate(node)) {
|
|
return {
|
|
node,
|
|
parentNodes,
|
|
};
|
|
}
|
|
}
|
|
|
|
// See https://www.ecma-international.org/ecma-262/5.1/#sec-A.5
|
|
function isJsSourceElement(type) {
|
|
return (
|
|
type === "Directive" ||
|
|
type === "TypeAlias" ||
|
|
type === "TSExportAssignment" ||
|
|
type.startsWith("Declare") ||
|
|
type.startsWith("TSDeclare") ||
|
|
type.endsWith("Statement") ||
|
|
type.endsWith("Declaration")
|
|
);
|
|
}
|
|
|
|
const jsonSourceElements = new Set([
|
|
"ObjectExpression",
|
|
"ArrayExpression",
|
|
"StringLiteral",
|
|
"NumericLiteral",
|
|
"BooleanLiteral",
|
|
"NullLiteral",
|
|
]);
|
|
const graphqlSourceElements = new Set([
|
|
"OperationDefinition",
|
|
"FragmentDefinition",
|
|
"VariableDefinition",
|
|
"TypeExtensionDefinition",
|
|
"ObjectTypeDefinition",
|
|
"FieldDefinition",
|
|
"DirectiveDefinition",
|
|
"EnumTypeDefinition",
|
|
"EnumValueDefinition",
|
|
"InputValueDefinition",
|
|
"InputObjectTypeDefinition",
|
|
"SchemaDefinition",
|
|
"OperationTypeDefinition",
|
|
"InterfaceTypeDefinition",
|
|
"UnionTypeDefinition",
|
|
"ScalarTypeDefinition",
|
|
]);
|
|
function isSourceElement(opts, node) {
|
|
/* istanbul ignore next */
|
|
if (node == null) {
|
|
return false;
|
|
}
|
|
switch (opts.parser) {
|
|
case "flow":
|
|
case "babel":
|
|
case "babel-flow":
|
|
case "babel-ts":
|
|
case "typescript":
|
|
case "espree":
|
|
return isJsSourceElement(node.type);
|
|
case "json":
|
|
return jsonSourceElements.has(node.type);
|
|
case "graphql":
|
|
return graphqlSourceElements.has(node.kind);
|
|
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 (/\S/.test(text[endNonWhitespace - 1])) {
|
|
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 { startNode, endNode } = findSiblingAncestors(
|
|
startNodeAndParents,
|
|
endNodeAndParents,
|
|
opts
|
|
);
|
|
|
|
return {
|
|
rangeStart: Math.min(opts.locStart(startNode), opts.locStart(endNode)),
|
|
rangeEnd: Math.max(opts.locEnd(startNode), opts.locEnd(endNode)),
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
calculateRange,
|
|
findNodeAtOffset,
|
|
};
|