@oml/language 0.14.16 → 0.15.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/out/oml/generated/ast.d.ts +1 -1
- package/out/oml/generated/ast.js +1 -1
- package/out/oml/generated/grammar.d.ts +1 -1
- package/out/oml/generated/grammar.js +1 -1
- package/out/oml/generated/module.d.ts +1 -1
- package/out/oml/generated/module.js +1 -1
- package/out/oml/oml-candidates.js +1 -42
- package/out/oml/oml-candidates.js.map +1 -1
- package/out/oml/oml-document.js +1 -5
- package/out/oml/oml-document.js.map +1 -1
- package/out/oml/oml-index.d.ts +1 -0
- package/out/oml/oml-index.js +8 -1
- package/out/oml/oml-index.js.map +1 -1
- package/out/oml/oml-scope.js +10 -4
- package/out/oml/oml-scope.js.map +1 -1
- package/out/oml/oml-update.d.ts +32 -15
- package/out/oml/oml-update.js +393 -199
- package/out/oml/oml-update.js.map +1 -1
- package/out/oml/oml-utils.d.ts +18 -0
- package/out/oml/oml-utils.js +183 -26
- package/out/oml/oml-utils.js.map +1 -1
- package/out/oml/oml-workspace.js +1 -4
- package/out/oml/oml-workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/oml/generated/ast.ts +1 -1
- package/src/oml/generated/grammar.ts +1 -1
- package/src/oml/generated/module.ts +1 -1
- package/src/oml/oml-candidates.ts +1 -46
- package/src/oml/oml-document.ts +1 -5
- package/src/oml/oml-index.ts +9 -1
- package/src/oml/oml-scope.ts +10 -4
- package/src/oml/oml-update.ts +459 -223
- package/src/oml/oml-utils.ts +199 -26
- package/src/oml/oml-workspace.ts +1 -4
package/src/oml/oml-utils.ts
CHANGED
|
@@ -120,15 +120,72 @@ export function deref(node: any): any {
|
|
|
120
120
|
return node;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
export const normalizeIri = (value: string): string =>
|
|
124
|
+
String(value ?? '').trim().replace(/^<|>$/g, '');
|
|
125
|
+
|
|
123
126
|
export const normalizeNamespace = (raw: string): string =>
|
|
124
|
-
raw
|
|
127
|
+
normalizeIri(raw).replace(/[#/]?$/, '');
|
|
128
|
+
|
|
129
|
+
export const withNamespaceSeparator = (rawNamespace: string): string => {
|
|
130
|
+
const trimmed = normalizeIri(rawNamespace);
|
|
131
|
+
if (!trimmed) {
|
|
132
|
+
return trimmed;
|
|
133
|
+
}
|
|
134
|
+
return /[#/]$/.test(trimmed) ? trimmed : `${trimmed}#`;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export function resolveImportPrefix(imp: Import): string | undefined {
|
|
138
|
+
const explicitPrefix = typeof imp?.prefix === 'string' ? imp.prefix.trim() : '';
|
|
139
|
+
if (explicitPrefix) {
|
|
140
|
+
return explicitPrefix;
|
|
141
|
+
}
|
|
142
|
+
const resolvedPrefix = typeof (imp as any)?.imported?.ref?.prefix === 'string'
|
|
143
|
+
? (imp as any).imported.ref.prefix.trim()
|
|
144
|
+
: '';
|
|
145
|
+
return resolvedPrefix || undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function resolveImportNamespaceRaw(imp: Import): string | undefined {
|
|
149
|
+
const resolvedNamespace = typeof (imp as any)?.imported?.ref?.namespace === 'string'
|
|
150
|
+
? (imp as any).imported.ref.namespace
|
|
151
|
+
: '';
|
|
152
|
+
if (resolvedNamespace.trim().length > 0) {
|
|
153
|
+
return resolvedNamespace.trim();
|
|
154
|
+
}
|
|
155
|
+
const refText = typeof (imp as any)?.imported?.$refText === 'string'
|
|
156
|
+
? (imp as any).imported.$refText.trim()
|
|
157
|
+
: '';
|
|
158
|
+
return refText || undefined;
|
|
159
|
+
}
|
|
125
160
|
|
|
126
161
|
export const resolveImportNamespace = (imp: Import): string => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
162
|
+
return resolveImportNamespaceRaw(imp) ?? '';
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export type IriParts = { namespace: string; fragment: string; separator: '#' | '/' };
|
|
166
|
+
|
|
167
|
+
export const parseIriParts = (iri: string): IriParts | undefined => {
|
|
168
|
+
const normalized = normalizeIri(iri);
|
|
169
|
+
if (!normalized) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const hashIndex = normalized.lastIndexOf('#');
|
|
173
|
+
if (hashIndex > 0 && hashIndex < normalized.length - 1) {
|
|
174
|
+
return {
|
|
175
|
+
namespace: normalizeNamespace(normalized.slice(0, hashIndex)),
|
|
176
|
+
fragment: normalized.slice(hashIndex + 1),
|
|
177
|
+
separator: '#',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const slashIndex = normalized.lastIndexOf('/');
|
|
181
|
+
if (slashIndex > 0 && slashIndex < normalized.length - 1) {
|
|
182
|
+
return {
|
|
183
|
+
namespace: normalizeNamespace(normalized.slice(0, slashIndex)),
|
|
184
|
+
fragment: normalized.slice(slashIndex + 1),
|
|
185
|
+
separator: '/',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
132
189
|
};
|
|
133
190
|
|
|
134
191
|
export const getAllLangiumDocuments = (documents: LangiumDocuments): LangiumDocument[] => {
|
|
@@ -164,37 +221,153 @@ const OML_LS_IGNORED_DOCUMENT_SCHEMES = new Set([
|
|
|
164
221
|
'chat-editing-text-model',
|
|
165
222
|
'chat-editing-snapshot-text-model',
|
|
166
223
|
'vscode-chat-code-block',
|
|
224
|
+
'copilot-chat-code-block',
|
|
225
|
+
'claude-code-chat-code-block',
|
|
167
226
|
]);
|
|
168
227
|
|
|
169
228
|
export function isIgnoredByOmlLsDocumentUri(uri: string): boolean {
|
|
170
229
|
try {
|
|
171
|
-
|
|
230
|
+
const scheme = URI.parse(uri).scheme.toLowerCase();
|
|
231
|
+
if (OML_LS_IGNORED_DOCUMENT_SCHEMES.has(scheme)) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
return scheme.includes('chat') || scheme.includes('copilot') || scheme.includes('claude');
|
|
172
235
|
} catch {
|
|
173
236
|
return false;
|
|
174
237
|
}
|
|
175
238
|
}
|
|
176
239
|
|
|
177
240
|
export const splitIri = (iri: string): { base: string; fragment: string; separator: string } | null => {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
241
|
+
const parts = parseIriParts(iri);
|
|
242
|
+
return parts
|
|
243
|
+
? { base: parts.namespace, fragment: parts.fragment, separator: parts.separator }
|
|
244
|
+
: null;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export function getRefText(ref: any): string | undefined {
|
|
248
|
+
if (typeof ref === 'string') {
|
|
249
|
+
return ref;
|
|
187
250
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
base: trimmed.slice(0, slashIndex),
|
|
192
|
-
fragment: trimmed.slice(slashIndex + 1),
|
|
193
|
-
separator: '/',
|
|
194
|
-
};
|
|
251
|
+
if (typeof ref?.$refText === 'string') {
|
|
252
|
+
return ref.$refText;
|
|
195
253
|
}
|
|
196
|
-
|
|
197
|
-
|
|
254
|
+
if (typeof ref?.$refNode?.text === 'string') {
|
|
255
|
+
return ref.$refNode.text;
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function getLocalNameForNamespace(iri: string, rawNamespace: string | undefined): string | undefined {
|
|
261
|
+
const namespace = normalizeNamespace(String(rawNamespace ?? ''));
|
|
262
|
+
if (!namespace) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
const parts = parseIriParts(iri);
|
|
266
|
+
return parts?.namespace === namespace ? parts.fragment : undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function expandLocalRefText(rawNamespace: string | undefined, localName: string): string | undefined {
|
|
270
|
+
const local = String(localName ?? '').trim();
|
|
271
|
+
const namespace = withNamespaceSeparator(String(rawNamespace ?? ''));
|
|
272
|
+
if (!namespace || !local) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
return `${namespace}${local}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function expandRefTextToIri(ontology: any, refText: string): string | undefined {
|
|
279
|
+
const trimmed = String(refText ?? '').trim();
|
|
280
|
+
if (!trimmed) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
if (trimmed === 'a') {
|
|
284
|
+
return 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
285
|
+
}
|
|
286
|
+
if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
|
|
287
|
+
return normalizeIri(trimmed);
|
|
288
|
+
}
|
|
289
|
+
if (trimmed.includes('://')) {
|
|
290
|
+
return normalizeIri(trimmed);
|
|
291
|
+
}
|
|
292
|
+
const localInCurrentOntology = expandLocalRefText(ontology?.namespace, trimmed);
|
|
293
|
+
if (localInCurrentOntology) {
|
|
294
|
+
return localInCurrentOntology;
|
|
295
|
+
}
|
|
296
|
+
const separatorIndex = trimmed.indexOf(':');
|
|
297
|
+
if (separatorIndex <= 0) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
const prefix = trimmed.slice(0, separatorIndex);
|
|
301
|
+
const local = trimmed.slice(separatorIndex + 1);
|
|
302
|
+
if (!local) {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
if (typeof ontology?.prefix === 'string' && ontology.prefix.trim() === prefix) {
|
|
306
|
+
return expandLocalRefText(ontology.namespace, local);
|
|
307
|
+
}
|
|
308
|
+
const imports = Array.isArray(ontology?.ownedImports) ? ontology.ownedImports : [];
|
|
309
|
+
for (const imp of imports) {
|
|
310
|
+
if (resolveImportPrefix(imp) !== prefix) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
return expandLocalRefText(resolveImportNamespaceRaw(imp), local);
|
|
314
|
+
}
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function localNameFromRefText(refText: string): string | undefined {
|
|
319
|
+
const trimmed = normalizeIri(refText);
|
|
320
|
+
if (!trimmed) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
const iriParts = parseIriParts(trimmed);
|
|
324
|
+
if (iriParts) {
|
|
325
|
+
return iriParts.fragment;
|
|
326
|
+
}
|
|
327
|
+
const colonIndex = trimmed.indexOf(':');
|
|
328
|
+
if (colonIndex > 0 && colonIndex < trimmed.length - 1) {
|
|
329
|
+
return trimmed.slice(colonIndex + 1);
|
|
330
|
+
}
|
|
331
|
+
return trimmed;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function isSameIriTarget(left: string, right: string): boolean {
|
|
335
|
+
const leftNorm = normalizeIri(left);
|
|
336
|
+
const rightNorm = normalizeIri(right);
|
|
337
|
+
if (leftNorm === rightNorm) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
const leftParts = parseIriParts(leftNorm);
|
|
341
|
+
const rightParts = parseIriParts(rightNorm);
|
|
342
|
+
if (!leftParts || !rightParts) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
return leftParts.namespace === rightParts.namespace
|
|
346
|
+
&& leftParts.fragment === rightParts.fragment;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function pickImportPrefix(namespace: string, usedPrefixes: ReadonlySet<string>): string {
|
|
350
|
+
const normalizedNamespace = normalizeNamespace(namespace);
|
|
351
|
+
const pieces = normalizedNamespace.split('/').filter((piece) => piece.length > 0);
|
|
352
|
+
const tail = (pieces[pieces.length - 1] ?? normalizedNamespace)
|
|
353
|
+
.replace(/[^A-Za-z0-9_]/g, '')
|
|
354
|
+
.toLowerCase();
|
|
355
|
+
const base = /^[A-Za-z_]/.test(tail) ? tail : `ns${tail}`;
|
|
356
|
+
const candidateBase = (base || 'ns').slice(0, 32);
|
|
357
|
+
if (!usedPrefixes.has(candidateBase)) {
|
|
358
|
+
return candidateBase;
|
|
359
|
+
}
|
|
360
|
+
let suffix = 1;
|
|
361
|
+
while (usedPrefixes.has(`${candidateBase}${suffix}`)) {
|
|
362
|
+
suffix += 1;
|
|
363
|
+
}
|
|
364
|
+
return `${candidateBase}${suffix}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function normalizeUriPath(path: string): string {
|
|
368
|
+
const normalized = path.replace(/\/+$/, '');
|
|
369
|
+
return normalized || '/';
|
|
370
|
+
}
|
|
198
371
|
|
|
199
372
|
export const resolveIndent = (text: string, rootOffset: number, insertOffset: number): string => {
|
|
200
373
|
const slice = text.slice(rootOffset, insertOffset);
|
|
@@ -239,7 +412,7 @@ export function getIriOwningElement(node: any): IriOwningElement | undefined {
|
|
|
239
412
|
}
|
|
240
413
|
|
|
241
414
|
export function getNamespaceSeparator(rawNamespace: string | undefined): '#' | '/' {
|
|
242
|
-
const value = (rawNamespace ?? '')
|
|
415
|
+
const value = normalizeIri(rawNamespace ?? '');
|
|
243
416
|
if (value.endsWith('/')) return '/';
|
|
244
417
|
if (value.endsWith('#')) return '#';
|
|
245
418
|
return '#';
|
package/src/oml/oml-workspace.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { URI, type LangiumSharedCoreServices } from 'langium';
|
|
4
4
|
import { DefaultWorkspaceManager } from 'langium';
|
|
5
5
|
import type { Connection } from 'vscode-languageserver';
|
|
6
|
+
import { normalizeUriPath } from './oml-utils.js';
|
|
6
7
|
|
|
7
8
|
// LSP request name — must match the handler registered in node-client.ts
|
|
8
9
|
const GET_WORKSPACE_FOLDERS_REQUEST = 'oml/fs/getWorkspaceFolders';
|
|
@@ -75,7 +76,3 @@ function isUriPathWithinWorkspace(candidate: URI, workspace: URI): boolean {
|
|
|
75
76
|
return candidatePath === workspacePath || candidatePath.startsWith(`${workspacePath}/`);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
function normalizeUriPath(path: string): string {
|
|
79
|
-
const normalized = path.replace(/\/+$/, '');
|
|
80
|
-
return normalized || '/';
|
|
81
|
-
}
|