2022-03-02 13:44:00 +08:00
|
|
|
import assert from "node:assert";
|
2023-12-17 19:34:12 +00:00
|
|
|
|
2023-02-10 15:33:13 +08:00
|
|
|
import { getSortedChildNodes } from "./comments/attach.js";
|
2018-04-26 13:38:57 -03:00
|
|
|
|
2021-03-12 06:24:45 +08:00
|
|
|
const isJsonParser = ({ parser }) =>
|
|
|
|
|
parser === "json" || parser === "json5" || parser === "json-stringify";
|
|
|
|
|
|
|
|
|
|
function findCommonAncestor(startNodeAndParents, endNodeAndParents) {
|
|
|
|
|
const startNodeAndAncestors = [
|
|
|
|
|
startNodeAndParents.node,
|
|
|
|
|
...startNodeAndParents.parentNodes,
|
|
|
|
|
];
|
|
|
|
|
const endNodeAndAncestors = new Set([
|
|
|
|
|
endNodeAndParents.node,
|
|
|
|
|
...endNodeAndParents.parentNodes,
|
|
|
|
|
]);
|
|
|
|
|
return startNodeAndAncestors.find(
|
2023-07-05 21:50:05 +09:00
|
|
|
(node) =>
|
|
|
|
|
jsonSourceElements.has(node.type) && endNodeAndAncestors.has(node),
|
2021-03-12 06:24:45 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-25 01:19:11 +09:00
|
|
|
function dropRootParents(parents) {
|
|
|
|
|
let lastParentIndex = parents.length - 1;
|
|
|
|
|
for (;;) {
|
|
|
|
|
const parent = parents[lastParentIndex];
|
2022-11-03 14:23:07 +08:00
|
|
|
if (parent?.type === "Program" || parent?.type === "File") {
|
2021-03-25 01:19:11 +09:00
|
|
|
lastParentIndex--;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return parents.slice(0, lastParentIndex + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function findSiblingAncestors(
|
|
|
|
|
startNodeAndParents,
|
|
|
|
|
endNodeAndParents,
|
2023-07-05 21:50:05 +09:00
|
|
|
{ locStart, locEnd },
|
2021-03-25 01:19:11 +09:00
|
|
|
) {
|
2018-04-26 13:38:57 -03:00
|
|
|
let resultStartNode = startNodeAndParents.node;
|
|
|
|
|
let resultEndNode = endNodeAndParents.node;
|
|
|
|
|
|
|
|
|
|
if (resultStartNode === resultEndNode) {
|
|
|
|
|
return {
|
|
|
|
|
startNode: resultStartNode,
|
2020-03-21 22:07:52 +02:00
|
|
|
endNode: resultEndNode,
|
2018-04-26 13:38:57 -03:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-25 01:19:11 +09:00
|
|
|
const startNodeStart = locStart(startNodeAndParents.node);
|
|
|
|
|
for (const endParent of dropRootParents(endNodeAndParents.parentNodes)) {
|
|
|
|
|
if (locStart(endParent) >= startNodeStart) {
|
2018-04-26 13:38:57 -03:00
|
|
|
resultEndNode = endParent;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-25 01:19:11 +09:00
|
|
|
const endNodeEnd = locEnd(endNodeAndParents.node);
|
|
|
|
|
for (const startParent of dropRootParents(startNodeAndParents.parentNodes)) {
|
|
|
|
|
if (locEnd(startParent) <= endNodeEnd) {
|
2018-04-26 13:38:57 -03:00
|
|
|
resultStartNode = startParent;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-07-25 12:28:24 +03:00
|
|
|
if (resultStartNode === resultEndNode) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
startNode: resultStartNode,
|
2020-03-21 22:07:52 +02:00
|
|
|
endNode: resultEndNode,
|
2018-04-26 13:38:57 -03:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 12:29:48 +08:00
|
|
|
function findNodeAtOffset(
|
|
|
|
|
node,
|
|
|
|
|
offset,
|
|
|
|
|
options,
|
|
|
|
|
predicate,
|
|
|
|
|
parentNodes = [],
|
2023-07-05 21:50:05 +09:00
|
|
|
type,
|
2020-11-30 12:29:48 +08:00
|
|
|
) {
|
|
|
|
|
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
|
|
|
|
2022-02-23 20:16:30 +08:00
|
|
|
for (const childNode of getSortedChildNodes(node, options)) {
|
2020-05-27 11:48:02 +03:00
|
|
|
const childResult = findNodeAtOffset(
|
|
|
|
|
childNode,
|
|
|
|
|
offset,
|
|
|
|
|
options,
|
|
|
|
|
predicate,
|
2020-11-30 12:29:48 +08:00
|
|
|
[node, ...parentNodes],
|
2023-07-05 21:50:05 +09:00
|
|
|
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
|
|
|
|
2021-03-10 15:12:27 +02:00
|
|
|
if (!predicate || predicate(node, parentNodes[0])) {
|
2020-05-27 11:48:02 +03:00
|
|
|
return {
|
|
|
|
|
node,
|
|
|
|
|
parentNodes,
|
|
|
|
|
};
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See https://www.ecma-international.org/ecma-262/5.1/#sec-A.5
|
2021-03-10 15:12:27 +02:00
|
|
|
function isJsSourceElement(type, parentType) {
|
2020-05-29 07:29:09 +03:00
|
|
|
return (
|
2021-03-10 15:12:27 +02:00
|
|
|
parentType !== "DeclareExportDeclaration" &&
|
|
|
|
|
type !== "TypeParameterDeclaration" &&
|
|
|
|
|
(type === "Directive" ||
|
|
|
|
|
type === "TypeAlias" ||
|
|
|
|
|
type === "TSExportAssignment" ||
|
|
|
|
|
type.startsWith("Declare") ||
|
|
|
|
|
type.startsWith("TSDeclare") ||
|
|
|
|
|
type.endsWith("Statement") ||
|
|
|
|
|
type.endsWith("Declaration"))
|
2020-05-29 07:29:09 +03:00
|
|
|
);
|
|
|
|
|
}
|
2020-04-09 13:22:28 +08:00
|
|
|
|
|
|
|
|
const jsonSourceElements = new Set([
|
2023-01-14 10:29:07 +08:00
|
|
|
"JsonRoot",
|
2020-04-09 13:22:28 +08:00
|
|
|
"ObjectExpression",
|
|
|
|
|
"ArrayExpression",
|
|
|
|
|
"StringLiteral",
|
|
|
|
|
"NumericLiteral",
|
|
|
|
|
"BooleanLiteral",
|
|
|
|
|
"NullLiteral",
|
2021-03-12 06:24:45 +08:00
|
|
|
"UnaryExpression",
|
|
|
|
|
"TemplateLiteral",
|
2020-04-09 13:22:28 +08:00
|
|
|
]);
|
|
|
|
|
const graphqlSourceElements = new Set([
|
|
|
|
|
"OperationDefinition",
|
|
|
|
|
"FragmentDefinition",
|
|
|
|
|
"VariableDefinition",
|
|
|
|
|
"TypeExtensionDefinition",
|
|
|
|
|
"ObjectTypeDefinition",
|
|
|
|
|
"FieldDefinition",
|
|
|
|
|
"DirectiveDefinition",
|
|
|
|
|
"EnumTypeDefinition",
|
|
|
|
|
"EnumValueDefinition",
|
|
|
|
|
"InputValueDefinition",
|
|
|
|
|
"InputObjectTypeDefinition",
|
|
|
|
|
"SchemaDefinition",
|
|
|
|
|
"OperationTypeDefinition",
|
|
|
|
|
"InterfaceTypeDefinition",
|
|
|
|
|
"UnionTypeDefinition",
|
|
|
|
|
"ScalarTypeDefinition",
|
|
|
|
|
]);
|
2021-03-10 15:12:27 +02:00
|
|
|
function isSourceElement(opts, node, parentNode) {
|
2022-10-22 23:33:08 +08:00
|
|
|
/* c8 ignore next 3 */
|
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":
|
2022-01-28 15:25:30 +08:00
|
|
|
case "acorn":
|
2020-10-13 09:14:02 +08:00
|
|
|
case "espree":
|
2020-11-05 14:08:37 +08:00
|
|
|
case "meriyah":
|
2021-05-19 09:22:52 +08:00
|
|
|
case "__babel_estree":
|
2022-11-03 14:23:07 +08:00
|
|
|
return isJsSourceElement(node.type, parentNode?.type);
|
2018-04-26 13:38:57 -03:00
|
|
|
case "json":
|
2021-03-12 06:24:45 +08:00
|
|
|
case "json5":
|
|
|
|
|
case "json-stringify":
|
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,
|
2021-03-10 15:12:27 +02:00
|
|
|
(node, parentNode) => isSourceElement(opts, node, parentNode),
|
2020-11-30 12:29:48 +08:00
|
|
|
[],
|
2023-07-05 21:50:05 +09:00
|
|
|
"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),
|
|
|
|
|
[],
|
2023-07-05 21:50:05 +09:00
|
|
|
"rangeEnd",
|
2020-11-30 12:29:48 +08:00
|
|
|
);
|
2018-04-26 13:38:57 -03:00
|
|
|
if (!startNodeAndParents || !endNodeAndParents) {
|
|
|
|
|
return {
|
|
|
|
|
rangeStart: 0,
|
2020-03-21 22:07:52 +02:00
|
|
|
rangeEnd: 0,
|
2018-04-26 13:38:57 -03:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 06:24:45 +08:00
|
|
|
let startNode;
|
|
|
|
|
let endNode;
|
|
|
|
|
if (isJsonParser(opts)) {
|
|
|
|
|
const commonAncestor = findCommonAncestor(
|
|
|
|
|
startNodeAndParents,
|
2023-07-05 21:50:05 +09:00
|
|
|
endNodeAndParents,
|
2021-03-12 06:24:45 +08:00
|
|
|
);
|
|
|
|
|
startNode = commonAncestor;
|
|
|
|
|
endNode = commonAncestor;
|
|
|
|
|
} else {
|
|
|
|
|
({ startNode, endNode } = findSiblingAncestors(
|
|
|
|
|
startNodeAndParents,
|
|
|
|
|
endNodeAndParents,
|
2023-07-05 21:50:05 +09:00
|
|
|
opts,
|
2021-03-12 06:24:45 +08:00
|
|
|
));
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-23 20:16:30 +08:00
|
|
|
export { calculateRange, findNodeAtOffset };
|