/
scope-computation.ts
140 lines (125 loc) · 7.5 KB
/
scope-computation.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
/******************************************************************************
* Copyright 2021-2022 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 { LangiumCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription } from '../syntax-tree.js';
import type { AstNodeDescriptionProvider } from '../workspace/ast-descriptions.js';
import type { LangiumDocument, PrecomputedScopes } from '../workspace/documents.js';
import type { NameProvider } from './name-provider.js';
import { CancellationToken } from '../utils/cancellation.js';
import { streamAllContents, streamContents } from '../utils/ast-utils.js';
import { MultiMap } from '../utils/collections.js';
import { interruptAndCheck } from '../utils/promise-utils.js';
/**
* Language-specific service for precomputing global and local scopes. The service methods are executed
* as the first and second phase in the `DocumentBuilder`.
*/
export interface ScopeComputation {
/**
* Creates descriptions of all AST nodes that shall be exported into the _global_ scope from the given
* document. These descriptions are gathered by the `IndexManager` and stored in the global index so
* they can be referenced from other documents.
*
* _Note:_ You should not resolve any cross-references in this service method. Cross-reference resolution
* depends on the scope computation phase to be completed (`computeScope` method), which runs after the
* initial indexing where this method is used.
*
* @param document The document from which to gather exported AST nodes.
* @param cancelToken Indicates when to cancel the current operation.
* @throws `OperationCanceled` if a user action occurs during execution
*/
computeExports(document: LangiumDocument, cancelToken?: CancellationToken): Promise<AstNodeDescription[]>;
/**
* Precomputes the _local_ scopes for a document, which are necessary for the default way of
* resolving references to symbols in the same document. The result is a multimap assigning a
* set of AST node descriptions to every level of the AST. These data are used by the `ScopeProvider`
* service to determine which target nodes are visible in the context of a specific cross-reference.
*
* _Note:_ You should not resolve any cross-references in this service method. Cross-reference
* resolution depends on the scope computation phase to be completed.
*
* @param document The document in which to compute scopes.
* @param cancelToken Indicates when to cancel the current operation.
* @throws `OperationCanceled` if a user action occurs during execution
*/
computeLocalScopes(document: LangiumDocument, cancelToken?: CancellationToken): Promise<PrecomputedScopes>;
}
/**
* The default scope computation creates and collectes descriptions of the AST nodes to be exported into the
* _global_ scope from the given document. By default those are the document's root AST node and its directly
* contained child nodes.
*
* Besides, it gathers all AST nodes that have a name (according to the `NameProvider` service) and includes them
* in the local scope of their particular container nodes. As a result, for every cross-reference in the AST,
* target elements from the same level (siblings) and further up towards the root (parents and siblings of parents)
* are visible. Elements being nested inside lower levels (children, children of siblings and parents' siblings)
* are _invisible_ by default, but that can be changed by customizing this service.
*/
export class DefaultScopeComputation implements ScopeComputation {
protected readonly nameProvider: NameProvider;
protected readonly descriptions: AstNodeDescriptionProvider;
constructor(services: LangiumCoreServices) {
this.nameProvider = services.references.NameProvider;
this.descriptions = services.workspace.AstNodeDescriptionProvider;
}
async computeExports(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<AstNodeDescription[]> {
return this.computeExportsForNode(document.parseResult.value, document, undefined, cancelToken);
}
/**
* Creates {@link AstNodeDescription AstNodeDescriptions} for the given {@link AstNode parentNode} and its children.
* The list of children to be considered is determined by the function parameter {@link children}.
* By default only the direct children of {@link parentNode} are visited, nested nodes are not exported.
*
* @param parentNode AST node to be exported, i.e., of which an {@link AstNodeDescription} shall be added to the returned list.
* @param document The document containing the AST node to be exported.
* @param children A function called with {@link parentNode} as single argument and returning an {@link Iterable} supplying the children to be visited, which must be directly or transitively contained in {@link parentNode}.
* @param cancelToken Indicates when to cancel the current operation.
* @throws `OperationCanceled` if a user action occurs during execution.
* @returns A list of {@link AstNodeDescription AstNodeDescriptions} to be published to index.
*/
async computeExportsForNode(parentNode: AstNode, document: LangiumDocument<AstNode>, children: (root: AstNode) => Iterable<AstNode> = streamContents, cancelToken: CancellationToken = CancellationToken.None): Promise<AstNodeDescription[]> {
const exports: AstNodeDescription[] = [];
this.exportNode(parentNode, exports, document);
for (const node of children(parentNode)) {
await interruptAndCheck(cancelToken);
this.exportNode(node, exports, document);
}
return exports;
}
/**
* Add a single node to the list of exports if it has a name. Override this method to change how
* symbols are exported, e.g. by modifying their exported name.
*/
protected exportNode(node: AstNode, exports: AstNodeDescription[], document: LangiumDocument): void {
const name = this.nameProvider.getName(node);
if (name) {
exports.push(this.descriptions.createDescription(node, name, document));
}
}
async computeLocalScopes(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<PrecomputedScopes> {
const rootNode = document.parseResult.value;
const scopes = new MultiMap<AstNode, AstNodeDescription>();
// Here we navigate the full AST - local scopes shall be available in the whole document
for (const node of streamAllContents(rootNode)) {
await interruptAndCheck(cancelToken);
this.processNode(node, document, scopes);
}
return scopes;
}
/**
* Process a single node during scopes computation. The default implementation makes the node visible
* in the subtree of its container (if the node has a name). Override this method to change this,
* e.g. by increasing the visibility to a higher level in the AST.
*/
protected processNode(node: AstNode, document: LangiumDocument, scopes: PrecomputedScopes): void {
const container = node.$container;
if (container) {
const name = this.nameProvider.getName(node);
if (name) {
scopes.add(container, this.descriptions.createDescription(node, name, document));
}
}
}
}