From ec1c398ebab60487ee5c775abc756c48197d5e1e Mon Sep 17 00:00:00 2001 From: Yannick Daveluy Date: Mon, 23 Sep 2024 21:31:52 +0200 Subject: [PATCH] split scope computation & provider --- ...-scope.ts => xsmpcat-scope-computation.ts} | 196 +----------------- .../references/xsmpcat-scope-provider.ts | 193 +++++++++++++++++ src/language/xsmpcat-module.ts | 3 +- 3 files changed, 198 insertions(+), 194 deletions(-) rename src/language/references/{xsmpcat-scope.ts => xsmpcat-scope-computation.ts} (50%) create mode 100644 src/language/references/xsmpcat-scope-provider.ts diff --git a/src/language/references/xsmpcat-scope.ts b/src/language/references/xsmpcat-scope-computation.ts similarity index 50% rename from src/language/references/xsmpcat-scope.ts rename to src/language/references/xsmpcat-scope-computation.ts index 7d12a0f..1384202 100644 --- a/src/language/references/xsmpcat-scope.ts +++ b/src/language/references/xsmpcat-scope-computation.ts @@ -1,10 +1,6 @@ - -import type { AstNode, AstNodeDescription, AstNodeDescriptionProvider, AstReflection, IndexManager, LangiumCoreServices, LangiumDocument, LangiumDocuments, PrecomputedScopes, Reference, ReferenceInfo, Scope, ScopeComputation, ScopeProvider, Stream, URI } from 'langium'; -import type { XsmpcatServices } from '../xsmpcat-module.js'; +import type { AstNode, AstNodeDescription, AstNodeDescriptionProvider, LangiumCoreServices, LangiumDocument, PrecomputedScopes, ScopeComputation } from 'langium'; import * as ast from '../generated/ast.js'; -import { AstUtils, Cancellation, DocumentCache, EMPTY_SCOPE, MultiMap, WorkspaceCache, interruptAndCheck, stream } from 'langium'; -import * as ProjectUtils from '../utils/project-utils.js'; -import type { XsmpTypeProvider } from './type-provider.js'; +import { Cancellation, MultiMap, interruptAndCheck } from 'langium'; import * as XsmpUtils from '../utils/xsmp-utils.js'; import { VisibilityKind } from '../utils/visibility-kind.js'; @@ -166,190 +162,4 @@ export class XsmpcatScopeComputation implements ScopeComputation { return { ...description, name: `${element.name}.${description.name}` }; } -} - -export class XsmpcatScopeProvider implements ScopeProvider { - protected documents: LangiumDocuments; - protected readonly visibleUris: WorkspaceCache | undefined>; - protected readonly reflection: AstReflection; - protected readonly indexManager: IndexManager; - protected readonly typeProvider: XsmpTypeProvider; - protected readonly globalScopeCache: WorkspaceCache; - protected readonly contexts: Set = new Set(); - protected readonly precomputedCache: DocumentCache>; - - constructor(services: XsmpcatServices) { - this.documents = services.shared.workspace.LangiumDocuments; - this.visibleUris = new WorkspaceCache | undefined>(services.shared); - this.reflection = services.shared.AstReflection; - this.indexManager = services.shared.workspace.IndexManager; - this.typeProvider = services.shared.TypeProvider; - this.globalScopeCache = new WorkspaceCache(services.shared); - this.precomputedCache = new DocumentCache>(services.shared); - } - - protected collectScopesFromNode(node: AstNode, scopes: Array>, - document: LangiumDocument) { - - const precomputed = this.getPrecomputedScope(node, document); - if (precomputed.size > 0) { - scopes.push(precomputed); - } - switch (node.$type) { - case ast.Model: - case ast.Service: { - const component = node as ast.Component; - if (component.base) { - this.collectScopesFromReference(component.base, scopes); - } - component.interface.forEach(i => this.collectScopesFromReference(i, scopes)); - break; - } - case ast.Interface: { - (node as ast.Interface).base.forEach(i => this.collectScopesFromReference(i, scopes)); - break; - } - case ast.Class: - case ast.Exception: { - const clazz = node as ast.Class; - if (clazz.base) - this.collectScopesFromReference(clazz.base, scopes); - break; - } - } - } - protected collectScopesFromReference(node: Reference, scopes: Array>) { - // Check if the node is currently processed to avoid cyclic dependency - if (!this.contexts.has(node) && node.ref) { - this.contexts.add(node); - try { - this.collectScopesFromNode(node.ref, scopes, AstUtils.getDocument(node.ref)); - } - finally { - // Remove the context - this.contexts.delete(node); - } - } - } - - getScope(context: ReferenceInfo): Scope { - // Store the context - this.contexts.add(context.reference); - try { - return this.computeScope(context); - } - finally { - // Remove the context - this.contexts.delete(context.reference); - } - } - - private computeScope(context: ReferenceInfo): Scope { - let parent: Scope; - - const scopes: Array> = []; - - if (ast.DesignatedInitializer === context.container.$type && context.property === 'field') { - if (context.container.$container) { - const type = this.typeProvider.getType(context.container.$container); - switch (type?.$type) { - case ast.Structure: - case ast.Class: - case ast.Exception: - this.collectScopesFromNode(type, scopes, AstUtils.getDocument(type)); - parent = EMPTY_SCOPE; - break; - default: return EMPTY_SCOPE; - } - } - else { - return EMPTY_SCOPE; - } - } - else { - let currentNode = context.container.$container; - const document = AstUtils.getDocument(context.container); - parent = this.getGlobalScope(document); - while (currentNode) { - this.collectScopesFromNode(currentNode, scopes, document); - currentNode = currentNode.$container; - } - } - - for (let i = scopes.length - 1; i >= 0; i--) { - parent = new MapScope(scopes[i], parent); - } - - return parent; - } - - private getVisibleUris(uri: URI): Set | undefined { - return this.visibleUris.get(uri, () => ProjectUtils.findVisibleUris(this.documents, uri)); - } - - /** - * Create a global scope filtered for the given referenceType and on visibles projects URIs - */ - protected getGlobalScope(document: LangiumDocument): Scope { - return this.globalScopeCache.get(document.uri, () => new GlobalScope(this.indexManager.allElements(undefined, this.getVisibleUris(document.uri)))); - } - - protected getPrecomputedScope(node: AstNode, document: LangiumDocument): Map { - return this.precomputedCache.get(document.uri, node, () => { - const precomputed = new Map(); - if (document.precomputedScopes) { - for (const element of document.precomputedScopes.get(node)) { - precomputed.set(element.name, element); - } - } - return precomputed; - }); - } -} - -export class GlobalScope implements Scope { - readonly elements: Map; - constructor(elements: Stream) { - this.elements = new Map(); - for (const element of elements) { - this.elements.set(element.name, element); - - // Import elements from Smp and Attributes namespaces in global namespace - if (element.name.startsWith('Smp.')) { - const name = element.name.substring(4); - this.elements.set(name, { ...element, name }); - } - else if (element.name.startsWith('Attributes.')) { - const name = element.name.substring(11); - this.elements.set(name, { ...element, name }); - } - } - } - - getElement(name: string): AstNodeDescription | undefined { - return this.elements.get(name); - } - - getAllElements(): Stream { - return stream(this.elements.values()); - } -} - -export class MapScope implements Scope { - readonly elements: Map; - readonly outerScope: Scope; - - constructor(elements: Map, outerScope: Scope) { - this.elements = elements; - this.outerScope = outerScope; - } - - getElement(name: string): AstNodeDescription | undefined { - return this.elements.get(name) ?? this.outerScope.getElement(name); - } - - getAllElements(): Stream { - return stream(this.elements.values()).concat(this.outerScope.getAllElements()); - } - -} +} \ No newline at end of file diff --git a/src/language/references/xsmpcat-scope-provider.ts b/src/language/references/xsmpcat-scope-provider.ts new file mode 100644 index 0000000..7b00b33 --- /dev/null +++ b/src/language/references/xsmpcat-scope-provider.ts @@ -0,0 +1,193 @@ + +import type { AstNode, AstNodeDescription, AstReflection, IndexManager, LangiumDocument, LangiumDocuments, Reference, ReferenceInfo, Scope, ScopeProvider, Stream, URI } from 'langium'; +import type { XsmpcatServices } from '../xsmpcat-module.js'; +import * as ast from '../generated/ast.js'; +import { AstUtils, DocumentCache, EMPTY_SCOPE, WorkspaceCache, stream } from 'langium'; +import * as ProjectUtils from '../utils/project-utils.js'; +import type { XsmpTypeProvider } from './type-provider.js'; + +export class XsmpcatScopeProvider implements ScopeProvider { + protected documents: LangiumDocuments; + protected readonly visibleUris: WorkspaceCache | undefined>; + protected readonly reflection: AstReflection; + protected readonly indexManager: IndexManager; + protected readonly typeProvider: XsmpTypeProvider; + protected readonly globalScopeCache: WorkspaceCache; + protected readonly contexts: Set = new Set(); + protected readonly precomputedCache: DocumentCache>; + + constructor(services: XsmpcatServices) { + this.documents = services.shared.workspace.LangiumDocuments; + this.visibleUris = new WorkspaceCache | undefined>(services.shared); + this.reflection = services.shared.AstReflection; + this.indexManager = services.shared.workspace.IndexManager; + this.typeProvider = services.shared.TypeProvider; + this.globalScopeCache = new WorkspaceCache(services.shared); + this.precomputedCache = new DocumentCache>(services.shared); + } + + protected collectScopesFromNode(node: AstNode, scopes: Array>, + document: LangiumDocument) { + + const precomputed = this.getPrecomputedScope(node, document); + if (precomputed.size > 0) { + scopes.push(precomputed); + } + switch (node.$type) { + case ast.Model: + case ast.Service: { + const component = node as ast.Component; + if (component.base) { + this.collectScopesFromReference(component.base, scopes); + } + component.interface.forEach(i => this.collectScopesFromReference(i, scopes)); + break; + } + case ast.Interface: { + (node as ast.Interface).base.forEach(i => this.collectScopesFromReference(i, scopes)); + break; + } + case ast.Class: + case ast.Exception: { + const clazz = node as ast.Class; + if (clazz.base) + this.collectScopesFromReference(clazz.base, scopes); + break; + } + } + } + protected collectScopesFromReference(node: Reference, scopes: Array>) { + // Check if the node is currently processed to avoid cyclic dependency + if (!this.contexts.has(node) && node.ref) { + this.contexts.add(node); + try { + this.collectScopesFromNode(node.ref, scopes, AstUtils.getDocument(node.ref)); + } + finally { + // Remove the context + this.contexts.delete(node); + } + } + } + + getScope(context: ReferenceInfo): Scope { + // Store the context + this.contexts.add(context.reference); + try { + return this.computeScope(context); + } + finally { + // Remove the context + this.contexts.delete(context.reference); + } + } + + private computeScope(context: ReferenceInfo): Scope { + let parent: Scope; + + const scopes: Array> = []; + + if (ast.DesignatedInitializer === context.container.$type && context.property === 'field') { + if (context.container.$container) { + const type = this.typeProvider.getType(context.container.$container); + switch (type?.$type) { + case ast.Structure: + case ast.Class: + case ast.Exception: + this.collectScopesFromNode(type, scopes, AstUtils.getDocument(type)); + parent = EMPTY_SCOPE; + break; + default: return EMPTY_SCOPE; + } + } + else { + return EMPTY_SCOPE; + } + } + else { + let currentNode = context.container.$container; + const document = AstUtils.getDocument(context.container); + parent = this.getGlobalScope(document); + while (currentNode) { + this.collectScopesFromNode(currentNode, scopes, document); + currentNode = currentNode.$container; + } + } + + for (let i = scopes.length - 1; i >= 0; i--) { + parent = new MapScope(scopes[i], parent); + } + + return parent; + } + + private getVisibleUris(uri: URI): Set | undefined { + return this.visibleUris.get(uri, () => ProjectUtils.findVisibleUris(this.documents, uri)); + } + + /** + * Create a global scope filtered for the given referenceType and on visibles projects URIs + */ + protected getGlobalScope(document: LangiumDocument): Scope { + return this.globalScopeCache.get(document.uri, () => new GlobalScope(this.indexManager.allElements(undefined, this.getVisibleUris(document.uri)))); + } + + protected getPrecomputedScope(node: AstNode, document: LangiumDocument): Map { + return this.precomputedCache.get(document.uri, node, () => { + const precomputed = new Map(); + if (document.precomputedScopes) { + for (const element of document.precomputedScopes.get(node)) { + precomputed.set(element.name, element); + } + } + return precomputed; + }); + } +} + +export class GlobalScope implements Scope { + readonly elements: Map; + constructor(elements: Stream) { + this.elements = new Map(); + for (const element of elements) { + this.elements.set(element.name, element); + + // Import elements from Smp and Attributes namespaces in global namespace + if (element.name.startsWith('Smp.')) { + const name = element.name.substring(4); + this.elements.set(name, { ...element, name }); + } + else if (element.name.startsWith('Attributes.')) { + const name = element.name.substring(11); + this.elements.set(name, { ...element, name }); + } + } + } + + getElement(name: string): AstNodeDescription | undefined { + return this.elements.get(name); + } + + getAllElements(): Stream { + return stream(this.elements.values()); + } +} + +export class MapScope implements Scope { + readonly elements: Map; + readonly outerScope: Scope; + + constructor(elements: Map, outerScope: Scope) { + this.elements = elements; + this.outerScope = outerScope; + } + + getElement(name: string): AstNodeDescription | undefined { + return this.elements.get(name) ?? this.outerScope.getElement(name); + } + + getAllElements(): Stream { + return stream(this.elements.values()).concat(this.outerScope.getAllElements()); + } + +} diff --git a/src/language/xsmpcat-module.ts b/src/language/xsmpcat-module.ts index 7a63862..ee5264e 100644 --- a/src/language/xsmpcat-module.ts +++ b/src/language/xsmpcat-module.ts @@ -2,7 +2,8 @@ import type { Module } from 'langium'; import { XsmpcatValidator } from './validation/xsmpcat-validator.js'; import type { LangiumServices, PartialLangiumServices } from 'langium/lsp'; -import { XsmpcatScopeComputation, XsmpcatScopeProvider } from './references/xsmpcat-scope.js'; +import { XsmpcatScopeComputation } from './references/xsmpcat-scope-computation.js'; +import { XsmpcatScopeProvider } from './references/xsmpcat-scope-provider.js'; import { XsmpcatFormatter } from './lsp/xsmpcat-formatter.js'; import { XsmpHoverProvider } from './lsp/hover-provider.js'; import { XsmpDocumentSymbolProvider } from './lsp/document-symbol-provider.js';