/
ast-descriptions.ts
148 lines (131 loc) · 6.26 KB
/
ast-descriptions.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/******************************************************************************
* 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 { URI } from '../utils/uri-utils.js';
import type { NameProvider } from '../references/name-provider.js';
import type { LangiumCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription, ReferenceInfo } from '../syntax-tree.js';
import type { AstNodeLocator } from './ast-node-locator.js';
import type { DocumentSegment, LangiumDocument } from './documents.js';
import { CancellationToken } from '../utils/cancellation.js';
import { isLinkingError } from '../syntax-tree.js';
import { getDocument, streamAst, streamReferences } from '../utils/ast-utils.js';
import { toDocumentSegment } from '../utils/cst-utils.js';
import { interruptAndCheck } from '../utils/promise-utils.js';
import { UriUtils } from '../utils/uri-utils.js';
/**
* Language-specific service for creating descriptions of AST nodes to be used for cross-reference resolutions.
*/
export interface AstNodeDescriptionProvider {
/**
* Create a description for the given AST node. This service method is typically used while indexing
* the contents of a document and during scope computation.
*
* @param node An AST node.
* @param name The name to be used to refer to the AST node. By default, this is determined by the
* `NameProvider` service, but alternative names may be provided according to the semantics
* of your language.
* @param document The document containing the AST node. If omitted, it is taken from the root AST node.
*/
createDescription(node: AstNode, name: string | undefined, document?: LangiumDocument): AstNodeDescription;
}
export class DefaultAstNodeDescriptionProvider implements AstNodeDescriptionProvider {
protected readonly astNodeLocator: AstNodeLocator;
protected readonly nameProvider: NameProvider;
constructor(services: LangiumCoreServices) {
this.astNodeLocator = services.workspace.AstNodeLocator;
this.nameProvider = services.references.NameProvider;
}
createDescription(node: AstNode, name: string | undefined, document: LangiumDocument = getDocument(node)): AstNodeDescription {
name ??= this.nameProvider.getName(node);
const path = this.astNodeLocator.getAstNodePath(node);
if (!name) {
throw new Error(`Node at path ${path} has no name.`);
}
let nameNodeSegment: DocumentSegment | undefined;
const nameSegmentGetter = () => nameNodeSegment ??= toDocumentSegment(this.nameProvider.getNameNode(node) ?? node.$cstNode);
return {
node,
name,
get nameSegment() {
return nameSegmentGetter();
},
selectionSegment: toDocumentSegment(node.$cstNode),
type: node.$type,
documentUri: document.uri,
path
};
}
}
/**
* Describes a cross-reference within a document or between two documents.
*/
export interface ReferenceDescription {
/** URI of the document that holds a reference */
sourceUri: URI
/** Path to AstNode that holds a reference */
sourcePath: string
/** Target document uri */
targetUri: URI
/** Path to the target AstNode inside the document */
targetPath: string
/** Segment of the reference text. */
segment: DocumentSegment
/** Marks a local reference i.e. a cross reference inside a document. */
local?: boolean
}
/**
* Language-specific service to create descriptions of all cross-references in a document. These are used by the `IndexManager`
* to determine which documents are affected and should be rebuilt when a document is changed.
*/
export interface ReferenceDescriptionProvider {
/**
* Create descriptions of all cross-references found in the given document. These descriptions are
* gathered by the `IndexManager` and stored in the global index so they can be considered when
* a document change is reported by the client.
*
* @param document The document in which to gather cross-references.
* @param cancelToken Indicates when to cancel the current operation.
* @throws `OperationCanceled` if a user action occurs during execution
*/
createDescriptions(document: LangiumDocument, cancelToken?: CancellationToken): Promise<ReferenceDescription[]>;
}
export class DefaultReferenceDescriptionProvider implements ReferenceDescriptionProvider {
protected readonly nodeLocator: AstNodeLocator;
constructor(services: LangiumCoreServices) {
this.nodeLocator = services.workspace.AstNodeLocator;
}
async createDescriptions(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<ReferenceDescription[]> {
const descr: ReferenceDescription[] = [];
const rootNode = document.parseResult.value;
for (const astNode of streamAst(rootNode)) {
await interruptAndCheck(cancelToken);
streamReferences(astNode).filter(refInfo => !isLinkingError(refInfo)).forEach(refInfo => {
// TODO: Consider logging a warning or throw an exception when DocumentState is < than Linked
const description = this.createDescription(refInfo);
if (description) {
descr.push(description);
}
});
}
return descr;
}
protected createDescription(refInfo: ReferenceInfo): ReferenceDescription | undefined {
const targetNodeDescr = refInfo.reference.$nodeDescription;
const refCstNode = refInfo.reference.$refNode;
if (!targetNodeDescr || !refCstNode) {
return undefined;
}
const docUri = getDocument(refInfo.container).uri;
return {
sourceUri: docUri,
sourcePath: this.nodeLocator.getAstNodePath(refInfo.container),
targetUri: targetNodeDescr.documentUri,
targetPath: targetNodeDescr.path,
segment: toDocumentSegment(refCstNode),
local: UriUtils.equals(targetNodeDescr.documentUri, docUri)
};
}
}