2025-09-24 08:24:17 +08:00
|
|
|
import * as assert from "#universal/assert";
|
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 }) =>
|
2024-01-02 10:59:34 +08:00
|
|
|
parser === "json" ||
|
|
|
|
|
parser === "json5" ||
|
|
|
|
|
parser === "jsonc" ||
|
|
|
|
|
parser === "json-stringify";
|
2021-03-12 06:24:45 +08:00
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
function findCommonAncestor(startNodeAndAncestors, endNodeAndAncestors) {
|
|
|
|
|
endNodeAndAncestors = new Set(endNodeAndAncestors);
|
2021-03-12 06:24:45 +08:00
|
|
|
return startNodeAndAncestors.find(
|
|
|
|
|
(node) =>
|
|
|
|
|
jsonSourceElements.has(node.type) && endNodeAndAncestors.has(node),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-25 01:19:11 +09:00
|
|
|
function dropRootParents(parents) {
|
2024-06-18 07:00:16 +08:00
|
|
|
const index = parents.findLastIndex(
|
|
|
|
|
(node) => node.type !== "Program" && node.type !== "File",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
return parents;
|
2021-03-25 01:19:11 +09:00
|
|
|
}
|
2024-06-18 07:00:16 +08:00
|
|
|
|
|
|
|
|
return parents.slice(0, index + 1);
|
2021-03-25 01:19:11 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function findSiblingAncestors(
|
2025-10-12 20:47:36 +08:00
|
|
|
startNodeAndAncestors,
|
|
|
|
|
endNodeAndAncestors,
|
2021-03-25 01:19:11 +09:00
|
|
|
{ locStart, locEnd },
|
|
|
|
|
) {
|
2025-10-12 20:47:36 +08:00
|
|
|
let [resultStartNode, ...startNodeAncestors] = startNodeAndAncestors;
|
|
|
|
|
let [resultEndNode, ...endNodeAncestors] = endNodeAndAncestors;
|
2018-04-26 13:38:57 -03:00
|
|
|
|
|
|
|
|
if (resultStartNode === resultEndNode) {
|
2025-10-12 20:47:36 +08:00
|
|
|
return [resultStartNode, resultEndNode];
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
const startNodeStart = locStart(resultStartNode);
|
|
|
|
|
for (const endAncestor of dropRootParents(endNodeAncestors)) {
|
|
|
|
|
if (locStart(endAncestor) >= startNodeStart) {
|
|
|
|
|
resultEndNode = endAncestor;
|
2018-04-26 13:38:57 -03:00
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
const endNodeEnd = locEnd(resultEndNode);
|
|
|
|
|
for (const startAncestor of dropRootParents(startNodeAncestors)) {
|
|
|
|
|
if (locEnd(startAncestor) <= endNodeEnd) {
|
|
|
|
|
resultStartNode = startAncestor;
|
2018-04-26 13:38:57 -03:00
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-07-25 12:28:24 +03:00
|
|
|
if (resultStartNode === resultEndNode) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
return [resultStartNode, resultEndNode];
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
|
2020-11-30 12:29:48 +08:00
|
|
|
function findNodeAtOffset(
|
|
|
|
|
node,
|
|
|
|
|
offset,
|
|
|
|
|
options,
|
|
|
|
|
predicate,
|
2025-10-12 20:47:36 +08:00
|
|
|
ancestors = [],
|
2020-11-30 12:29:48 +08:00
|
|
|
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
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
const nodeAndAncestors = [node, ...ancestors];
|
|
|
|
|
const childNodes = getSortedChildNodes(node, options, nodeAndAncestors);
|
|
|
|
|
for (const child of childNodes) {
|
|
|
|
|
const childAndAncestors = findNodeAtOffset(
|
|
|
|
|
child,
|
2020-05-27 11:48:02 +03:00
|
|
|
offset,
|
|
|
|
|
options,
|
|
|
|
|
predicate,
|
2025-10-12 20:47:36 +08:00
|
|
|
nodeAndAncestors,
|
2020-11-30 12:29:48 +08:00
|
|
|
type,
|
2020-05-27 11:48:02 +03:00
|
|
|
);
|
2025-10-12 20:47:36 +08:00
|
|
|
if (childAndAncestors) {
|
|
|
|
|
return childAndAncestors;
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
}
|
2020-05-27 11:48:02 +03:00
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
if (predicate(node, ancestors[0])) {
|
|
|
|
|
return nodeAndAncestors;
|
2020-05-27 11:48:02 +03:00
|
|
|
}
|
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":
|
2025-05-29 15:30:45 +08:00
|
|
|
case "hermes":
|
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":
|
2025-05-27 09:35:31 +08:00
|
|
|
case "oxc":
|
2025-05-27 13:32:23 +08:00
|
|
|
case "oxc-ts":
|
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":
|
2024-01-02 10:59:34 +08:00
|
|
|
case "jsonc":
|
2021-03-12 06:24:45 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
/**
|
|
|
|
|
@param {string} text
|
|
|
|
|
@param {*} opts
|
|
|
|
|
@param {*} ast
|
|
|
|
|
@returns {[number, number]}
|
|
|
|
|
*/
|
2018-04-26 13:38:57 -03:00
|
|
|
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.
|
2024-06-18 06:52:54 +08:00
|
|
|
const firstNonWhitespaceCharacterIndex = text.slice(start, end).search(/\S/u);
|
2020-11-30 12:29:48 +08:00
|
|
|
const isAllWhitespace = firstNonWhitespaceCharacterIndex === -1;
|
|
|
|
|
if (!isAllWhitespace) {
|
|
|
|
|
start += firstNonWhitespaceCharacterIndex;
|
|
|
|
|
for (; end > start; --end) {
|
2024-06-18 06:52:54 +08:00
|
|
|
if (/\S/u.test(text[end - 1])) {
|
2020-11-30 12:29:48 +08:00
|
|
|
break;
|
|
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
const startNodeAndAncestors = findNodeAtOffset(
|
2018-04-26 13:38:57 -03:00
|
|
|
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
|
|
|
[],
|
|
|
|
|
"rangeStart",
|
2018-04-26 13:38:57 -03:00
|
|
|
);
|
2025-10-12 20:47:36 +08:00
|
|
|
if (!startNodeAndAncestors) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const endNodeAndAncestors =
|
|
|
|
|
// No need find Node at `end`, it will be the same as `startNodeAndAncestors`
|
2020-11-30 12:29:48 +08:00
|
|
|
isAllWhitespace
|
2025-10-12 20:47:36 +08:00
|
|
|
? startNodeAndAncestors
|
2020-11-30 12:29:48 +08:00
|
|
|
: findNodeAtOffset(
|
|
|
|
|
ast,
|
|
|
|
|
end,
|
|
|
|
|
opts,
|
|
|
|
|
(node) => isSourceElement(opts, node),
|
|
|
|
|
[],
|
|
|
|
|
"rangeEnd",
|
|
|
|
|
);
|
2025-10-12 20:47:36 +08:00
|
|
|
if (!endNodeAndAncestors) {
|
|
|
|
|
return;
|
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(
|
2025-10-12 20:47:36 +08:00
|
|
|
startNodeAndAncestors,
|
|
|
|
|
endNodeAndAncestors,
|
2021-03-12 06:24:45 +08:00
|
|
|
);
|
|
|
|
|
startNode = commonAncestor;
|
|
|
|
|
endNode = commonAncestor;
|
|
|
|
|
} else {
|
2025-10-12 20:47:36 +08:00
|
|
|
[startNode, endNode] = findSiblingAncestors(
|
|
|
|
|
startNodeAndAncestors,
|
|
|
|
|
endNodeAndAncestors,
|
2021-03-12 06:24:45 +08:00
|
|
|
opts,
|
2025-10-12 20:47:36 +08:00
|
|
|
);
|
2021-03-12 06:24:45 +08:00
|
|
|
}
|
2018-04-26 13:38:57 -03:00
|
|
|
|
2025-10-12 20:47:36 +08:00
|
|
|
return [
|
|
|
|
|
Math.min(locStart(startNode), locStart(endNode)),
|
|
|
|
|
Math.max(locEnd(startNode), locEnd(endNode)),
|
|
|
|
|
];
|
2018-04-26 13:38:57 -03:00
|
|
|
}
|
|
|
|
|
|
2024-09-18 16:40:10 +08:00
|
|
|
export { calculateRange };
|