@likec4/language-server 1.9.0 → 1.10.1

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.
Files changed (52) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +3 -4
  4. package/dist/browser.d.mts +3 -4
  5. package/dist/browser.d.ts +3 -4
  6. package/dist/browser.mjs +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +1 -1
  12. package/dist/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.mjs +1 -1
  14. package/dist/node.cjs +1 -1
  15. package/dist/node.d.cts +3 -4
  16. package/dist/node.d.mts +3 -4
  17. package/dist/node.d.ts +3 -4
  18. package/dist/node.mjs +1 -1
  19. package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.BFBeyvV8.mjs} +486 -108
  20. package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.BGy3FJPJ.d.cts} +43 -14
  21. package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.Bfc-5M8A.cjs} +482 -104
  22. package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.CbqwHp7Q.mjs} +184 -120
  23. package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CnVuAxDh.d.ts} +43 -14
  24. package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.DEK39RmI.d.mts} +43 -14
  25. package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DJhoJBWh.cjs} +180 -116
  26. package/package.json +13 -11
  27. package/src/ast.ts +8 -6
  28. package/src/formatting/LikeC4Formatter.ts +390 -0
  29. package/src/formatting/utils.ts +26 -0
  30. package/src/generated/ast.ts +203 -11
  31. package/src/generated/grammar.ts +2 -2
  32. package/src/generated/module.ts +1 -1
  33. package/src/like-c4.langium +34 -7
  34. package/src/lsp/CompletionProvider.ts +1 -1
  35. package/src/lsp/DocumentLinkProvider.ts +27 -15
  36. package/src/lsp/SemanticTokenProvider.ts +1 -1
  37. package/src/lsp/index.ts +1 -1
  38. package/src/model/fqn-index.ts +0 -1
  39. package/src/model/model-builder.ts +43 -32
  40. package/src/model/model-parser.ts +43 -21
  41. package/src/model-graph/compute-view/compute.ts +111 -80
  42. package/src/model-graph/compute-view/predicates.ts +3 -5
  43. package/src/model-graph/dynamic-view/compute.ts +96 -60
  44. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  45. package/src/model-graph/utils/uniqueTags.test.ts +42 -0
  46. package/src/model-graph/utils/uniqueTags.ts +19 -0
  47. package/src/module.ts +6 -9
  48. package/src/test/testServices.ts +27 -7
  49. package/src/validation/index.ts +2 -1
  50. package/src/validation/property-checks.ts +13 -1
  51. package/src/validation/specification.ts +3 -3
  52. package/src/view-utils/resolve-relative-paths.ts +14 -17
@@ -1,4 +1,4 @@
1
- import { DocumentState, EmptyFileSystem } from 'langium'
1
+ import { DocumentState, EmptyFileSystem, TextDocument } from 'langium'
2
2
  import * as assert from 'node:assert'
3
3
  import stripIndent from 'strip-indent'
4
4
  import { type Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types'
@@ -13,6 +13,7 @@ export function createTestServices(workspace = 'file:///test/workspace') {
13
13
  const documentBuilder = services.shared.workspace.DocumentBuilder
14
14
  const modelBuilder = services.likec4.ModelBuilder
15
15
  const workspaceUri = URI.parse(workspace)
16
+ const formatter = services.lsp.Formatter
16
17
  const workspaceFolder = {
17
18
  name: 'test',
18
19
  uri: workspaceUri.toString()
@@ -27,11 +28,13 @@ export function createTestServices(workspace = 'file:///test/workspace') {
27
28
  return
28
29
  }
29
30
  isInitialized = true
30
- await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder])
31
- // Workaround to set protected folders property
32
- Object.assign(services.shared.workspace.WorkspaceManager, {
33
- folders: [workspaceFolder]
31
+ services.shared.workspace.WorkspaceManager.initialize({
32
+ capabilities: {},
33
+ processId: null,
34
+ rootUri: null,
35
+ workspaceFolders: [workspaceFolder]
34
36
  })
37
+ await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder])
35
38
  })
