/* * 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 {Markers} from "./markers"; import {UUID} from "./uuid"; import {PlainText} from "./text"; import {RpcCodecs} from "./rpc"; export const TreeKind = { Checksum: "org.openrewrite.Checksum", FileAttributes: "org.openrewrite.FileAttributes" } as const export interface Tree { readonly kind: string /** * Use randomId() to generate a unique id for a tree. */ readonly id: UUID /** * It is possible to use EmptyMarkers here to represent no markers. */ readonly markers: Markers } export function isTree(tree: any): tree is Tree { return ( typeof tree === "object" && tree !== null && "id" in tree && "markers" in tree ); } export function isScope(a: Tree, b: Tree): boolean { return a !== undefined && b !== undefined && (a === b || a.id === b.id); } export class Cursor { private _messages?: Map; constructor(public readonly value: any, public readonly parent?: Cursor) { } get messages(): Map { if (!this._messages) { this._messages = new Map(); } return this._messages; } getNearestMessage(key: string): any { const t = this._messages == undefined ? undefined : this._messages.get(key); return t == null && this.parent != null ? this.parent.getNearestMessage(key) : t; } asArray(): any[] { const path: any[] = []; let current: Cursor | undefined = this; while (current !== undefined) { path.push(current.value); current = current.parent; } return path; } parentTree(level: number = 1): Cursor | undefined { let c: Cursor | undefined = this.parent; let treeCount = 0; while (c) { if (isTree(c.value)) { treeCount++; if (treeCount === level) { return c; } } c = c.parent; } return undefined; } firstEnclosing(match: (value: any) => value is T): T | undefined { let c: Cursor | undefined = this; while (c) { if (match(c.value)) { return c.value as T; } c = c.parent; } return undefined; } get root(): Cursor { let root: Cursor = this; while (root.parent) { root = root.parent; } return root; } } export function rootCursor(): Cursor { return new Cursor("root"); } export interface Checksum { kind: typeof TreeKind.Checksum readonly algorithm: string, readonly value: ArrayBuffer } export interface FileAttributes { kind: typeof TreeKind.FileAttributes // ISO-8601, as sent by org.openrewrite.FileAttributes#rpcSend readonly creationDate?: string readonly lastModifiedTime?: string readonly lastAccessTime?: string readonly isReadable: boolean readonly isWritable: boolean readonly isExecutable: boolean readonly size: number } export function isSourceFile(tree: any): tree is SourceFile { return isTree(tree) && "sourcePath" in tree } export interface SourceFile extends Tree { sourcePath: string charsetName?: string charsetBomMarked?: boolean checksum?: Checksum fileAttributes?: FileAttributes }