@likec4/language-server 0.48.0 → 0.50.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/dist/ast.js CHANGED
@@ -47,11 +47,10 @@ export function cleanParsedModel(doc) {
47
47
  c4Relations: [],
48
48
  c4Views: []
49
49
  };
50
- Object.assign(doc, props);
51
- return doc;
50
+ return Object.assign(doc, props);
52
51
  }
53
52
  export function isFqnIndexedDocument(doc) {
54
- return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !isNil(doc.c4fqns);
53
+ return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !!doc.c4fqns;
55
54
  }
56
55
  export function isLikeC4LangiumDocument(doc) {
57
56
  return doc.textDocument.languageId === LikeC4LanguageMetaData.languageId;
@@ -5,5 +5,6 @@ export declare class LikeC4DocumentLinkProvider implements DocumentLinkProvider
5
5
  private services;
6
6
  constructor(services: LikeC4Services);
7
7
  getDocumentLinks(doc: LangiumDocument, _params: DocumentLinkParams): MaybePromise<DocumentLink[]>;
8
+ resolveLink(doc: LangiumDocument, link: string): string;
8
9
  }
9
10
  //# sourceMappingURL=DocumentLinkProvider.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { findNodeForProperty, streamAllContents } from "langium";
2
+ import { hasProtocol, isRelative, withBase } from "ufo";
2
3
  import { ast, isParsedLikeC4LangiumDocument } from "../ast.js";
3
4
  import { logError } from "../logger.js";
4
5
  export class LikeC4DocumentLinkProvider {
@@ -9,17 +10,16 @@ export class LikeC4DocumentLinkProvider {
9
10
  if (!isParsedLikeC4LangiumDocument(doc)) {
10
11
  return [];
11
12
  }
12
- const base = new URL(doc.uri.toString());
13
13
  return streamAllContents(doc.parseResult.value).filter(ast.isLinkProperty).flatMap((n) => {
14
14
  try {
15
- const u = new URL(n.value, base);
16
- const valueCst = findNodeForProperty(n.$cstNode, "value");
17
- if (!valueCst) {
15
+ const range = findNodeForProperty(n.$cstNode, "value")?.range;
16
+ if (!range) {
18
17
  return [];
19
18
  }
19
+ const target = this.resolveLink(doc, n.value);
20
20
  return {
21
- range: valueCst.range,
22
- target: u.toString()
21
+ range,
22
+ target
23
23
  };
24
24
  } catch (e) {
25
25
  logError(e);
@@ -27,4 +27,15 @@ export class LikeC4DocumentLinkProvider {
27
27
  }
28
28
  }).toArray();
29
29
  }
30
+ resolveLink(doc, link) {
31
+ if (hasProtocol(link)) {
32
+ return link;
33
+ }
34
+ if (isRelative(link)) {
35
+ const base = new URL(doc.uri.toString(true));
36
+ return new URL(link, base).toString();
37
+ }
38
+ const workspace = this.services.shared.workspace.WorkspaceManager.workspaceURL;
39
+ return withBase(link, workspace.toString());
40
+ }
30
41
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DocumentLinkProvider.test.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { beforeAll, describe, expect, it, vi } from "vitest";
2
+ import { createTestServices } from "../test/index.js";
3
+ vi.mock("../logger");
4
+ describe("DocumentLinkProvider", () => {
5
+ let services;
6
+ let doc;
7
+ let documentLinkProvider;
8
+ beforeAll(async () => {
9
+ const test = createTestServices("vscode-vfs://host/virtual");
10
+ services = test.services;
11
+ documentLinkProvider = services.lsp.DocumentLinkProvider;
12
+ doc = await test.parse(
13
+ `
14
+ specification {
15
+ element component
16
+ }
17
+ `,
18
+ "dir1/doc.c4"
19
+ );
20
+ });
21
+ it("test should have correct doc uri and workspace uri", () => {
22
+ expect(services.shared.workspace.WorkspaceManager.workspaceUri.toString()).toBe(
23
+ "vscode-vfs://host/virtual"
24
+ );
25
+ expect(services.shared.workspace.WorkspaceManager.workspaceURL.toString()).toBe(
26
+ "vscode-vfs://host/virtual"
27
+ );
28
+ expect(doc.uri.toString()).toBe("vscode-vfs://host/virtual/src/dir1/doc.c4");
29
+ });
30
+ it("should return the link unchanged if it has a protocol", () => {
31
+ const link = "http://example.com/link";
32
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(link);
33
+ });
34
+ it("should resolve a relative link against the document URI", () => {
35
+ const link = "./relative/link#fragment";
36
+ const expected = "vscode-vfs://host/virtual/src/dir1/relative/link#fragment";
37
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected);
38
+ });
39
+ it("should resolve a parent relative link against the document URI", () => {
40
+ const link = "../dir2/link?query=1#L1=22";
41
+ const expected = "vscode-vfs://host/virtual/src/dir2/link?query=1#L1=22";
42
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected);
43
+ });
44
+ it("should resolve a link against the workspace URL", () => {
45
+ const link = "/root";
46
+ const expected = "vscode-vfs://host/virtual/root";
47
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected);
48
+ });
49
+ it("should resolve a link with quary and hash against the workspace URL", () => {
50
+ const link = "/root/a/b/c/link?query=1#L1=22";
51
+ const expected = "vscode-vfs://host/virtual/root/a/b/c/link?query=1#L1=22";
52
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected);
53
+ });
54
+ });
@@ -11,10 +11,7 @@ import { isValidLikeC4LangiumDocument } from "../ast.js";
11
11
  import { logError, logWarnError, logger } from "../logger.js";