36
39
  }
37
40
  const docUri = Utils.resolvePath(
@@ -66,12 +69,28 @@ export function createTestServices(workspace = 'file:///test/workspace') {
66
69
  }
67
70
  }
68
71
 
72
+ const format = async (input: string | LikeC4LangiumDocument, uri?: string) => {
73
+ const document = typeof input === 'string' ? await parse(input, uri) : input
74
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
75
+ await documentBuilder.build([document], { validation: false })
76
+ })
77
+
78
+ const edits = await services.lsp.Formatter?.formatDocument(
79
+ document,
80
+ {
81
+ options: {tabSize: 2, insertSpaces: true},
82
+ textDocument: { uri: document.uri.toString() }
83
+ });
84
+
85
+ return TextDocument.applyEdits(document.textDocument, edits ?? []);
86
+ }
87
+
69
88
  type ValidateAllResult = {
70
89
  diagnostics: Diagnostic[]
71
90
  errors: string[]
72
91
  warnings: string[]
73
92
  }
74
- let previousPromise = Promise.resolve() as Promise<any>
93
+
75
94
  const validateAll = async () => {
76
95
  await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
77
96
  const docs = langiumDocuments.all.toArray()
@@ -110,7 +129,8 @@ export function createTestServices(workspace = 'file:///test/workspace') {
110
129
  validate,
111
130
  validateAll,
112
131
  buildModel,
113
- resetState
132
+ resetState,
133
+ format
114
134
  }
115
135
  }
116
136
 
@@ -4,7 +4,7 @@ import type { LikeC4Services } from '../module'
4
4
  import { dynamicViewRulePredicate } from './dynamic-view-rule'
5
5
  import { dynamicViewStep } from './dynamic-view-step'
6
6
  import { elementChecks } from './element'
