2018-04-26 13:38:57 -03:00
|
|
|
"use strict";
|
|
|
|
|
|
2020-11-30 12:29:48 +08:00
|
|
|
const assert = require("assert");
|
2018-04-26 13:38:57 -03:00
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 12:29:48 +08:00
|
|
|
function findNodeAtOffset(
|
|
|
|
|
node,
|
|
|
|
|
offset,
|
|
|
|
|
options,
|
|
|
|
|
predicate,
|
|
|
|
|
parentNodes = [],
|
|
|
|
|
type
|
|
|
|
|
) {
|
|
|
|
|
const { locStart, locEnd } = options;
|
|
|
|
|
const start = locStart(node);
|
|
|
|
|
const end = locEnd(node);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
offset > end ||
|
|
|
|
|
offset < start ||
|
|
|
|
|
(type === "rangeEnd" && offset === start) ||
|
|
|
|
|
(type === "rangeStart" && offset === end)
|
|
|
|
|
) {
|
2020-05-27 11:48:02 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
|
2020-05-27 11:48:02 +03:00
|
|
|
for (const childNode of comments.getSortedChildNodes(node, options)) {
|
|
|
|
|
const childResult = findNodeAtOffset(
|
|
|
|
|
childNode,
|
|
|
|
|
offset,
|
|
|
|
|
options,
|
|
|
|
|
predicate,
|
2020-11-30 12:29:48 +08:00
|
|
|
[node, ...parentNodes],
|
|
|
|
|
type
|
2020-05-27 11:48:02 +03:00
|
|
|
);
|
|
|
|
|
if (childResult) {
|
|
|
|
|
return childResult;
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
}
|
2020-05-27 11:48:02 +03:00
|
|
|
|
|
|
|
|
if (!predicate || predicate(node)) {
|
|
|
|
|
return {
|
|
|
|
|
node,
|
|
|
|
|
parentNodes,
|
|
|
|
|
};
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See https://www.ecma-international.org/ecma-262/5.1/#sec-A.5
|
2020-05-29 07:29:09 +03:00
|
|
|
function isJsSourceElement(type) {
|
|
|
|
|
return (
|
|
|
|
|
type === "Directive" ||
|
|
|
|
|
type === "TypeAlias" ||
|
|
|
|
|
type === "TSExportAssignment" ||
|
|
|
|
|
type.startsWith("Declare") ||
|
|
|
|
|
type.startsWith("TSDeclare") ||
|
|
|
|
|
type.endsWith("Statement") ||
|
|
|
|
|
type.endsWith("Declaration")
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-04-09 13:22:28 +08:00
|
|
|
|
|
|
|
|
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",
|
|
|
|
|
]);
|
2018-04-26 13:38:57 -03:00
|
|
|
function isSourceElement(opts, node) {
|
2020-08-21 09:43:24 +08:00
|
|
|
/* istanbul ignore next */
|
2021-02-21 08:44:16 +08:00
|
|
|
if (!node) {
|
2018-04-26 13:38:57 -03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
switch (opts.parser) {
|
|
|
|
|
case "flow":
|
2018-12-27 08:05:19 -05:00
|
|
|
case "babel":
|
2020-01-29 20:11:16 +08:00
|
|
|
case "babel-flow":
|
|
|
|
|
case "babel-ts":
|
2018-04-26 13:38:57 -03:00
|
|
|
case "typescript":
|
2020-10-13 09:14:02 +08:00
|
|
|
case "espree":
|
2020-11-05 14:08:37 +08:00
|
|
|
case "meriyah":
|
2020-05-29 07:29:09 +03:00
|
|
|
return isJsSourceElement(node.type);
|
2018-04-26 13:38:57 -03:00
|
|
|
case "json":
|
2020-04-09 13:22:28 +08:00
|
|
|
return jsonSourceElements.has(node.type);
|
2018-04-26 13:38:57 -03:00
|
|
|
case "graphql":
|
2020-04-09 13:22:28 +08:00
|
|
|
return graphqlSourceElements.has(node.kind);
|
2018-07-22 15:58:29 +08:00
|
|
|
case "vue":
|
|
|
|
|
return node.tag !== "root";
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function calculateRange(text, opts, ast) {
|
2020-11-30 12:29:48 +08:00
|
|
|
let { rangeStart: start, rangeEnd: end, locStart, locEnd } = opts;
|
|
|
|
|
assert.ok(end > start);
|
2018-04-26 13:38:57 -03:00
|
|
|
// 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.
|
2020-11-30 12:29:48 +08:00
|
|
|
const firstNonWhitespaceCharacterIndex = text.slice(start, end).search(/\S/);
|
|
|
|
|
const isAllWhitespace = firstNonWhitespaceCharacterIndex === -1;
|
|
|
|
|
if (!isAllWhitespace) {
|
|
|
|
|
start += firstNonWhitespaceCharacterIndex;
|
|
|
|
|
for (; end > start; --end) {
|
|
|
|
|
if (/\S/.test(text[end - 1])) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const startNodeAndParents = findNodeAtOffset(
|
|
|
|
|
ast,
|
2020-11-30 12:29:48 +08:00
|
|
|
start,
|
2018-04-26 13:38:57 -03:00
|
|
|
opts,
|
2020-11-30 12:29:48 +08:00
|
|
|
(node) => isSourceElement(opts, node),
|
|
|
|
|
[],
|
|
|
|
|
"rangeStart"
|
2018-04-26 13:38:57 -03:00
|
|
|
);
|
2020-11-30 12:29:48 +08:00
|
|
|
const endNodeAndParents =
|
|
|
|
|
// No need find Node at `end`, it will be the same as `startNodeAndParents`
|
|
|
|
|
isAllWhitespace
|
|
|
|
|
? startNodeAndParents
|
|
|
|
|
: findNodeAtOffset(
|
|
|
|
|
ast,
|
|
|
|
|
end,
|
|
|
|
|
opts,
|
|
|
|
|
(node) => isSourceElement(opts, node),
|
|
|
|
|
[],
|
|
|
|
|
"rangeEnd"
|
|
|
|
|
);
|
2018-04-26 13:38:57 -03:00
|
|
|
if (!startNodeAndParents || !endNodeAndParents) {
|
|
|
|
|
return {
|
|
|
|
|
rangeStart: 0,
|
|
|
|
|
rangeEnd: 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-27 11:48:02 +03:00
|
|
|
const { startNode, endNode } = findSiblingAncestors(
|
2018-04-26 13:38:57 -03:00
|
|
|
startNodeAndParents,
|
|
|
|
|
endNodeAndParents,
|
|
|
|
|
opts
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
2020-11-30 12:29:48 +08:00
|
|
|
rangeStart: Math.min(locStart(startNode), locStart(endNode)),
|
|
|
|
|
rangeEnd: Math.max(locEnd(startNode), locEnd(endNode)),
|
2018-04-26 13:38:57 -03:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
calculateRange,
|
|
|
|
|
findNodeAtOffset,
|
|
|
|
|
};
|