/* * Copyright 2025 the original author or authors. *
* Licensed under the Moderne Source Available License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *
* https://docs.moderne.io/licensing/moderne-source-available-license *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as ts from "typescript";
import {Expression, J, Statement} from "../java";
import {JS} from "./tree";
const is_statements: string[] = [
J.Kind.Assert,
J.Kind.Assignment,
J.Kind.AssignmentOperation,
J.Kind.Block,
J.Kind.Break,
J.Kind.Case,
J.Kind.ClassDeclaration,
J.Kind.Continue,
J.Kind.DoWhileLoop,
J.Kind.Empty,
J.Kind.EnumValueSet,
J.Kind.Erroneous,
J.Kind.FieldAccess,
J.Kind.ForEachLoop,
J.Kind.ForLoop,
J.Kind.If,
J.Kind.Import,
J.Kind.Label,
J.Kind.Lambda,
J.Kind.MethodDeclaration,
J.Kind.MethodInvocation,
J.Kind.NewClass,
J.Kind.Package,
J.Kind.Return,
J.Kind.Switch,
J.Kind.Synchronized,
J.Kind.Ternary,
J.Kind.Throw,
J.Kind.Try,
J.Kind.Unary,
J.Kind.Unknown,
J.Kind.VariableDeclarations,
J.Kind.WhileLoop,
J.Kind.Yield,
JS.Kind.ArrowFunction,
JS.Kind.BindingElement,
JS.Kind.Delete,
JS.Kind.Export,
JS.Kind.ExportAssignment,
JS.Kind.ExportDeclaration,
JS.Kind.ForInLoop,
JS.Kind.ForOfLoop,
JS.Kind.ImportAttribute,
JS.Kind.IndexSignatureDeclaration,
JS.Kind.AssignmentOperation,
JS.Kind.Import,
JS.Kind.ComputedPropertyMethodDeclaration,
JS.Kind.MappedTypeKeysRemapping,
JS.Kind.MappedTypeParameter,
JS.Kind.NamespaceDeclaration,
JS.Kind.PropertyAssignment,
JS.Kind.ScopedVariableDeclarations,
JS.Kind.TaggedTemplateExpression,
JS.Kind.TemplateExpression,
JS.Kind.TypeDeclaration,
JS.Kind.Void,
JS.Kind.WithStatement,
JS.Kind.ExpressionStatement,
JS.Kind.StatementExpression
]
const is_expressions: string[] = [
J.Kind.AnnotatedType,
J.Kind.Annotation,
J.Kind.ArrayAccess,
J.Kind.ArrayType,
J.Kind.Assignment,
J.Kind.AssignmentOperation,
J.Kind.Binary,
J.Kind.ControlParentheses,
J.Kind.Empty,
J.Kind.Erroneous,
J.Kind.FieldAccess,
J.Kind.Identifier,
J.Kind.InstanceOf,
J.Kind.IntersectionType,
J.Kind.Lambda,
J.Kind.Literal,
J.Kind.MethodInvocation,
J.Kind.MemberReference,
J.Kind.NewArray,
J.Kind.NewClass,
J.Kind.NullableType,
J.Kind.ParameterizedType,
J.Kind.Parentheses,
J.Kind.ParenthesizedTypeTree,
J.Kind.Primitive,
J.Kind.SwitchExpression,
J.Kind.Ternary,
J.Kind.TypeCast,
J.Kind.Unary,
J.Kind.Unknown,
J.Kind.Wildcard,
JS.Kind.Alias,
JS.Kind.ArrayBindingPattern,
JS.Kind.ArrowFunction,
JS.Kind.Await,
JS.Kind.BindingElement,
JS.Kind.ConditionalType,
JS.Kind.Delete,
JS.Kind.ExportSpecifier,
JS.Kind.ExpressionWithTypeArguments,
JS.Kind.FunctionType,
JS.Kind.ImportType,
JS.Kind.IndexedAccessType,
JS.Kind.IndexedAccessTypeIndexType,
JS.Kind.InferType,
JS.Kind.Intersection,
JS.Kind.AssignmentOperation,
JS.Kind.Binary,
JS.Kind.ImportSpecifier,
JS.Kind.LiteralType,
JS.Kind.MappedType,
JS.Kind.NamedExports,
JS.Kind.NamedImports,
JS.Kind.ObjectBindingPattern,
JS.Kind.SatisfiesExpression,
JS.Kind.TaggedTemplateExpression,
JS.Kind.TemplateExpression,
JS.Kind.Tuple,
JS.Kind.TypeInfo,
JS.Kind.TypeLiteral,
JS.Kind.TypeOf,
JS.Kind.TypeOperator,
JS.Kind.TypePredicate,
JS.Kind.TypeQuery,
JS.Kind.TypeTreeExpression,
JS.Kind.Union,
JS.Kind.Void,
JS.Kind.ExpressionStatement,
JS.Kind.StatementExpression
]
export function isStatement(statement: J): statement is Statement {
return is_statements.includes(statement.kind);
}
export function isExpression(expression: J): expression is Expression {
return is_expressions.includes(expression.kind);
}
export function getNextSibling(node: ts.Node): ts.Node | undefined {
const parent = node.parent;
if (!parent) {
return undefined;
}
const syntaxList = findContainingSyntaxList(node);
if (syntaxList) {
const children = syntaxList.getChildren();
const nodeIndex = children.indexOf(node);
if (nodeIndex === -1) {
throw new Error('Node not found among SyntaxList\'s children.');
}
// If the node is the last child in the SyntaxList, recursively check the parent's next sibling
if (nodeIndex === children.length - 1) {
const syntaxListIndex = parent.getChildren().indexOf(syntaxList);
if (parent.getChildCount() > syntaxListIndex + 1) {
return parent.getChildAt(syntaxListIndex + 1);
}
const parentNextSibling = getNextSibling(parent);
if (!parentNextSibling) {
return undefined;
}
// Return the first child of the parent's next sibling
const parentSyntaxList = findContainingSyntaxList(parentNextSibling);
if (parentSyntaxList) {
const siblings = parentSyntaxList.getChildren();
return siblings[0] || null;
} else {
return parentNextSibling;
}
}
// Otherwise, return the next sibling in the SyntaxList
return children[nodeIndex + 1];
}
const parentChildren = parent.getChildren();
const nodeIndex = parentChildren.indexOf(node);
if (nodeIndex === -1) {
throw new Error('Node not found among parent\'s children.');
}
// If the node is the last child, recursively check the parent's next sibling
if (nodeIndex === parentChildren.length - 1) {
const parentNextSibling = getNextSibling(parent);
if (!parentNextSibling) {
return undefined;
}
// Return the first child of the parent's next sibling
return parentNextSibling.getChildCount() > 0 ? parentNextSibling.getChildAt(0) : parentNextSibling;
}
// Otherwise, return the next sibling
return parentChildren[nodeIndex + 1];
}
export function getPreviousSibling(node: ts.Node): ts.Node | undefined {
const parent = node.parent;
if (!parent) {
return undefined;
}
const syntaxList = findContainingSyntaxList(node);
if (syntaxList) {
const children = syntaxList.getChildren();
const nodeIndex = children.indexOf(node);
if (nodeIndex === -1) {
throw new Error('Node not found among SyntaxList\'s children.');
}
// If the node is the first child in the SyntaxList, recursively check the parent's previous sibling
if (nodeIndex === 0) {
const parentPreviousSibling = getPreviousSibling(parent);
if (!parentPreviousSibling) {
return undefined;
}
// Return the last child of the parent's previous sibling
const parentSyntaxList = findContainingSyntaxList(parentPreviousSibling);
if (parentSyntaxList) {
const siblings = parentSyntaxList.getChildren();
return siblings[siblings.length - 1] || null;
} else {
return parentPreviousSibling;
}
}
// Otherwise, return the previous sibling in the SyntaxList
return children[nodeIndex - 1];
}
const parentChildren = parent.getChildren();
const nodeIndex = parentChildren.indexOf(node);
if (nodeIndex === -1) {
throw new Error('Node not found among parent\'s children.');
}
// If the node is the first child, recursively check the parent's previous sibling
if (nodeIndex === 0) {
const parentPreviousSibling = getPreviousSibling(parent);
if (!parentPreviousSibling) {
return undefined;
}
// Return the last child of the parent's previous sibling
return parentPreviousSibling.getChildCount() > 0 ? parentPreviousSibling.getLastToken()! : parentPreviousSibling;
}
// Otherwise, return the previous sibling
return parentChildren[nodeIndex - 1];
}
function findContainingSyntaxList(node: ts.Node): ts.SyntaxList | undefined {
const parent = node.parent;
if (!parent) {
return undefined;
}
const children = parent.getChildren();
for (const child of children) {
if (child.kind == ts.SyntaxKind.SyntaxList && child.getChildren().includes(node)) {
return child as ts.SyntaxList;
}
}
return undefined;
}
export type TextSpan = [number, number];
export function compareTextSpans(span1: TextSpan, span2: TextSpan) {
// First, compare the first elements
if (span1[0] < span2[0]) {
return -1;
}
if (span1[0] > span2[0]) {
return 1;
}
// If the first elements are equal, compare the second elements
if (span1[1] < span2[1]) {
return -1;
}
if (span1[1] > span2[1]) {
return 1;
}
// If both elements are equal, the tuples are considered equal
return 0;
}
export function binarySearch