@likec4/language-server 1.8.1 → 1.10.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/contrib/likec4.tmLanguage.json +1 -1
- package/dist/browser.cjs +21 -0
- package/dist/browser.d.cts +22 -0
- package/dist/browser.d.mts +22 -0
- package/dist/browser.d.ts +22 -0
- package/dist/browser.mjs +19 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +1 -0
- package/dist/likec4lib.cjs +961 -0
- package/dist/likec4lib.d.cts +6 -0
- package/dist/likec4lib.d.mts +6 -0
- package/dist/likec4lib.d.ts +6 -0
- package/dist/likec4lib.mjs +957 -0
- package/dist/model-graph/index.cjs +10 -0
- package/dist/model-graph/index.d.cts +79 -0
- package/dist/model-graph/index.d.mts +79 -0
- package/dist/model-graph/index.d.ts +79 -0
- package/dist/model-graph/index.mjs +1 -0
- package/dist/node.cjs +18 -0
- package/dist/node.d.cts +20 -0
- package/dist/node.d.mts +20 -0
- package/dist/node.d.ts +20 -0
- package/dist/node.mjs +16 -0
- package/dist/protocol.cjs +25 -0
- package/dist/protocol.d.cts +43 -0
- package/dist/protocol.d.mts +43 -0
- package/dist/protocol.d.ts +43 -0
- package/dist/protocol.mjs +17 -0
- package/dist/shared/language-server.CjFzaJwI.d.cts +1223 -0
- package/dist/shared/language-server.CtKHXJDD.d.ts +1223 -0
- package/dist/shared/language-server.D-84I33F.d.mts +1223 -0
- package/dist/shared/language-server.DBJJUUgF.mjs +5737 -0
- package/dist/shared/language-server.DtBRb9os.mjs +1656 -0
- package/dist/shared/language-server.DwyCJvXm.cjs +1669 -0
- package/dist/shared/language-server.JWkqVjGv.cjs +5748 -0
- package/package.json +36 -20
- package/src/ast.ts +48 -36
- package/src/browser.ts +0 -3
- package/src/elementRef.ts +1 -1
- package/src/formatting/LikeC4Formatter.ts +388 -0
- package/src/formatting/utils.ts +26 -0
- package/src/generated/ast.ts +170 -12
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +1 -1
- package/src/like-c4.langium +49 -8
- package/src/likec4lib.ts +2 -3
- package/src/logger.ts +9 -1
- package/src/lsp/DocumentLinkProvider.ts +27 -15
- package/src/lsp/RenameProvider.ts +8 -0
- package/src/lsp/SemanticTokenProvider.ts +20 -2
- package/src/lsp/index.ts +1 -0
- package/src/model/fqn-computation.ts +33 -23
- package/src/model/fqn-index.ts +5 -21
- package/src/model/model-builder.ts +180 -112
- package/src/model/model-locator.ts +1 -1
- package/src/model/model-parser-where.ts +3 -2
- package/src/model/model-parser.ts +99 -39
- package/src/model-graph/LikeC4ModelGraph.ts +42 -21
- package/src/model-graph/compute-view/__test__/fixture.ts +16 -14
- package/src/model-graph/compute-view/compute.ts +110 -81
- package/src/model-graph/compute-view/predicates.ts +6 -8
- package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
- package/src/model-graph/dynamic-view/compute.ts +98 -61
- package/src/model-graph/utils/buildElementNotations.ts +1 -1
- package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
- package/src/model-graph/utils/sortNodes.ts +2 -6
- package/src/module.ts +21 -4
- package/src/protocol.ts +4 -5
- package/src/references/scope-computation.ts +10 -1
- package/src/references/scope-provider.ts +2 -1
- package/src/shared/NodeKindProvider.ts +73 -34
- package/src/test/setup.ts +3 -8
- package/src/test/testServices.ts +27 -7
- package/src/utils/graphlib.ts +11 -0
- 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/manual-layout.ts +1 -1
- package/src/view-utils/resolve-extended-views.ts +19 -10
- package/src/view-utils/resolve-relative-paths.ts +19 -24
- package/src/view-utils/view-hash.ts +1 -1
- package/src/reset.d.ts +0 -2
package/src/module.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type PartialLangiumSharedServices
|
|
10
10
|
} from 'langium/lsp'
|
|
11
11
|
import { LikeC4GeneratedModule, LikeC4GeneratedSharedModule } from './generated/module'
|
|
12
|
-
import { logErrorToTelemetry } from './logger'
|
|
12
|
+
import { logErrorToTelemetry, logToLspConnection } from './logger'
|
|
13
13
|
import {
|
|
14
14
|
LikeC4CodeLensProvider,
|
|
15
15
|
LikeC4CompletionProvider,
|
|
@@ -25,6 +25,7 @@ import { LikeC4ScopeComputation, LikeC4ScopeProvider } from './references'
|
|
|
25
25
|
import { Rpc } from './Rpc'
|
|
26
26
|
import { LikeC4WorkspaceManager, NodeKindProvider, WorkspaceSymbolProvider } from './shared'
|
|
27
27
|
import { registerValidationChecks } from './validation'
|
|
28
|
+
import { LikeC4Formatter } from './formatting/LikeC4Formatter'
|
|
28
29
|
|
|
29
30
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
31
|
type Constructor<T, Arguments extends unknown[] = any[]> = new(...arguments_: Arguments) => T
|
|
@@ -69,6 +70,7 @@ export interface LikeC4AddedServices {
|
|
|
69
70
|
ModelChanges: LikeC4ModelChanges
|
|
70
71
|
}
|
|
71
72
|
lsp: {
|
|
73
|
+
// RenameProvider: LikeC4RenameProvider
|
|
72
74
|
CompletionProvider: LikeC4CompletionProvider
|
|
73
75
|
DocumentHighlightProvider: LikeC4DocumentHighlightProvider
|
|
74
76
|
DocumentSymbolProvider: LikeC4DocumentSymbolProvider
|
|
@@ -101,13 +103,15 @@ export const LikeC4Module: Module<LikeC4Services, PartialLangiumServices & LikeC
|
|
|
101
103
|
ModelLocator: bind(LikeC4ModelLocator)
|
|
102
104
|
},
|
|
103
105
|
lsp: {
|
|
106
|
+
// RenameProvider: bind(LikeC4RenameProvider),
|
|
104
107
|
CompletionProvider: bind(LikeC4CompletionProvider),
|
|
105
108
|
DocumentHighlightProvider: bind(LikeC4DocumentHighlightProvider),
|
|
106
109
|
DocumentSymbolProvider: bind(LikeC4DocumentSymbolProvider),
|
|
107
110
|
SemanticTokenProvider: bind(LikeC4SemanticTokenProvider),
|
|
108
111
|
HoverProvider: bind(LikeC4HoverProvider),
|
|
109
112
|
CodeLensProvider: bind(LikeC4CodeLensProvider),
|
|
110
|
-
DocumentLinkProvider: bind(LikeC4DocumentLinkProvider)
|
|
113
|
+
DocumentLinkProvider: bind(LikeC4DocumentLinkProvider),
|
|
114
|
+
Formatter: bind(LikeC4Formatter)
|
|
111
115
|
},
|
|
112
116
|
references: {
|
|
113
117
|
ScopeComputation: bind(LikeC4ScopeComputation),
|
|
@@ -136,7 +140,15 @@ export function createCustomLanguageServices<I1, I2, I3, I extends I1 & I2 & I3
|
|
|
136
140
|
const likec4 = inject(modules)
|
|
137
141
|
shared.ServiceRegistry.register(likec4)
|
|
138
142
|
registerValidationChecks(likec4)
|
|
139
|
-
|
|
143
|
+
|
|
144
|
+
if (!context.connection) {
|
|
145
|
+
// We don't run inside a language server
|
|
146
|
+
// Therefore, initialize the configuration provider instantly
|
|
147
|
+
shared.workspace.ConfigurationProvider.initialized({})
|
|
148
|
+
} else {
|
|
149
|
+
likec4.Rpc.init()
|
|
150
|
+
}
|
|
151
|
+
|
|
140
152
|
return { shared, likec4 }
|
|
141
153
|
}
|
|
142
154
|
|
|
@@ -146,6 +158,7 @@ export function createSharedServices(context: LanguageServicesContext = {}): Lik
|
|
|
146
158
|
...context
|
|
147
159
|
}
|
|
148
160
|
if (context.connection) {
|
|
161
|
+
logToLspConnection(context.connection)
|
|
149
162
|
logErrorToTelemetry(context.connection)
|
|
150
163
|
}
|
|
151
164
|
|
|
@@ -164,7 +177,11 @@ export function createLanguageServices(context: LanguageServicesContext = {}): {
|
|
|
164
177
|
const likec4 = inject(createDefaultModule({ shared }), LikeC4GeneratedModule, LikeC4Module)
|
|
165
178
|
shared.ServiceRegistry.register(likec4)
|
|
166
179
|
registerValidationChecks(likec4)
|
|
167
|
-
|
|
180
|
+
|
|
181
|
+
if (context.connection) {
|
|
182
|
+
likec4.Rpc.init()
|
|
183
|
+
}
|
|
184
|
+
|
|
168
185
|
return { shared, likec4 }
|
|
169
186
|
}
|
|
170
187
|
|
package/src/protocol.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
ComputedLikeC4Model,
|
|
3
3
|
ComputedView,
|
|
4
4
|
Fqn,
|
|
5
|
-
|
|
6
|
-
LikeC4Model,
|
|
5
|
+
ParsedLikeC4Model,
|
|
7
6
|
RelationID,
|
|
8
7
|
ViewChange,
|
|
9
8
|
ViewID
|
|
@@ -17,12 +16,12 @@ export type OnDidChangeModelNotification = typeof onDidChangeModel
|
|
|
17
16
|
// #endregion
|
|
18
17
|
|
|
19
18
|
// #region To server
|
|
20
|
-
export const fetchModel = new RequestType0<{ model:
|
|
19
|
+
export const fetchModel = new RequestType0<{ model: ParsedLikeC4Model | null }, void>(
|
|
21
20
|
'likec4/fetchModel'
|
|
22
21
|
)
|
|
23
22
|
export type FetchModelRequest = typeof fetchModel
|
|
24
23
|
|
|
25
|
-
export const fetchComputedModel = new RequestType0<{ model:
|
|
24
|
+
export const fetchComputedModel = new RequestType0<{ model: ComputedLikeC4Model | null }, void>(
|
|
26
25
|
'likec4/fetchComputedModel'
|
|
27
26
|
)
|
|
28
27
|
export type FetchComputedModelRequest = typeof fetchComputedModel
|
|
@@ -106,7 +106,8 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
106
106
|
const spec of specifications.flatMap(s => [
|
|
107
107
|
...s.elements,
|
|
108
108
|
...s.relationships,
|
|
109
|
-
...s.tags
|
|
109
|
+
...s.tags,
|
|
110
|
+
...s.colors
|
|
110
111
|
])
|
|
111
112
|
) {
|
|
112
113
|
try {
|
|
@@ -136,6 +137,14 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
136
137
|
}
|
|
137
138
|
continue
|
|
138
139
|
}
|
|
140
|
+
case ast.isSpecificationColor(spec): {
|
|
141
|
+
if (isTruthy(spec.name.name)) {
|
|
142
|
+
docExports.push(
|
|
143
|
+
this.descriptions.createDescription(spec.name, spec.name.name, document)
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
139
148
|
// Thow error if not exhaustive
|
|
140
149
|
default:
|
|
141
150
|
nonexhaustive(spec)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AstNode, type AstNodeDescription } from 'langium'
|
|
1
|
+
import { type AstNode, type AstNodeDescription, isAstNode } from 'langium'
|
|
2
2
|
import type { LangiumSharedServices, NodeKindProvider as LspNodeKindProvider } from 'langium/lsp'
|
|
3
3
|
import { CompletionItemKind, SymbolKind } from 'vscode-languageserver-types'
|
|
4
4
|
import { ast } from '../ast'
|
|
@@ -11,32 +11,44 @@ export class NodeKindProvider implements LspNodeKindProvider {
|
|
|
11
11
|
*/
|
|
12
12
|
// prettier-ignore
|
|
13
13
|
getSymbolKind(node: AstNode | AstNodeDescription): SymbolKind {
|
|
14
|
-
const
|
|
14
|
+
const nodeType = isAstNode(node) ? node.$type : node.type
|
|
15
|
+
const hasType = (...types: string[]) => types.some(t => this.services.AstReflection.isSubtype(nodeType, t))
|
|
15
16
|
switch (true) {
|
|
16
|
-
case
|
|
17
|
-
|
|
17
|
+
case hasType(
|
|
18
|
+
ast.Element,
|
|
19
|
+
ast.ExtendElement
|
|
20
|
+
):
|
|
18
21
|
return SymbolKind.Constructor
|
|
19
|
-
|
|
20
|
-
case
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
case hasType(
|
|
24
|
+
ast.Model,
|
|
25
|
+
ast.ModelViews,
|
|
26
|
+
ast.SpecificationRule
|
|
27
|
+
):
|
|
22
28
|
return SymbolKind.Namespace
|
|
23
|
-
|
|
24
|
-
case
|
|
29
|
+
|
|
30
|
+
case hasType(ast.LikeC4View):
|
|
25
31
|
return SymbolKind.Class
|
|
26
|
-
|
|
27
|
-
case
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
|
|
33
|
+
case hasType(
|
|
34
|
+
ast.Tag,
|
|
35
|
+
ast.LibIcon,
|
|
36
|
+
ast.CustomColor,
|
|
37
|
+
ast.SpecificationTag
|
|
38
|
+
):
|
|
30
39
|
return SymbolKind.EnumMember
|
|
31
|
-
|
|
32
|
-
case
|
|
33
|
-
|
|
40
|
+
|
|
41
|
+
case hasType(
|
|
42
|
+
ast.RelationshipKind,
|
|
43
|
+
ast.SpecificationRelationshipKind
|
|
44
|
+
):
|
|
34
45
|
return SymbolKind.Event
|
|
35
|
-
|
|
36
|
-
case
|
|
37
|
-
|
|
46
|
+
|
|
47
|
+
case hasType(
|
|
48
|
+
ast.ElementKind,
|
|
49
|
+
ast.SpecificationElementKind
|
|
50
|
+
):
|
|
38
51
|
return SymbolKind.TypeParameter
|
|
39
|
-
}
|
|
40
52
|
}
|
|
41
53
|
return SymbolKind.Field
|
|
42
54
|
}
|
|
@@ -44,25 +56,52 @@ export class NodeKindProvider implements LspNodeKindProvider {
|
|
|
44
56
|
* Returns a `CompletionItemKind` as used by the `CompletionProvider`.
|
|
45
57
|
*/
|
|
46
58
|
getCompletionItemKind(node: AstNode | AstNodeDescription): CompletionItemKind {
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
const nodeType = isAstNode(node) ? node.$type : node.type
|
|
60
|
+
const hasType = (...types: string[]) => types.some(t => this.services.AstReflection.isSubtype(nodeType, t))
|
|
61
|
+
switch (true) {
|
|
62
|
+
case hasType(
|
|
63
|
+
ast.CustomColor
|
|
64
|
+
):
|
|
65
|
+
return CompletionItemKind.Color
|
|
66
|
+
|
|
67
|
+
case hasType(
|
|
68
|
+
ast.Element,
|
|
69
|
+
ast.ExtendElement
|
|
70
|
+
):
|
|
49
71
|
return CompletionItemKind.Constructor
|
|
50
|
-
|
|
72
|
+
|
|
73
|
+
case hasType(
|
|
74
|
+
ast.Model,
|
|
75
|
+
ast.ModelViews,
|
|
76
|
+
ast.SpecificationRule
|
|
77
|
+
):
|
|
51
78
|
return CompletionItemKind.Module
|
|
52
|
-
|
|
79
|
+
|
|
80
|
+
case hasType(
|
|
81
|
+
ast.LikeC4View
|
|
82
|
+
):
|
|
53
83
|
return CompletionItemKind.Class
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
|
|
85
|
+
case hasType(
|
|
86
|
+
ast.Tag,
|
|
87
|
+
ast.LibIcon,
|
|
88
|
+
ast.CustomColor,
|
|
89
|
+
ast.SpecificationTag
|
|
90
|
+
):
|
|
57
91
|
return CompletionItemKind.EnumMember
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
|
|
93
|
+
case hasType(
|
|
94
|
+
ast.RelationshipKind,
|
|
95
|
+
ast.SpecificationRelationshipKind
|
|
96
|
+
):
|
|
63
97
|
return CompletionItemKind.Event
|
|
64
|
-
|
|
65
|
-
|
|
98
|
+
|
|
99
|
+
case hasType(
|
|
100
|
+
ast.ElementKind,
|
|
101
|
+
ast.SpecificationElementKind
|
|
102
|
+
):
|
|
103
|
+
return CompletionItemKind.TypeParameter
|
|
104
|
+
|
|
66
105
|
default:
|
|
67
106
|
return CompletionItemKind.Reference
|
|
68
107
|
}
|
package/src/test/setup.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { consola } from '@likec4/log'
|
|
2
|
+
import { beforeEach, vi } from 'vitest'
|
|
2
3
|
import { logger } from '../logger'
|
|
3
|
-
|
|
4
|
-
beforeAll(() => {
|
|
5
|
-
// Redirect std and console to consola too
|
|
6
|
-
// Calling this once is sufficient
|
|
7
|
-
logger.wrapAll()
|
|
8
|
-
})
|
|
9
|
-
|
|
10
4
|
beforeEach(() => {
|
|
11
5
|
// Vitest
|
|
6
|
+
consola.mockTypes(() => vi.fn())
|
|
12
7
|
logger.mockTypes(() => vi.fn())
|
|
13
8
|
})
|
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
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// '@dagrejs/graphlib' is a CommonJS module
|
|
2
|
+
// Here is a workaround to import it
|
|
3
|
+
|
|
4
|
+
import { Graph } from '@dagrejs/graphlib'
|
|
5
|
+
import graphlib from '@dagrejs/graphlib'
|
|
6
|
+
|
|
7
|
+
export { Graph }
|
|
8
|
+
|
|
9
|
+
export const postorder = graphlib.alg.postorder
|
|
10
|
+
export const findCycles = graphlib.alg.findCycles
|
|
11
|
+
export const isAcyclic = graphlib.alg.isAcyclic
|
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,9 +1,7 @@
|
|
|
1
|
-
import graphlib from '@dagrejs/graphlib'
|
|
2
1
|
import { isExtendsElementView, type LikeC4View } from '@likec4/core'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const { Graph, alg } = graphlib
|
|
2
|
+
import { logger } from '@likec4/log'
|
|
3
|
+
import { first, last, values } from 'remeda'
|
|
4
|
+
import { findCycles, Graph, isAcyclic, postorder } from '../utils/graphlib'
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Resolve rules of extended views
|
|
@@ -18,21 +16,31 @@ export function resolveRulesExtendedViews<V extends Record<any, LikeC4View>>(
|
|
|
18
16
|
multigraph: false,
|
|
19
17
|
compound: false
|
|
20
18
|
})
|
|
21
|
-
for (const view of
|
|
19
|
+
for (const view of values(unresolvedViews)) {
|
|
22
20
|
g.setNode(view.id)
|
|
23
21
|
if (isExtendsElementView(view)) {
|
|
24
22
|
// view -> parent
|
|
25
23
|
g.setEdge(view.id, view.extends)
|
|
26
24
|
}
|
|
27
25
|
}
|
|
26
|
+
if (g.edgeCount() === 0) {
|
|
27
|
+
return unresolvedViews
|
|
28
|
+
}
|
|
28
29
|
|
|
29
30
|
// Remove circular dependencies
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
while (!isAcyclic(g)) {
|
|
32
|
+
const firstCycle = first(findCycles(g))
|
|
33
|
+
if (!firstCycle) {
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
const cycledNode = last(firstCycle)
|
|
37
|
+
if (!cycledNode) {
|
|
38
|
+
break
|
|
39
|
+
}
|
|
40
|
+
g.removeNode(cycledNode)
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
const ordered =
|
|
43
|
+
const ordered = postorder(g, g.sources())
|
|
36
44
|
|
|
37
45
|
return ordered.reduce((acc, id) => {
|
|
38
46
|
const view = unresolvedViews[id]
|
|
@@ -42,6 +50,7 @@ export function resolveRulesExtendedViews<V extends Record<any, LikeC4View>>(
|
|
|
42
50
|
if (isExtendsElementView(view)) {
|
|
43
51
|
const extendsFrom = acc[view.extends]
|
|
44
52
|
if (!extendsFrom) {
|
|
53
|
+
logger.debug(`View "${view.id}" extends from "${view.extends}" which does not exist`)
|
|
45
54
|
return acc
|
|
46
55
|
}
|
|
47
56
|
return Object.assign(acc, {
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
+
import { parsePath } from 'ufo'
|
|
4
5
|
|
|
5
6
|
function commonAncestorPath(views: LikeC4View[], sep = '/') {
|
|
6
7
|
const uniqURIs = pipe(
|
|
@@ -10,21 +11,21 @@ function commonAncestorPath(views: LikeC4View[], sep = '/') {
|
|
|
10
11
|
unique()
|
|
11
12
|
)
|
|
12
13
|
if (uniqURIs.length === 0) return ''
|
|
13
|
-
if (
|
|
14
|
-
const parts =
|
|
14
|
+
if (uniqURIs.length === 1) {
|
|
15
|
+
const parts = parsePath(uniqURIs[0]).pathname.split(sep)
|
|
15
16
|
if (parts.length <= 1) return sep
|
|
16
17
|
parts.pop() // remove filename
|
|
17
18
|
return parts.join(sep) + sep
|
|
18
19
|
}
|
|
19
20
|
invariant(hasAtLeast(uniqURIs, 2), 'Expected at least 2 unique URIs')
|
|
20
21
|
const [baseUri, ...tail] = uniqURIs
|
|
21
|
-
const parts =
|
|
22
|
+
const parts = parsePath(baseUri).pathname.split(sep)
|
|
22
23
|
let endOfPrefix = parts.length
|
|
23
24
|
for (const uri of tail) {
|
|
24
25
|
if (uri === baseUri) {
|
|
25
26
|
continue
|
|
26
27
|
}
|
|
27
|
-
const compare =
|
|
28
|
+
const compare = parsePath(uri).pathname.split(sep)
|
|
28
29
|
for (let i = 0; i < endOfPrefix; i++) {
|
|
29
30
|
if (compare[i] !== parts[i]) {
|
|
30
31
|
endOfPrefix = i
|
|
@@ -46,11 +47,11 @@ export function resolveRelativePaths(views: LikeC4View[]): LikeC4View[] {
|
|
|
46
47
|
.map(view => {
|
|
47
48
|
if (!view.docUri) {
|
|
48
49
|
return {
|
|
49
|
-
|
|
50
|
+
view,
|
|
50
51
|
parts: []
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
|
-
let path =
|
|
54
|
+
let path = parsePath(view.docUri).pathname
|
|
54
55
|
if (commonPrefix.length > 0) {
|
|
55
56
|
invariant(
|
|
56
57
|
path.startsWith(commonPrefix),
|
|
@@ -61,31 +62,25 @@ export function resolveRelativePaths(views: LikeC4View[]): LikeC4View[] {
|
|
|
61
62
|
path = path.includes(sep) ? path.slice(path.lastIndexOf(sep) + 1) : path
|
|
62
63
|
}
|
|
63
64
|
return {
|
|
64
|
-
|
|
65
|
+
view,
|
|
65
66
|
parts: path.split(sep)
|
|
66
67
|
}
|
|
67
68
|
})
|
|
68
69
|
// Sort views by path segments
|
|
69
70
|
.sort((a, b) => {
|
|
70
|
-
if (a.parts.length
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for (const [_a, _b] of zip(a.parts, b.parts)) {
|
|
78
|
-
const compare = _a.localeCompare(_b)
|
|
79
|
-
if (compare !== 0) {
|
|
80
|
-
return compare
|
|
81
|
-
}
|
|
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
|
|
82
78
|
}
|
|
83
|
-
return 0
|
|
84
79
|
}
|
|
85
|
-
return a.
|
|
80
|
+
return compareNatural(a.view.title ?? a.view.id, b.view.title ?? b.view.id)
|
|
86
81
|
})
|
|
87
82
|
// Build relativePath from path segments
|
|
88
|
-
.map(({ parts,
|
|
83
|
+
.map(({ parts, view }) => {
|
|
89
84
|
return {
|
|
90
85
|
...view,
|
|
91
86
|
relativePath: parts.join(sep)
|
package/src/reset.d.ts
DELETED