12
12
  import { printDocs } from "../utils.js";
13
13
  import { assignNavigateTo, resolveRelativePaths, resolveRulesExtendedViews } from "../view-utils/index.js";
14
- function isRelativeLink(link) {
15
- return link.startsWith(".") || link.startsWith("/");
16
- }
17
- function buildModel(docs) {
14
+ function buildModel(services, docs) {
18
15
  const c4Specification = {
19
16
  kinds: {},
20
17
  relationships: {}
@@ -23,9 +20,8 @@ function buildModel(docs) {
23
20
  Object.assign(c4Specification.kinds, spec.kinds), Object.assign(c4Specification.relationships, spec.relationships);
24
21
  });
25
22
  const resolveLinks = (doc, links) => {
26
- const base = new URL(doc.uri.toString());
27
23
  return links.map(
28
- (l) => isRelativeLink(l) ? new URL(l, base).toString() : l
24
+ (l) => services.lsp.DocumentLinkProvider.resolveLink(doc, l)
29
25
  );
30
26
  };
31
27
  const toModelElement = (doc) => {
@@ -190,7 +186,7 @@ ${printDocs(docs)}`);
190
186
  }
191
187
  logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:
192
188
  ${printDocs(docs)}`);
193
- return buildModel(docs);
189
+ return buildModel(this.services, docs);
194
190
  } catch (e) {
195
191
  logError(e);
196
192
  return null;
@@ -1,6 +1,6 @@
1
1
  import { type c4 } from '@likec4/core';
2
2
  import type { LangiumDocument } from 'langium';
3
- import type { LikeC4LangiumDocument, ParsedLikeC4LangiumDocument } from '../ast';
3
+ import type { FqnIndexedDocument, ParsedLikeC4LangiumDocument } from '../ast';
4
4
  import { ast } from '../ast';
5
5
  import type { LikeC4Services } from '../module';
6
6
  export type ModelParsedListener = () => void;
@@ -9,7 +9,7 @@ export declare class LikeC4ModelParser {
9
9
  private fqnIndex;
10
10
  constructor(services: LikeC4Services);
11
11
  parse(doc: LangiumDocument | LangiumDocument[]): ParsedLikeC4LangiumDocument[];
12
- protected parseLikeC4Document(_doc: LikeC4LangiumDocument): ParsedLikeC4LangiumDocument;
12
+ protected parseLikeC4Document(_doc: FqnIndexedDocument): ParsedLikeC4LangiumDocument;
13
13
  private parseSpecification;
14
14
  private parseModel;
15
15
  private parseElement;
@@ -1,18 +1,13 @@
1
- import {
2
- InvalidModelError,
3
- invariant,
4
- isNonEmptyArray,
5
- nonexhaustive
6
- } from "@likec4/core";
1
+ import { InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from "@likec4/core";
7
2
  import { getDocument } from "langium";
8
3
  import objectHash from "object-hash";
4
+ import { isTruthy } from "remeda";
9
5
  import stripIndent from "strip-indent";
10
6
  import {
11
7
  ElementViewOps,
12
8
  ast,
13
9
  cleanParsedModel,
14
10
  isFqnIndexedDocument,
15
- isLikeC4LangiumDocument,
16
11
  resolveRelationPoints,
17
12
  streamModel,
18
13
  toAutoLayout,
@@ -22,7 +17,6 @@ import {
22
17
  } from "../ast.js";
23
18
  import { elementRef, getFqnElementRef } from "../elementRef.js";
24
19
  import { logError, logWarnError, logger } from "../logger.js";
25
- import { isTruthy } from "remeda";
26
20
  function toSingleLine(str) {
27
21
  return str?.split("\n").join(" ").trim();
28
22
  }
@@ -40,7 +34,8 @@ export class LikeC4ModelParser {
40
34
  const docs = Array.isArray(doc) ? doc : [doc];
41
35
  const result = [];
42
36
  for (const doc2 of docs) {
43
- if (!isLikeC4LangiumDocument(doc2)) {
37
+ if (!isFqnIndexedDocument(doc2)) {
38
+ logger.warn(`Not a FqnIndexedDocument: ${doc2.uri.toString(true)}`);
44
39
  continue;
45
40
  }
46
41
  try {
@@ -52,7 +47,6 @@ export class LikeC4ModelParser {
52
47
  return result;
53
48
  }
54
49
  parseLikeC4Document(_doc) {
55
- invariant(isFqnIndexedDocument(_doc), "Not a FqnIndexedDocument");
56
50
  const doc = cleanParsedModel(_doc);
57
51
  this.parseSpecification(doc);
58
52
  this.parseModel(doc);
package/dist/module.d.ts CHANGED
@@ -1,7 +1,18 @@
1
1
  import { WorkspaceCache, type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type Module, type PartialLangiumServices } from 'langium';
2
- import { LikeC4DocumentSymbolProvider } from './lsp';
3
- import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from './model';
4
2
  import { Rpc } from './Rpc';
3
+ import { LikeC4DocumentLinkProvider, LikeC4DocumentSymbolProvider } from './lsp';
4
+ import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from './model';
5
+ import { LikeC4WorkspaceManager, NodeKindProvider, WorkspaceSymbolProvider } from './shared';
6
+ interface LikeC4AddedSharedServices {
7
+ lsp: {
8
+ NodeKindProvider: NodeKindProvider;
9
+ WorkspaceSymbolProvider: WorkspaceSymbolProvider;
10
+ };
11
+ workspace: {
12
+ WorkspaceManager: LikeC4WorkspaceManager;
13
+ };
14
+ }
15
+ export type LikeC4SharedServices = LangiumSharedServices & LikeC4AddedSharedServices;
5
16
  /**
6
17
  * Declaration of custom services - add your own service classes here.
7
18
  */
@@ -15,14 +26,16 @@ export interface LikeC4AddedServices {
15
26
  ModelLocator: LikeC4ModelLocator;
16
27
  };
17
28
  lsp: {
29
+ DocumentLinkProvider: LikeC4DocumentLinkProvider;
18
30
  DocumentSymbolProvider: LikeC4DocumentSymbolProvider;
19
31
  };
32
+ shared?: LikeC4SharedServices;
20
33
  }
21
34
  export type LikeC4Services = LangiumServices & LikeC4AddedServices;
22
35
  export declare const LikeC4Module: Module<LikeC4Services, PartialLangiumServices & LikeC4AddedServices>;
23
36
  type LanguageServicesContext = Partial<DefaultSharedModuleContext>;
24
37
  export declare function createLanguageServices(context?: LanguageServicesContext): {
25
- shared: LangiumSharedServices;
38
+ shared: LikeC4SharedServices;
26
39
  likec4: LikeC4Services;
27
40
  };
28
41
  export {};
package/dist/module.js CHANGED
@@ -1,8 +1,14 @@
1
+ import { serializeError } from "@likec4/core";
1
2
  import {
2
- WorkspaceCache
3
+ EmptyFileSystem,
4
+ WorkspaceCache,
5
+ createDefaultModule,
6
+ createDefaultSharedModule,
7
+ inject
3
8
  } from "langium";
4
- import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from "langium";
9
+ import { Rpc } from "./Rpc.js";
5
10
  import { LikeC4GeneratedModule, LikeC4GeneratedSharedModule } from "./generated/module.js";
11
+ import { logger } from "./logger.js";
6
12
  import {
7
13
  LikeC4CodeLensProvider,
8
14
  LikeC4DocumentLinkProvider,
@@ -12,11 +18,17 @@ import {
12
18
  } from "./lsp/index.js";
13
19
  import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from "./model/index.js";
14
20
  import { LikeC4ScopeComputation, LikeC4ScopeProvider } from "./references/index.js";
15
- import { Rpc } from "./Rpc.js";
21
+ import { LikeC4WorkspaceManager, NodeKindProvider, WorkspaceSymbolProvider } from "./shared/index.js";
16
22
  import { registerValidationChecks } from "./validation/index.js";
17
- import { logger } from "./logger.js";
18
- import { serializeError } from "@likec4/core";
19
- import { WorkspaceSymbolProvider, NodeKindProvider } from "./shared/index.js";
23
+ const LikeC4SharedModule = {
24
+ lsp: {
25
+ NodeKindProvider: (services) => new NodeKindProvider(services),
26
+ WorkspaceSymbolProvider: (services) => new WorkspaceSymbolProvider(services)
27
+ },
28
+ workspace: {
29
+ WorkspaceManager: (services) => new LikeC4WorkspaceManager(services)
30
+ }
31
+ };
20
32
  function bind(Type) {
21
33
  return (services) => new Type(services);
22
34
  }
@@ -41,15 +53,6 @@ export const LikeC4Module = {
41
53
  ScopeProvider: bind(LikeC4ScopeProvider)
42
54
  }
43
55
  };
44
- const LikeC4SharedModule = {
45
- lsp: {
46
- NodeKindProvider: (services) => new NodeKindProvider(services),
47
- WorkspaceSymbolProvider: (services) => new WorkspaceSymbolProvider(services)
48
- }
49
- // workspace: {
50
- // WorkspaceManager: services => new LikeC4WorkspaceManager(services)
51
- // }
52
- };
53
56
  export function createLanguageServices(context) {
54
57
  const connection = context?.connection;
55
58
  if (connection) {
@@ -1,6 +1,8 @@
1
+ /// <reference types="node" />
1
2
  import type { LangiumDocument } from 'langium';
2
3
  import { DefaultWorkspaceManager } from 'langium';
3
4
  import type { WorkspaceFolder } from 'vscode-languageserver';
5
+ import { URI } from 'vscode-uri';
4
6
  export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
5
7
  /**
6
8
  * Load all additional documents that shall be visible in the context of the given workspace
@@ -9,5 +11,7 @@ export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
9
11
  */
10
12
  protected loadAdditionalDocuments(_folders: WorkspaceFolder[], _collector: (document: LangiumDocument) => void): Promise<void>;
11
13
  workspace(): WorkspaceFolder | null;
14
+ get workspaceUri(): URI;
15
+ get workspaceURL(): import("url").URL;
12
16
  }
13
17
  //# sourceMappingURL=WorkspaceManager.d.ts.map
@@ -1,5 +1,6 @@
1
- import { nonNullable } from "@likec4/core";
1
+ import { hasAtLeast, invariant } from "@likec4/core";
2
2
  import { DefaultWorkspaceManager } from "langium";
3
+ import { URI } from "vscode-uri";
3
4
  export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
4
5
  /**
5
6
  * Load all additional documents that shall be visible in the context of the given workspace
@@ -10,6 +11,19 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
10
11
  return Promise.resolve();
11
12
  }
12
13
  workspace() {
13
- return this.folders && this.folders.length > 0 ? nonNullable(this.folders[0]) : null;
14
+ if (this.folders && hasAtLeast(this.folders, 1)) {
15
+ return this.folders[0];
16
+ }
17
+ return null;
18
+ }
19
+ get workspaceUri() {
20
+ const workspace = this.workspace();
21
+ invariant(workspace, "Workspace not initialized");
22
+ return URI.parse(workspace.uri);
23
+ }
24
+ get workspaceURL() {
25
+ const workspace = this.workspace();
26
+ invariant(workspace, "Workspace not initialized");
27
+ return new URL(workspace.uri);
14
28
  }
15
29
  }
@@ -1,9 +1,9 @@
1
- import { createLanguageServices } from "../module.js";
2
1
  import { EmptyFileSystem } from "langium";
3
- import { URI, Utils } from "vscode-uri";
4
- import stripIndent from "strip-indent";
5
2
  import * as assert from "node:assert";
3
+ import stripIndent from "strip-indent";
6
4
  import { DiagnosticSeverity } from "vscode-languageserver-protocol";
5
+ import { URI, Utils } from "vscode-uri";
6
+ import { createLanguageServices } from "../module.js";
7
7
  export function createTestServices(workspace = "file:///test/workspace") {
8
8
  const services = createLanguageServices(EmptyFileSystem).likec4;
9
9
  const metaData = services.LanguageMetaData;
package/dist/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- export const printDocs = (docs) => docs.map((d) => " - " + d.uri).join("\n");
1
+ export const printDocs = (docs) => docs.map((d) => " - " + d.uri.toString(true)).join("\n");
2
2
  export function queueMicrotask(cb) {
3
3
  return Promise.resolve().then(cb);
4
4
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "0.48.0",
4
+ "version": "0.50.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -50,27 +50,28 @@
50
50
  "test": "vitest run"
51
51
  },
52
52
  "dependencies": {
53
- "@likec4/core": "0.48.0",
54
- "@likec4/graph": "0.48.0",
55
- "langium": "^2.1.2",
53
+ "@likec4/core": "0.50.0",
54
+ "@likec4/graph": "0.50.0",
55
+ "langium": "^2.1.3",
56
56
  "object-hash": "^3.0.0",
57
57
  "p-debounce": "^4.0.0",
58
58
  "p-throttle": "^5.1.0",
59
59
  "rambdax": "^9.1.1",
60
60
  "remeda": "^1.29.0",
61
61
  "strip-indent": "^4.0.0",
62
- "type-fest": "^4.8.1",
62
+ "type-fest": "^4.8.2",
63
+ "ufo": "^1.3.2",
63
64
  "vscode-languageserver": "9.0.1",
64
65
  "vscode-languageserver-protocol": "3.17.5",
65
66
  "vscode-uri": "3.0.8"
66
67
  },
67
68
  "devDependencies": {
68
- "@types/node": "^20.8.7",
69
+ "@types/node": "^20.10.1",
69
70
  "@types/object-hash": "^3.0.6",
70
71
  "execa": "^8.0.1",
71
72
  "langium-cli": "^2.1.0",
72
73
  "npm-run-all": "^4.1.5",
73
- "typescript": "^5.2.2",
74
+ "typescript": "^5.3.2",
74
75
  "unbuild": "^2.0.0",
75
76
  "vitest": "^0.34.6"
76
77
  },