@oml/language 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/out/oml/generated/ast.d.ts +2109 -0
- package/out/oml/generated/ast.js +1807 -0
- package/out/oml/generated/ast.js.map +1 -0
- package/out/oml/generated/grammar.d.ts +6 -0
- package/out/oml/generated/grammar.js +6885 -0
- package/out/oml/generated/grammar.js.map +1 -0
- package/out/oml/generated/module.d.ts +13 -0
- package/out/oml/generated/module.js +21 -0
- package/out/oml/generated/module.js.map +1 -0
- package/out/oml/index.d.ts +17 -0
- package/out/oml/index.js +19 -0
- package/out/oml/index.js.map +1 -0
- package/out/oml/oml-candidates.d.ts +27 -0
- package/out/oml/oml-candidates.js +146 -0
- package/out/oml/oml-candidates.js.map +1 -0
- package/out/oml/oml-code-actions.d.ts +6 -0
- package/out/oml/oml-code-actions.js +79 -0
- package/out/oml/oml-code-actions.js.map +1 -0
- package/out/oml/oml-completion.d.ts +50 -0
- package/out/oml/oml-completion.js +188 -0
- package/out/oml/oml-completion.js.map +1 -0
- package/out/oml/oml-converter.d.ts +10 -0
- package/out/oml/oml-converter.js +62 -0
- package/out/oml/oml-converter.js.map +1 -0
- package/out/oml/oml-document-validator.d.ts +10 -0
- package/out/oml/oml-document-validator.js +31 -0
- package/out/oml/oml-document-validator.js.map +1 -0
- package/out/oml/oml-document.d.ts +9 -0
- package/out/oml/oml-document.js +18 -0
- package/out/oml/oml-document.js.map +1 -0
- package/out/oml/oml-edit.d.ts +72 -0
- package/out/oml/oml-edit.js +1155 -0
- package/out/oml/oml-edit.js.map +1 -0
- package/out/oml/oml-formatter.d.ts +22 -0
- package/out/oml/oml-formatter.js +357 -0
- package/out/oml/oml-formatter.js.map +1 -0
- package/out/oml/oml-hover.d.ts +13 -0
- package/out/oml/oml-hover.js +71 -0
- package/out/oml/oml-hover.js.map +1 -0
- package/out/oml/oml-index-manager.d.ts +10 -0
- package/out/oml/oml-index-manager.js +48 -0
- package/out/oml/oml-index-manager.js.map +1 -0
- package/out/oml/oml-index.d.ts +20 -0
- package/out/oml/oml-index.js +133 -0
- package/out/oml/oml-index.js.map +1 -0
- package/out/oml/oml-module.d.ts +42 -0
- package/out/oml/oml-module.js +76 -0
- package/out/oml/oml-module.js.map +1 -0
- package/out/oml/oml-rename.d.ts +14 -0
- package/out/oml/oml-rename.js +114 -0
- package/out/oml/oml-rename.js.map +1 -0
- package/out/oml/oml-scope.d.ts +30 -0
- package/out/oml/oml-scope.js +225 -0
- package/out/oml/oml-scope.js.map +1 -0
- package/out/oml/oml-serializer.d.ts +2 -0
- package/out/oml/oml-serializer.js +883 -0
- package/out/oml/oml-serializer.js.map +1 -0
- package/out/oml/oml-utils.d.ts +53 -0
- package/out/oml/oml-utils.js +241 -0
- package/out/oml/oml-utils.js.map +1 -0
- package/out/oml/oml-validator.d.ts +49 -0
- package/out/oml/oml-validator.js +668 -0
- package/out/oml/oml-validator.js.map +1 -0
- package/out/oml/oml-workspace.d.ts +23 -0
- package/out/oml/oml-workspace.js +68 -0
- package/out/oml/oml-workspace.js.map +1 -0
- package/package.json +50 -0
- package/src/oml/generated/ast.ts +2641 -0
- package/src/oml/generated/grammar.ts +6887 -0
- package/src/oml/generated/module.ts +25 -0
- package/src/oml/index.ts +19 -0
- package/src/oml/oml-candidates.ts +176 -0
- package/src/oml/oml-code-actions.ts +120 -0
- package/src/oml/oml-completion.ts +222 -0
- package/src/oml/oml-converter.ts +66 -0
- package/src/oml/oml-document-validator.ts +39 -0
- package/src/oml/oml-document.ts +24 -0
- package/src/oml/oml-edit.ts +1292 -0
- package/src/oml/oml-formatter.ts +390 -0
- package/src/oml/oml-hover.ts +93 -0
- package/src/oml/oml-index-manager.ts +56 -0
- package/src/oml/oml-index.ts +145 -0
- package/src/oml/oml-module.ts +105 -0
- package/src/oml/oml-rename.ts +140 -0
- package/src/oml/oml-scope.ts +279 -0
- package/src/oml/oml-serializer.ts +1080 -0
- package/src/oml/oml-utils.ts +294 -0
- package/src/oml/oml-validator.ts +725 -0
- package/src/oml/oml-workspace.ts +81 -0
- package/src/oml/oml.langium +594 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { type Module, inject } from 'langium';
|
|
4
|
+
import { createDefaultModule, createDefaultSharedModule, type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type PartialLangiumServices, type PartialLangiumSharedServices } from 'langium/lsp';
|
|
5
|
+
import { OmlGeneratedModule, OmlGeneratedSharedModule } from './generated/module.js';
|
|
6
|
+
import { OmlCompletionProvider } from './oml-completion.js';
|
|
7
|
+
import { OmlCodeActionProvider } from './oml-code-actions.js';
|
|
8
|
+
import { OmlValueConverter } from './oml-converter.js';
|
|
9
|
+
import { OmlDocumentValidator } from './oml-document-validator.js';
|
|
10
|
+
import { OmlDocumentUpdateHandler } from './oml-document.js';
|
|
11
|
+
import { OmlFormatter } from './oml-formatter.js';
|
|
12
|
+
import { OmlHoverProvider } from './oml-hover.js';
|
|
13
|
+
import { OmlRenameProvider } from './oml-rename.js';
|
|
14
|
+
import { OmlScopeComputation, OmlScopeProvider } from './oml-scope.js';
|
|
15
|
+
import { OmlValidator, registerValidationChecks } from './oml-validator.js';
|
|
16
|
+
import { OmlWorkspaceManager } from './oml-workspace.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Declaration of custom services - add your own service classes here.
|
|
20
|
+
*/
|
|
21
|
+
export type OmlAddedServices = {
|
|
22
|
+
validation: {
|
|
23
|
+
OmlValidator: OmlValidator
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Union of Langium default services and your custom services - use this as constructor parameter
|
|
29
|
+
* of custom service classes.
|
|
30
|
+
*/
|
|
31
|
+
export type OmlServices = LangiumServices & OmlAddedServices
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Dependency injection module that overrides Langium default services and contributes the
|
|
35
|
+
* declared custom services. The Langium defaults can be partially specified to override only
|
|
36
|
+
* selected services, while the custom services must be fully specified.
|
|
37
|
+
*/
|
|
38
|
+
export const OmlModule: Module<OmlServices, PartialLangiumServices & OmlAddedServices> = {
|
|
39
|
+
lsp: {
|
|
40
|
+
CodeActionProvider: () => new OmlCodeActionProvider(),
|
|
41
|
+
CompletionProvider: (services) => new OmlCompletionProvider(services),
|
|
42
|
+
Formatter: () => new OmlFormatter(),
|
|
43
|
+
HoverProvider: (services) => new OmlHoverProvider(services),
|
|
44
|
+
RenameProvider: (services) => new OmlRenameProvider(services)
|
|
45
|
+
},
|
|
46
|
+
parser: {
|
|
47
|
+
ValueConverter: () => new OmlValueConverter()
|
|
48
|
+
},
|
|
49
|
+
references: {
|
|
50
|
+
ScopeComputation: (services) => new OmlScopeComputation(services),
|
|
51
|
+
ScopeProvider: (services) => new OmlScopeProvider(services)
|
|
52
|
+
},
|
|
53
|
+
validation: {
|
|
54
|
+
DocumentValidator: (services) => new OmlDocumentValidator(services),
|
|
55
|
+
OmlValidator: (services) => new OmlValidator(services)
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const OmlSharedModule: Module<LangiumSharedServices, PartialLangiumSharedServices> = {
|
|
60
|
+
lsp: {
|
|
61
|
+
DocumentUpdateHandler: (services) => new OmlDocumentUpdateHandler(services)
|
|
62
|
+
},
|
|
63
|
+
workspace: {
|
|
64
|
+
WorkspaceManager: (services) => new OmlWorkspaceManager(services)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create the full set of services required by Langium.
|
|
70
|
+
*
|
|
71
|
+
* First inject the shared services by merging two modules:
|
|
72
|
+
* - Langium default shared services
|
|
73
|
+
* - Services generated by langium-cli
|
|
74
|
+
*
|
|
75
|
+
* Then inject the language-specific services by merging three modules:
|
|
76
|
+
* - Langium default language-specific services
|
|
77
|
+
* - Services generated by langium-cli
|
|
78
|
+
* - Services specified in this file
|
|
79
|
+
*
|
|
80
|
+
* @param context Optional module context with the LSP connection
|
|
81
|
+
* @returns An object wrapping the shared services and the language-specific services
|
|
82
|
+
*/
|
|
83
|
+
export function createOmlServices(context: DefaultSharedModuleContext): {
|
|
84
|
+
shared: LangiumSharedServices,
|
|
85
|
+
Oml: OmlServices
|
|
86
|
+
} {
|
|
87
|
+
const shared = inject(
|
|
88
|
+
createDefaultSharedModule(context),
|
|
89
|
+
OmlGeneratedSharedModule,
|
|
90
|
+
OmlSharedModule
|
|
91
|
+
);
|
|
92
|
+
const Oml = inject(
|
|
93
|
+
createDefaultModule({ shared }),
|
|
94
|
+
OmlGeneratedModule,
|
|
95
|
+
OmlModule
|
|
96
|
+
);
|
|
97
|
+
shared.ServiceRegistry.register(Oml);
|
|
98
|
+
registerValidationChecks(Oml);
|
|
99
|
+
if (!context.connection) {
|
|
100
|
+
// We don't run inside a language server
|
|
101
|
+
// Therefore, initialize the configuration provider instantly
|
|
102
|
+
shared.workspace.ConfigurationProvider.initialized({});
|
|
103
|
+
}
|
|
104
|
+
return { shared, Oml };
|
|
105
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { DefaultRenameProvider, type LangiumServices } from 'langium/lsp';
|
|
4
|
+
import type { Position, Range, RenameParams, TextDocumentPositionParams, WorkspaceEdit } from 'vscode-languageserver-protocol';
|
|
5
|
+
import { CstUtils, GrammarUtils, URI } from 'langium';
|
|
6
|
+
import type { LangiumDocument } from 'langium';
|
|
7
|
+
import { TextEdit } from 'vscode-languageserver-types';
|
|
8
|
+
import { isOntology, type Ontology } from './generated/ast.js';
|
|
9
|
+
|
|
10
|
+
export class OmlRenameProvider extends DefaultRenameProvider {
|
|
11
|
+
private readonly documents;
|
|
12
|
+
|
|
13
|
+
constructor(services: LangiumServices) {
|
|
14
|
+
super(services);
|
|
15
|
+
this.documents = services.shared.workspace.LangiumDocuments;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override async rename(
|
|
19
|
+
document: LangiumDocument,
|
|
20
|
+
params: RenameParams,
|
|
21
|
+
cancelToken?: any
|
|
22
|
+
): Promise<WorkspaceEdit | undefined> {
|
|
23
|
+
const namespaceRename = this.renameOntologyNamespace(document, params.position, params.newName);
|
|
24
|
+
if (namespaceRename) {
|
|
25
|
+
return namespaceRename;
|
|
26
|
+
}
|
|
27
|
+
const result = await super.rename(document, params, cancelToken);
|
|
28
|
+
if (!result?.changes) {
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
for (const [uri, edits] of Object.entries(result.changes)) {
|
|
32
|
+
const refDoc = await this.documents.getOrCreateDocument(URI.parse(uri));
|
|
33
|
+
edits.forEach((edit) => {
|
|
34
|
+
const refText = refDoc.textDocument.getText(edit.range);
|
|
35
|
+
edit.newText = this.buildReplacement(refText, params.newName);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override async prepareRename(
|
|
42
|
+
document: LangiumDocument,
|
|
43
|
+
params: TextDocumentPositionParams,
|
|
44
|
+
cancelToken?: any
|
|
45
|
+
) {
|
|
46
|
+
const target = this.findOntologyNamespaceRenameTarget(document, params.position);
|
|
47
|
+
if (target) {
|
|
48
|
+
return target.range;
|
|
49
|
+
}
|
|
50
|
+
return super.prepareRename(document, params, cancelToken);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private renameOntologyNamespace(document: LangiumDocument, position: Position, newName: string): WorkspaceEdit | undefined {
|
|
54
|
+
const target = this.findOntologyNamespaceRenameTarget(document, position);
|
|
55
|
+
if (!target) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const newNamespaceText = this.toNamespaceToken(newName);
|
|
60
|
+
const changes: Record<string, TextEdit[]> = {};
|
|
61
|
+
changes[document.uri.toString()] = [TextEdit.replace(target.range, newNamespaceText)];
|
|
62
|
+
|
|
63
|
+
const references = this.references.findReferences(target.ontology, { includeDeclaration: false });
|
|
64
|
+
for (const ref of references) {
|
|
65
|
+
const uri = ref.sourceUri.toString();
|
|
66
|
+
const change = TextEdit.replace(ref.segment.range, newNamespaceText);
|
|
67
|
+
if (changes[uri]) {
|
|
68
|
+
changes[uri].push(change);
|
|
69
|
+
} else {
|
|
70
|
+
changes[uri] = [change];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { changes };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private findOntologyNamespaceRenameTarget(
|
|
77
|
+
document: LangiumDocument,
|
|
78
|
+
position: Position
|
|
79
|
+
): { ontology: Ontology; range: Range } | undefined {
|
|
80
|
+
const root = document.parseResult?.value;
|
|
81
|
+
const rootCst = root?.$cstNode;
|
|
82
|
+
if (!rootCst || !root || !isOntology(root)) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const offset = document.textDocument.offsetAt(position);
|
|
87
|
+
const namespaceNode = GrammarUtils.findNodeForProperty(rootCst, 'namespace');
|
|
88
|
+
if (namespaceNode && offset >= namespaceNode.offset && offset <= namespaceNode.offset + namespaceNode.length) {
|
|
89
|
+
return { ontology: root, range: namespaceNode.range };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const leaf = CstUtils.findLeafNodeAtOffset(rootCst, offset);
|
|
93
|
+
if (!leaf) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const declarations = this.references.findDeclarations(leaf);
|
|
97
|
+
const ontology = declarations.find((node): node is Ontology => isOntology(node));
|
|
98
|
+
if (!ontology) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const ontologyNamespaceNode = GrammarUtils.findNodeForProperty(ontology.$cstNode, 'namespace');
|
|
102
|
+
if (!ontologyNamespaceNode) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return { ontology, range: ontologyNamespaceNode.range };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private toNamespaceToken(name: string): string {
|
|
109
|
+
const trimmed = name.trim();
|
|
110
|
+
if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
|
|
111
|
+
return trimmed;
|
|
112
|
+
}
|
|
113
|
+
return `<${trimmed.replace(/^<|>$/g, '')}>`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private buildReplacement(refText: string, newName: string): string {
|
|
117
|
+
if (refText.startsWith('<') && refText.endsWith('>')) {
|
|
118
|
+
const inner = refText.slice(1, -1);
|
|
119
|
+
return `<${this.replaceIriTail(inner, newName)}>`;
|
|
120
|
+
}
|
|
121
|
+
if (refText.includes('://')) {
|
|
122
|
+
return this.replaceIriTail(refText, newName);
|
|
123
|
+
}
|
|
124
|
+
const qnameMatch = refText.match(/^([A-Za-z_][\w-]*:)[A-Za-z_][\w-]*$/);
|
|
125
|
+
if (qnameMatch) {
|
|
126
|
+
return `${qnameMatch[1]}${newName}`;
|
|
127
|
+
}
|
|
128
|
+
return newName;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private replaceIriTail(iri: string, newName: string): string {
|
|
132
|
+
const hashIdx = iri.lastIndexOf('#');
|
|
133
|
+
const slashIdx = iri.lastIndexOf('/');
|
|
134
|
+
const idx = Math.max(hashIdx, slashIdx);
|
|
135
|
+
if (idx >= 0) {
|
|
136
|
+
return `${iri.slice(0, idx + 1)}${newName}`;
|
|
137
|
+
}
|
|
138
|
+
return newName;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OML Scope Provider
|
|
5
|
+
* Handles cross-file reference resolution, IRI abbreviation, and qualified name scoping
|
|
6
|
+
*
|
|
7
|
+
* Provides enhanced scope resolution for references across imported ontologies and abbreviated IRI names.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { AstUtils, DefaultScopeComputation, DefaultScopeProvider, EMPTY_SCOPE, URI, stream } from 'langium';
|
|
11
|
+
import type { AstNodeDescription, LangiumCoreServices, ReferenceInfo, Scope } from 'langium';
|
|
12
|
+
import type { LangiumDocument } from 'langium';
|
|
13
|
+
import type { LangiumSharedServices } from 'langium/lsp';
|
|
14
|
+
import type { CancellationToken } from 'vscode-languageserver';
|
|
15
|
+
import { type Import, type Ontology, isOntology, isRelationBase, isRelationEntity } from './generated/ast.js';
|
|
16
|
+
import { getOntologyModelIndex } from './oml-index.js';
|
|
17
|
+
import {
|
|
18
|
+
normalizeNamespace,
|
|
19
|
+
} from './oml-utils.js';
|
|
20
|
+
|
|
21
|
+
interface ExportedSymbol {
|
|
22
|
+
node: any;
|
|
23
|
+
name: string;
|
|
24
|
+
relationAlias: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface WorkspaceOntology {
|
|
28
|
+
document: LangiumDocument;
|
|
29
|
+
ontology: Ontology;
|
|
30
|
+
namespace: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ResolvedImport {
|
|
34
|
+
prefix?: string;
|
|
35
|
+
target: WorkspaceOntology;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class OmlScopeComputation extends DefaultScopeComputation {
|
|
39
|
+
constructor(services: LangiumCoreServices) {
|
|
40
|
+
super(services);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override async collectExportedSymbols(
|
|
44
|
+
document: LangiumDocument,
|
|
45
|
+
cancelToken?: CancellationToken
|
|
46
|
+
): Promise<AstNodeDescription[]> {
|
|
47
|
+
let exported: AstNodeDescription[];
|
|
48
|
+
try {
|
|
49
|
+
exported = await super.collectExportedSymbols(document, cancelToken);
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const root = document.parseResult?.value;
|
|
54
|
+
if (!root || !isOntology(root)) {
|
|
55
|
+
return exported;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const namespace = ((root as any).namespace ?? '').toString().replace(/^<|>$/g, '').trim();
|
|
59
|
+
if (!namespace) {
|
|
60
|
+
return exported;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const exportedNames = new Set(exported.filter((desc) => desc.node === root).map((desc) => desc.name));
|
|
64
|
+
const rawName = `<${namespace}>`;
|
|
65
|
+
if (!exportedNames.has(rawName)) {
|
|
66
|
+
exported.push(this.descriptions.createDescription(root, rawName, document));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Default Langium exports only the root and its direct children. OML also needs
|
|
70
|
+
// nested forward/reverse relations exported so workspace-wide completion and
|
|
71
|
+
// cross-document linking can resolve them as SemanticProperty references.
|
|
72
|
+
const nestedExportKeys = new Set(exported.map((desc) => `${desc.path}|${desc.name}`));
|
|
73
|
+
const statements = (root as any).ownedStatements ?? [];
|
|
74
|
+
for (const statement of statements) {
|
|
75
|
+
if (!statement || typeof statement !== 'object') {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const forwardRelation = (statement as any).forwardRelation;
|
|
79
|
+
if (forwardRelation?.name) {
|
|
80
|
+
const key = `${document.uri.toString()}#${forwardRelation.$cstNode?.offset ?? ''}|${forwardRelation.name}`;
|
|
81
|
+
if (!nestedExportKeys.has(key)) {
|
|
82
|
+
exported.push(this.descriptions.createDescription(forwardRelation, forwardRelation.name as string, document));
|
|
83
|
+
nestedExportKeys.add(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (isRelationBase(statement) && (statement as any).reverseRelation?.name) {
|
|
87
|
+
const reverseRelation = (statement as any).reverseRelation;
|
|
88
|
+
const key = `${document.uri.toString()}#${reverseRelation.$cstNode?.offset ?? ''}|${reverseRelation.name}`;
|
|
89
|
+
if (!nestedExportKeys.has(key)) {
|
|
90
|
+
exported.push(this.descriptions.createDescription(reverseRelation, reverseRelation.name as string, document));
|
|
91
|
+
nestedExportKeys.add(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return exported;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* OML-specific scope provider for resolving cross-document references.
|
|
101
|
+
*/
|
|
102
|
+
export class OmlScopeProvider extends DefaultScopeProvider {
|
|
103
|
+
protected services: LangiumCoreServices;
|
|
104
|
+
|
|
105
|
+
constructor(services: LangiumCoreServices) {
|
|
106
|
+
super(services);
|
|
107
|
+
this.services = services;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override getScope(context: ReferenceInfo): Scope {
|
|
111
|
+
let referenceType = '';
|
|
112
|
+
|
|
113
|
+
// Determine the expected reference type
|
|
114
|
+
try {
|
|
115
|
+
referenceType = this.reflection.getReferenceType(context);
|
|
116
|
+
} catch {
|
|
117
|
+
return EMPTY_SCOPE;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const document = AstUtils.getDocument(context.container);
|
|
121
|
+
const localSymbols = (document as any)?.localSymbols;
|
|
122
|
+
// Linking should stay local/import-based. Workspace-global scope is only for ontology imports.
|
|
123
|
+
let result: Scope = referenceType === 'Ontology'
|
|
124
|
+
? this.getGlobalScope(referenceType, context)
|
|
125
|
+
: EMPTY_SCOPE;
|
|
126
|
+
|
|
127
|
+
// Add lexical local symbols (same behavior as DefaultScopeProvider, minus workspace-global fallback).
|
|
128
|
+
if (localSymbols) {
|
|
129
|
+
const scopes = [];
|
|
130
|
+
let currentNode: any = context.container;
|
|
131
|
+
do {
|
|
132
|
+
if (localSymbols.has(currentNode)) {
|
|
133
|
+
scopes.push(localSymbols.getStream(currentNode).filter((desc: any) => this.reflection.isSubtype(desc.type, referenceType)));
|
|
134
|
+
}
|
|
135
|
+
currentNode = currentNode.$container;
|
|
136
|
+
} while (currentNode);
|
|
137
|
+
|
|
138
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
139
|
+
result = this.createScope(scopes[i], result);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Enhance with cross-document IRI abbreviations from imports
|
|
144
|
+
const ontology = document?.parseResult?.value as Ontology | undefined;
|
|
145
|
+
|
|
146
|
+
if (ontology) {
|
|
147
|
+
result = this.addCurrentOntologyEntries(result, document, ontology);
|
|
148
|
+
result = this.addImportedOntologyEntries(result, ontology);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private getExportedSymbols(ontology: Ontology): ExportedSymbol[] {
|
|
155
|
+
const symbols: ExportedSymbol[] = [];
|
|
156
|
+
const statements = (ontology as any).ownedStatements || (ontology as any).ownedMembers || [];
|
|
157
|
+
for (const statement of statements) {
|
|
158
|
+
if (!statement || typeof statement !== 'object') {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if ('name' in statement && (statement as any).name) {
|
|
162
|
+
symbols.push({ node: statement, name: (statement as any).name as string, relationAlias: false });
|
|
163
|
+
}
|
|
164
|
+
if (isRelationEntity(statement) && (statement as any).forwardRelation?.name) {
|
|
165
|
+
symbols.push({
|
|
166
|
+
node: (statement as any).forwardRelation,
|
|
167
|
+
name: (statement as any).forwardRelation.name as string,
|
|
168
|
+
relationAlias: true
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (isRelationBase(statement) && (statement as any).reverseRelation?.name) {
|
|
172
|
+
symbols.push({
|
|
173
|
+
node: (statement as any).reverseRelation,
|
|
174
|
+
name: (statement as any).reverseRelation.name as string,
|
|
175
|
+
relationAlias: true
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return symbols;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private getImportNamespace(imp: Import): string {
|
|
183
|
+
// IMPORTANT: never dereference `imp.imported.ref` while building scopes,
|
|
184
|
+
// otherwise linking can recursively try to resolve the same import.
|
|
185
|
+
const raw = (imp.imported as any)?.$refText
|
|
186
|
+
?? (imp as any)?.imported
|
|
187
|
+
?? '';
|
|
188
|
+
return typeof raw === 'string' ? raw.replace(/^<|>$/g, '') : '';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private resolveOntologyDocument(modelUri: string): WorkspaceOntology | undefined {
|
|
192
|
+
const document = this.services.shared.workspace.LangiumDocuments.getDocument(URI.parse(modelUri));
|
|
193
|
+
if (!document) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const root = document.parseResult?.value;
|
|
197
|
+
if (!root || !isOntology(root)) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const namespace = ((root as any).namespace ?? '').toString().replace(/^<|>$/g, '');
|
|
201
|
+
if (!namespace) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
document,
|
|
206
|
+
ontology: root,
|
|
207
|
+
namespace
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private findOntologyByNamespace(normalizedNamespace: string): WorkspaceOntology | undefined {
|
|
212
|
+
const index = getOntologyModelIndex(this.services.shared as LangiumSharedServices);
|
|
213
|
+
const modelUri = index.resolveModelUri(normalizedNamespace);
|
|
214
|
+
return modelUri ? this.resolveOntologyDocument(modelUri) : undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private resolveImports(ontology: Ontology): ResolvedImport[] {
|
|
218
|
+
const imports = ((ontology as any).ownedImports ?? []) as Import[];
|
|
219
|
+
const resolved: ResolvedImport[] = [];
|
|
220
|
+
for (const imp of imports) {
|
|
221
|
+
const ns = this.getImportNamespace(imp);
|
|
222
|
+
const normalized = normalizeNamespace(ns);
|
|
223
|
+
if (!normalized) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const target = this.findOntologyByNamespace(normalized);
|
|
227
|
+
if (!target) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
resolved.push({ prefix: imp.prefix, target });
|
|
231
|
+
}
|
|
232
|
+
return resolved;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private addDescription(scope: Scope, node: any, name: string, document: LangiumDocument): Scope {
|
|
236
|
+
try {
|
|
237
|
+
const desc = this.descriptions.createDescription(node, name, document);
|
|
238
|
+
return this.createScope(stream([desc]), scope);
|
|
239
|
+
} catch {
|
|
240
|
+
return scope;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private addCurrentOntologyEntries(scope: Scope, document: LangiumDocument, ontology: Ontology): Scope {
|
|
245
|
+
let result = scope;
|
|
246
|
+
const prefix = (ontology as any).prefix as string | undefined;
|
|
247
|
+
for (const symbol of this.getExportedSymbols(ontology)) {
|
|
248
|
+
if (prefix) {
|
|
249
|
+
result = this.addDescription(result, symbol.node, `${prefix}:${symbol.name}`, document);
|
|
250
|
+
}
|
|
251
|
+
if (symbol.relationAlias) {
|
|
252
|
+
result = this.addDescription(result, symbol.node, symbol.name, document);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private addImportedOntologyEntries(scope: Scope, current: Ontology): Scope {
|
|
259
|
+
let result = scope;
|
|
260
|
+
for (const resolvedImport of this.resolveImports(current)) {
|
|
261
|
+
const imported = resolvedImport.target;
|
|
262
|
+
const importedSymbols = this.getExportedSymbols(imported.ontology);
|
|
263
|
+
for (const symbol of importedSymbols) {
|
|
264
|
+
if (resolvedImport.prefix) {
|
|
265
|
+
result = this.addDescription(result, symbol.node, `${resolvedImport.prefix}:${symbol.name}`, imported.document);
|
|
266
|
+
}
|
|
267
|
+
const separator = imported.namespace.endsWith('#') || imported.namespace.endsWith('/') ? '' : '#';
|
|
268
|
+
result = this.addDescription(
|
|
269
|
+
result,
|
|
270
|
+
symbol.node,
|
|
271
|
+
`<${imported.namespace}${separator}${symbol.name}>`,
|
|
272
|
+
imported.document
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
}
|