@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.
- package/contrib/likec4.tmLanguage.json +1 -1
- package/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +3 -4
- package/dist/browser.d.mts +3 -4
- package/dist/browser.d.ts +3 -4
- package/dist/browser.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/model-graph/index.cjs +1 -1
- package/dist/model-graph/index.mjs +1 -1
- package/dist/node.cjs +1 -1
- package/dist/node.d.cts +3 -4
- package/dist/node.d.mts +3 -4
- package/dist/node.d.ts +3 -4
- package/dist/node.mjs +1 -1
- package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.BFBeyvV8.mjs} +486 -108
- package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.BGy3FJPJ.d.cts} +43 -14
- package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.Bfc-5M8A.cjs} +482 -104
- package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.CbqwHp7Q.mjs} +184 -120
- package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CnVuAxDh.d.ts} +43 -14
- package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.DEK39RmI.d.mts} +43 -14
- package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DJhoJBWh.cjs} +180 -116
- package/package.json +13 -11
- package/src/ast.ts +8 -6
- package/src/formatting/LikeC4Formatter.ts +390 -0
- package/src/formatting/utils.ts +26 -0
- package/src/generated/ast.ts +203 -11
- package/src/generated/grammar.ts +2 -2
- package/src/generated/module.ts +1 -1
- package/src/like-c4.langium +34 -7
- package/src/lsp/CompletionProvider.ts +1 -1
- package/src/lsp/DocumentLinkProvider.ts +27 -15
- package/src/lsp/SemanticTokenProvider.ts +1 -1
- package/src/lsp/index.ts +1 -1
- package/src/model/fqn-index.ts +0 -1
- package/src/model/model-builder.ts +43 -32
- package/src/model/model-parser.ts +43 -21
- package/src/model-graph/compute-view/compute.ts +111 -80
- package/src/model-graph/compute-view/predicates.ts +3 -5
- package/src/model-graph/dynamic-view/compute.ts +96 -60
- package/src/model-graph/utils/buildElementNotations.ts +1 -1
- package/src/model-graph/utils/uniqueTags.test.ts +42 -0
- package/src/model-graph/utils/uniqueTags.ts +19 -0
- package/src/module.ts +6 -9
- package/src/test/testServices.ts +27 -7
- package/src/validation/index.ts +2 -1
- package/src/validation/property-checks.ts +13 -1
- package/src/validation/specification.ts +3 -3
- package/src/view-utils/resolve-relative-paths.ts +14 -17
package/src/test/testServices.ts
CHANGED
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
package/src/validation/index.ts
CHANGED
|
@@ -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
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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.
|
|
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,
|
|
83
|
+
.map(({ parts, view }) => {
|
|
87
84
|
return {
|
|
88
85
|
...view,
|
|
89
86
|
relativePath: parts.join(sep)
|