UNPKG

langium

Version:

A language engineering tool for the Language Server Protocol

78 lines (67 loc) 3.24 kB
/****************************************************************************** * Copyright 2021 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ import type { AstNode } from '../syntax-tree.js'; /** * Language-specific service for locating an `AstNode` in a document. */ export interface AstNodeLocator { /** * Creates a path represented by a `string` that identifies an `AstNode` inside its document. * It must be possible to retrieve exactly the same `AstNode` from the document using this path. * * @param node The `AstNode` for which to create the path. * @returns a path represented by a `string` that identifies `node` inside its document. * @see {@link getAstNode} */ getAstNodePath(node: AstNode): string; /** * Locates an `AstNode` inside another node by following the given path. * * @param node Parent element. * @param path Describes how to locate the `AstNode` inside the given `node`. * @returns The `AstNode` located under the given path, or `undefined` if the path cannot be resolved. * @see {@link getAstNodePath} */ getAstNode<T extends AstNode = AstNode>(node: AstNode, path: string): T | undefined; } export class DefaultAstNodeLocator implements AstNodeLocator { protected segmentSeparator = '/'; protected indexSeparator = '@'; getAstNodePath(node: AstNode): string { if (node.$container) { const containerPath = this.getAstNodePath(node.$container); const newSegment = this.getPathSegment(node); const nodePath = containerPath + this.segmentSeparator + newSegment; return nodePath; } return ''; } protected getPathSegment({ $containerProperty, $containerIndex }: AstNode): string { if (!$containerProperty) { throw new Error("Missing '$containerProperty' in AST node."); } if ($containerIndex !== undefined) { return $containerProperty + this.indexSeparator + $containerIndex; } return $containerProperty; } getAstNode<T extends AstNode = AstNode>(node: AstNode, path: string): T | undefined { const segments = path.split(this.segmentSeparator); return segments.reduce((previousValue, currentValue) => { if (!previousValue || currentValue.length === 0) { return previousValue; } const propertyIndex = currentValue.indexOf(this.indexSeparator); if (propertyIndex > 0) { const property = currentValue.substring(0, propertyIndex); const arrayIndex = parseInt(currentValue.substring(propertyIndex + 1)); const array = (previousValue as unknown as Record<string, AstNode[]>)[property]; return array?.[arrayIndex]; } return (previousValue as unknown as Record<string, AstNode>)[currentValue]; }, node) as T; } }