Files
test-repo/src/main/range.js
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

266 lines
6.4 KiB
JavaScript
Raw Normal View History

import * as assert from "#universal/assert";
2025-10-30 22:50:59 +08:00
import { childNodesCache } from "./comments/attach.js";
import getSortedChildNodes from "./utilities/get-sorted-child-nodes.js";
2018-04-26 13:38:57 -03:00
const isJsonParser = ({ parser }) =>
2024-01-02 10:59:34 +08:00
parser === "json" ||
parser === "json5" ||
parser === "jsonc" ||
parser === "json-stringify";
2025-10-12 20:47:36 +08:00
function findCommonAncestor(startNodeAndAncestors, endNodeAndAncestors) {
endNodeAndAncestors = new Set(endNodeAndAncestors);
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;
}
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];
2025-10-30 22:50:59 +08:00
const childNodes = getSortedChildNodes(node, nodeAndAncestors, {
cache: childNodesCache,
locStart,
locEnd,
getVisitorKeys: options.getVisitorKeys,
// These two property should be removed, since we don't care if it can attach comment
filter: options.printer.canAttachComment,
getChildren: options.printer.getCommentChildNodes,
});
2025-10-12 20:47:36 +08:00
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) {
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-04-09 13:22:28 +08:00
const jsonSourceElements = new Set([
"JsonRoot",
2020-04-09 13:22:28 +08:00
"ObjectExpression",
"ArrayExpression",
"StringLiteral",
"NumericLiteral",
"BooleanLiteral",
"NullLiteral",
"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":
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":
return isJsSourceElement(node.type, parentNode?.type);
2018-04-26 13:38:57 -03:00
case "json":
case "json5":
2024-01-02 10:59:34 +08:00
case "jsonc":
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.
const firstNonWhitespaceCharacterIndex = text.slice(start, end).search(/\S/);
2020-11-30 12:29:48 +08:00
const isAllWhitespace = firstNonWhitespaceCharacterIndex === -1;
if (!isAllWhitespace) {
start += firstNonWhitespaceCharacterIndex;
for (; end > start; --end) {
if (/\S/.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
}
let startNode;
let endNode;
if (isJsonParser(opts)) {
const commonAncestor = findCommonAncestor(
2025-10-12 20:47:36 +08:00
startNodeAndAncestors,
endNodeAndAncestors,
);
startNode = commonAncestor;
endNode = commonAncestor;
} else {
2025-10-12 20:47:36 +08:00
[startNode, endNode] = findSiblingAncestors(
startNodeAndAncestors,
endNodeAndAncestors,
opts,
2025-10-12 20:47:36 +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 };