7
- import { iconPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
7
+ import { iconPropertyRuleChecks, notesPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
8
8
  import { relationBodyChecks, relationChecks } from './relation'
9
9
  import {
10
10
  elementKindChecks,
@@ -27,6 +27,7 @@ export function registerValidationChecks(services: LikeC4Services) {
27
27
  logger.info('registerValidationChecks')
28
28
  const registry = services.validation.ValidationRegistry
29
29
  registry.register<ast.LikeC4AstType>({
30
+ NotesProperty: notesPropertyRuleChecks(services),
30
31
  OpacityProperty: opacityPropertyRuleChecks(services),
31
32
  IconProperty: iconPropertyRuleChecks(services),
32
33
  SpecificationRule: specificationRuleChecks(services),
@@ -1,4 +1,4 @@
1
- import type { ValidationCheck } from 'langium'
1
+ import { AstUtils, type ValidationCheck } from 'langium'
2
2
  import { ast } from '../ast'
3
3
  import type { LikeC4Services } from '../module'
4
4
 
@@ -37,3 +37,15 @@ export const iconPropertyRuleChecks = (
37
37
  }
38
38
  }
39
39
  }
40
+
41
+ export const notesPropertyRuleChecks = (
42
+ _: LikeC4Services
43
+ ): ValidationCheck<ast.NotesProperty> => {
44
+ return (node, accept) => {
45
+ if (!AstUtils.hasContainerOfType(node, ast.isDynamicViewStep)) {
46
+ accept('error', `Notes can be defined only inside dynamic view`, {
47
+ node
48
+ })
49
+ }
50
+ }
51
+ }
@@ -8,7 +8,7 @@ export const specificationRuleChecks = (
8
8
  ): ValidationCheck<ast.SpecificationRule> => {
9
9
  return (node, accept) => {
10
10
  if (node.$containerIndex && node.$containerIndex > 0) {
11
- accept('error', `Only one specification per document is allowed`, {
11
+ accept('warning', `Prefer one specification per document`, {
12
12
  node: node,
13
13
  property: 'name'
14
14
  })
@@ -19,7 +19,7 @@ export const specificationRuleChecks = (
19
19
  export const modelRuleChecks = (_: LikeC4Services): ValidationCheck<ast.Model> => {
20
20
  return (node, accept) => {
21
21
  if (node.$containerIndex && node.$containerIndex > 0) {
22
- accept('error', `Only one model per document is allowed`, {
22
+ accept('warning', `Prefer one model per document`, {
23
23
  node: node,
24
24
  property: 'name'
25
25
  })
@@ -30,7 +30,7 @@ export const modelRuleChecks = (_: LikeC4Services): ValidationCheck<ast.Model> =
30
30
  export const modelViewsChecks = (_: LikeC4Services): ValidationCheck<ast.ModelViews> => {
31
31
  return (node, accept) => {
32
32
  if (node.$containerIndex && node.$containerIndex > 0) {
33
- accept('error', `Only one views block per document is allowed`, {
33
+ accept('warning', `Prefer one views block per document`, {
34
34
  node: node,
35
35
  property: 'name'
36
36
  })
@@ -1,6 +1,6 @@
1
1
  import type { LikeC4View } from '@likec4/core'
2
- import { invariant } from '@likec4/core'
3
- import { filter, hasAtLeast, isTruthy, map, pipe, unique, zip } from 'remeda'
2
+ import { compareNatural, invariant } from '@likec4/core'
3
+ import { filter, hasAtLeast, isTruthy, map, pipe, unique } from 'remeda'
4
4
  import { parsePath } from 'ufo'
5
5
 
6
6
  function commonAncestorPath(views: LikeC4View[], sep = '/') {
@@ -11,7 +11,7 @@ function commonAncestorPath(views: LikeC4View[], sep = '/') {
11
11
  unique()
12
12
  )
13
13
  if (uniqURIs.length === 0) return ''
14
- if (hasAtLeast(uniqURIs, 1) && uniqURIs.length === 1) {
14
+ if (uniqURIs.length === 1) {
15
15
  const parts = parsePath(uniqURIs[0]).pathname.split(sep)
16
16
  if (parts.length <= 1) return sep
17
17
  parts.pop() // remove filename
@@ -47,7 +47,7 @@ export function resolveRelativePaths(views: LikeC4View[]): LikeC4View[] {
47
47
  .map(view => {
48
48
  if (!view.docUri) {
49
49
  return {
50
- ...view,
50
+ view,
51
51
  parts: []
52
52
  }
53
53
  }
@@ -62,28 +62,25 @@ export function resolveRelativePaths(views: LikeC4View[]): LikeC4View[] {
62
62
  path = path.includes(sep) ? path.slice(path.lastIndexOf(sep) + 1) : path
63
63
  }
64
64
  return {
65
- ...view,
65
+ view,
66
66
  parts: path.split(sep)
67
67
  }
68
68
  })
69
69
  // Sort views by path segments
70
70
  .sort((a, b) => {
71
- if (a.parts.length === b.parts.length) {
72
- if (a.parts.length === 0) {
73
- return 0
74
- }
75
- for (const [_a, _b] of zip(a.parts, b.parts)) {
76
- const compare = _a.localeCompare(_b)
77
- if (compare !== 0) {
78
- return compare
79
- }
71
+ if (a.parts.length !== b.parts.length) {
72
+ return a.parts.length - b.parts.length
73
+ }
74
+ for (let i = 0; i < a.parts.length; i++) {
75
+ const compare = compareNatural(a.parts[i], b.parts[i])
76
+ if (compare !== 0) {
77
+ return compare
80
78
  }
81
- return 0
82
79
  }
83
- return a.parts.length - b.parts.length
80
+ return compareNatural(a.view.title ?? a.view.id, b.view.title ?? b.view.id)
84
81
  })
85
82
  // Build relativePath from path segments
86
- .map(({ parts, ...view }) => {
83
+ .map(({ parts, view }) => {
87
84
  return {
88
85
  ...view,
89
86
  relativePath: parts.join(sep)