@likec4/language-server 1.21.0 → 1.22.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/README.md +4 -1
- package/bin/likec4-language-server.mjs +5 -2
- package/dist/LikeC4FileSystem.js +2 -2
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +17 -2
- package/dist/bundled.d.ts +8 -0
- package/dist/bundled.js +25 -0
- package/dist/bundled.mjs +2587 -4306
- package/dist/generated-lib/icons.js +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +23 -2
- package/dist/logger.d.ts +9 -3
- package/dist/logger.js +35 -55
- package/dist/model/fqn-computation.js +2 -2
- package/dist/model/model-builder.js +13 -14
- package/dist/model-change/ModelChanges.js +2 -2
- package/dist/module.js +1 -4
- package/dist/references/scope-provider.js +3 -3
- package/dist/view-utils/manual-layout.js +2 -2
- package/dist/views/configurable-layouter.js +4 -4
- package/dist/views/likec4-views.d.ts +2 -1
- package/dist/views/likec4-views.js +2 -2
- package/package.json +14 -14
- package/dist/test/setup.d.ts +0 -1
- package/dist/test/setup.js +0 -7
- package/src/LikeC4FileSystem.ts +0 -38
- package/src/Rpc.ts +0 -134
- package/src/ast.ts +0 -556
- package/src/browser.ts +0 -35
- package/src/documentation/documentation-provider.ts +0 -52
- package/src/documentation/index.ts +0 -1
- package/src/formatting/LikeC4Formatter.ts +0 -639
- package/src/formatting/utils.ts +0 -26
- package/src/generated/ast.ts +0 -3735
- package/src/generated/grammar.ts +0 -10
- package/src/generated/module.ts +0 -33
- package/src/generated-lib/icons.ts +0 -1537
- package/src/index.ts +0 -30
- package/src/like-c4.langium +0 -901
- package/src/likec4lib.ts +0 -6
- package/src/logger.ts +0 -80
- package/src/lsp/CodeLensProvider.ts +0 -50
- package/src/lsp/CompletionProvider.ts +0 -147
- package/src/lsp/DocumentHighlightProvider.ts +0 -12
- package/src/lsp/DocumentLinkProvider.ts +0 -65
- package/src/lsp/DocumentSymbolProvider.ts +0 -313
- package/src/lsp/HoverProvider.ts +0 -92
- package/src/lsp/RenameProvider.ts +0 -8
- package/src/lsp/SemanticTokenProvider.ts +0 -383
- package/src/lsp/index.ts +0 -8
- package/src/model/deployments-index.ts +0 -209
- package/src/model/fqn-computation.ts +0 -83
- package/src/model/fqn-index.ts +0 -138
- package/src/model/index.ts +0 -6
- package/src/model/model-builder.ts +0 -724
- package/src/model/model-locator.ts +0 -146
- package/src/model/model-parser-where.ts +0 -84
- package/src/model/model-parser.ts +0 -86
- package/src/model/parser/Base.ts +0 -113
- package/src/model/parser/DeploymentModelParser.ts +0 -192
- package/src/model/parser/DeploymentViewParser.ts +0 -122
- package/src/model/parser/FqnRefParser.ts +0 -143
- package/src/model/parser/GlobalsParser.ts +0 -96
- package/src/model/parser/ModelParser.ts +0 -170
- package/src/model/parser/PredicatesParser.ts +0 -315
- package/src/model/parser/SpecificationParser.ts +0 -133
- package/src/model/parser/ViewsParser.ts +0 -428
- package/src/model-change/ModelChanges.ts +0 -101
- package/src/model-change/changeElementStyle.ts +0 -172
- package/src/model-change/changeViewLayout.ts +0 -47
- package/src/model-change/saveManualLayout.ts +0 -41
- package/src/module.ts +0 -255
- package/src/protocol.ts +0 -93
- package/src/references/index.ts +0 -3
- package/src/references/name-provider.ts +0 -37
- package/src/references/scope-computation.ts +0 -364
- package/src/references/scope-provider.ts +0 -201
- package/src/shared/NodeKindProvider.ts +0 -121
- package/src/shared/WorkspaceManager.ts +0 -48
- package/src/shared/WorkspaceSymbolProvider.ts +0 -3
- package/src/shared/index.ts +0 -3
- package/src/test/index.ts +0 -1
- package/src/test/setup.ts +0 -8
- package/src/test/testServices.ts +0 -152
- package/src/utils/disposable.ts +0 -30
- package/src/utils/elementRef.ts +0 -26
- package/src/utils/fqnRef.ts +0 -56
- package/src/utils/index.ts +0 -2
- package/src/utils/printDocs.ts +0 -3
- package/src/utils/stringHash.ts +0 -6
- package/src/validation/_shared.ts +0 -29
- package/src/validation/deployment-checks.ts +0 -131
- package/src/validation/dynamic-view-rule.ts +0 -23
- package/src/validation/dynamic-view-step.ts +0 -36
- package/src/validation/element.ts +0 -56
- package/src/validation/index.ts +0 -171
- package/src/validation/property-checks.ts +0 -52
- package/src/validation/relation.ts +0 -63
- package/src/validation/specification.ts +0 -205
- package/src/validation/view-predicates/element-with.ts +0 -36
- package/src/validation/view-predicates/expanded-element.ts +0 -16
- package/src/validation/view-predicates/expression-v2.ts +0 -101
- package/src/validation/view-predicates/incoming.ts +0 -20
- package/src/validation/view-predicates/index.ts +0 -6
- package/src/validation/view-predicates/outgoing.ts +0 -20
- package/src/validation/view-predicates/relation-with.ts +0 -17
- package/src/validation/view.ts +0 -37
- package/src/view-utils/assignNavigateTo.ts +0 -31
- package/src/view-utils/index.ts +0 -2
- package/src/view-utils/manual-layout.ts +0 -116
- package/src/view-utils/resolve-relative-paths.ts +0 -90
- package/src/views/configurable-layouter.ts +0 -65
- package/src/views/index.ts +0 -1
- package/src/views/likec4-views.ts +0 -139
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import type * as c4 from '@likec4/core'
|
|
2
|
-
import type { LangiumDocuments } from 'langium'
|
|
3
|
-
import { AstUtils, GrammarUtils } from 'langium'
|
|
4
|
-
import { isString } from 'remeda'
|
|
5
|
-
import type { Location } from 'vscode-languageserver-types'
|
|
6
|
-
import type { ParsedAstElement } from '../ast'
|
|
7
|
-
import { ast, isParsedLikeC4LangiumDocument } from '../ast'
|
|
8
|
-
import type { LikeC4Services } from '../module'
|
|
9
|
-
import type { DeploymentsIndex } from './deployments-index'
|
|
10
|
-
import { type FqnIndex } from './fqn-index'
|
|
11
|
-
|
|
12
|
-
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils
|
|
13
|
-
const { getDocument } = AstUtils
|
|
14
|
-
|
|
15
|
-
export class LikeC4ModelLocator {
|
|
16
|
-
private fqnIndex: FqnIndex
|
|
17
|
-
private deploymentsIndex: DeploymentsIndex
|
|
18
|
-
private langiumDocuments: LangiumDocuments
|
|
19
|
-
|
|
20
|
-
constructor(private services: LikeC4Services) {
|
|
21
|
-
this.fqnIndex = services.likec4.FqnIndex
|
|
22
|
-
this.deploymentsIndex = services.likec4.DeploymentsIndex
|
|
23
|
-
this.langiumDocuments = services.shared.workspace.LangiumDocuments
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
private documents() {
|
|
27
|
-
return this.langiumDocuments.all.filter(isParsedLikeC4LangiumDocument)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public getParsedElement(astNodeOrFqn: ast.Element | c4.Fqn): ParsedAstElement | null {
|
|
31
|
-
if (isString(astNodeOrFqn)) {
|
|
32
|
-
const fqn = astNodeOrFqn
|
|
33
|
-
const entry = this.fqnIndex.byFqn(astNodeOrFqn).head()
|
|
34
|
-
if (!entry) {
|
|
35
|
-
return null
|
|
36
|
-
}
|
|
37
|
-
const doc = this.langiumDocuments.getDocument(entry.documentUri)
|
|
38
|
-
if (!doc || !isParsedLikeC4LangiumDocument(doc)) {
|
|
39
|
-
return null
|
|
40
|
-
}
|
|
41
|
-
return doc.c4Elements.find(e => e.id === fqn) ?? null
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const fqn = this.fqnIndex.getFqn(astNodeOrFqn)
|
|
45
|
-
if (!fqn) return null
|
|
46
|
-
const doc = getDocument(astNodeOrFqn)
|
|
47
|
-
if (!isParsedLikeC4LangiumDocument(doc)) {
|
|
48
|
-
return null
|
|
49
|
-
}
|
|
50
|
-
return doc.c4Elements.find(e => e.id === fqn) ?? null
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public locateElement(fqn: c4.Fqn, _prop?: string): Location | null {
|
|
54
|
-
const entry = this.fqnIndex.byFqn(fqn).head()
|
|
55
|
-
const docsegment = entry?.nameSegment ?? entry?.selectionSegment
|
|
56
|
-
if (!entry || !docsegment) {
|
|
57
|
-
return null
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
uri: entry.documentUri.toString(),
|
|
61
|
-
range: docsegment.range
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
public locateDeploymentElement(fqn: c4.Fqn, _prop?: string): Location | null {
|
|
65
|
-
const entry = this.deploymentsIndex.byFqn(fqn).head()
|
|
66
|
-
const docsegment = entry?.nameSegment ?? entry?.selectionSegment
|
|
67
|
-
if (!entry || !docsegment) {
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
uri: entry.documentUri.toString(),
|
|
72
|
-
range: docsegment.range
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public locateRelation(relationId: c4.RelationId): Location | null {
|
|
77
|
-
for (const doc of this.documents()) {
|
|
78
|
-
const relation = doc.c4Relations.find(r => r.id === relationId)
|
|
79
|
-
?? doc.c4DeploymentRelations.find(r => r.id === relationId)
|
|
80
|
-
if (!relation) {
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
|
-
const node = this.services.workspace.AstNodeLocator.getAstNode(
|
|
84
|
-
doc.parseResult.value,
|
|
85
|
-
relation.astPath
|
|
86
|
-
)
|
|
87
|
-
if (!ast.isRelation(node) && !ast.isDeploymentRelation(node)) {
|
|
88
|
-
continue
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
let targetNode = node.title ? findNodeForProperty(node.$cstNode, 'title') : undefined
|
|
92
|
-
targetNode ??= node.kind ? findNodeForProperty(node.$cstNode, 'kind') : undefined
|
|
93
|
-
targetNode ??= findNodeForProperty(node.$cstNode, 'target')
|
|
94
|
-
targetNode ??= node.$cstNode
|
|
95
|
-
|
|
96
|
-
if (!targetNode) {
|
|
97
|
-
continue
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
uri: doc.uri.toString(),
|
|
102
|
-
range: targetNode.range
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return null
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public locateViewAst(viewId: c4.ViewId) {
|
|
109
|
-
for (const doc of this.documents()) {
|
|
110
|
-
const view = doc.c4Views.find(r => r.id === viewId)
|
|
111
|
-
if (!view) {
|
|
112
|
-
continue
|
|
113
|
-
}
|
|
114
|
-
const viewAst = this.services.workspace.AstNodeLocator.getAstNode(
|
|
115
|
-
doc.parseResult.value,
|
|
116
|
-
view.astPath
|
|
117
|
-
)
|
|
118
|
-
if (ast.isLikeC4View(viewAst)) {
|
|
119
|
-
return {
|
|
120
|
-
doc,
|
|
121
|
-
view,
|
|
122
|
-
viewAst
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return null
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
public locateView(viewId: c4.ViewId): Location | null {
|
|
130
|
-
const res = this.locateViewAst(viewId)
|
|
131
|
-
if (!res) {
|
|
132
|
-
return null
|
|
133
|
-
}
|
|
134
|
-
const node = res.viewAst
|
|
135
|
-
let targetNode = node.name ? findNodeForProperty(node.$cstNode, 'name') : undefined
|
|
136
|
-
targetNode ??= findNodeForKeyword(node.$cstNode, 'view')
|
|
137
|
-
targetNode ??= node.$cstNode
|
|
138
|
-
if (!targetNode) {
|
|
139
|
-
return null
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
uri: res.doc.uri.toString(),
|
|
143
|
-
range: targetNode.range
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
2
|
-
import { isAndOperator, isOrOperator } from '@likec4/core'
|
|
3
|
-
import type * as c4 from '@likec4/core'
|
|
4
|
-
import { ast } from '../ast'
|
|
5
|
-
|
|
6
|
-
const parseEquals = (
|
|
7
|
-
{ operator, not }: ast.WhereKindEqual | ast.WhereTagEqual,
|
|
8
|
-
value: string,
|
|
9
|
-
): c4.EqualOperator<string> => {
|
|
10
|
-
if (operator.startsWith('!=')) {
|
|
11
|
-
return {
|
|
12
|
-
neq: value,
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
if (operator.startsWith('=')) {
|
|
16
|
-
return {
|
|
17
|
-
eq: value,
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return not ? { neq: value } : { eq: value }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function parseParticipant(astNode: ast.WhereExpression): ast.Participant | null {
|
|
24
|
-
if (!ast.isWhereRelationParticipantKind(astNode) && !ast.isWhereRelationParticipantTag(astNode)) {
|
|
25
|
-
return null
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return astNode.participant
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function parseWhereClause(astNode: ast.WhereExpression): c4.WhereOperator<string, string> {
|
|
32
|
-
switch (true) {
|
|
33
|
-
case ast.isWhereTagEqual(astNode): {
|
|
34
|
-
const tag = astNode.value?.ref?.name
|
|
35
|
-
const participant = parseParticipant(astNode)
|
|
36
|
-
invariant(tag, 'Expected tag name')
|
|
37
|
-
const tagOperator = { tag: parseEquals(astNode, tag) }
|
|
38
|
-
return participant ? { participant, operator: tagOperator } : tagOperator
|
|
39
|
-
}
|
|
40
|
-
case ast.isWhereKindEqual(astNode): {
|
|
41
|
-
const kind = astNode.value?.ref?.name
|
|
42
|
-
const participant = parseParticipant(astNode)
|
|
43
|
-
invariant(kind, 'Expected kind name')
|
|
44
|
-
const kindOperator = { kind: parseEquals(astNode, kind) }
|
|
45
|
-
return participant ? { participant, operator: kindOperator } : kindOperator
|
|
46
|
-
}
|
|
47
|
-
case ast.isWhereElementNegation(astNode) || ast.isWhereRelationNegation(astNode): {
|
|
48
|
-
return {
|
|
49
|
-
not: parseWhereClause(astNode.value),
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
case ast.isWhereBinaryExpression(astNode): {
|
|
53
|
-
const left = parseWhereClause(astNode.left)
|
|
54
|
-
const right = parseWhereClause(astNode.right)
|
|
55
|
-
const operator = astNode.operator.toLowerCase() as Lowercase<ast.WhereBinaryExpression['operator']>
|
|
56
|
-
switch (operator) {
|
|
57
|
-
case 'and': {
|
|
58
|
-
const operands = [
|
|
59
|
-
isAndOperator(left) ? left.and : left,
|
|
60
|
-
isAndOperator(right) ? right.and : right,
|
|
61
|
-
].flat()
|
|
62
|
-
invariant(isNonEmptyArray(operands), 'Expected non-empty array')
|
|
63
|
-
return {
|
|
64
|
-
and: operands,
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
case 'or': {
|
|
68
|
-
const operands = [
|
|
69
|
-
isOrOperator(left) ? left.or : left,
|
|
70
|
-
isOrOperator(right) ? right.or : right,
|
|
71
|
-
].flat()
|
|
72
|
-
invariant(isNonEmptyArray(operands), 'Expected non-empty array')
|
|
73
|
-
return {
|
|
74
|
-
or: operands,
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
default:
|
|
78
|
-
nonexhaustive(operator)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
default:
|
|
82
|
-
nonexhaustive(astNode)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { invariant } from '@likec4/core'
|
|
2
|
-
import { type LangiumDocument, DocumentCache, DocumentState } from 'langium'
|
|
3
|
-
import { DefaultWeakMap } from 'mnemonist'
|
|
4
|
-
import { pipe } from 'remeda'
|
|
5
|
-
import type { LikeC4DocumentProps, ParsedLikeC4LangiumDocument } from '../ast'
|
|
6
|
-
import { isFqnIndexedDocument } from '../ast'
|
|
7
|
-
import type { LikeC4Services } from '../module'
|
|
8
|
-
import { BaseParser } from './parser/Base'
|
|
9
|
-
import { DeploymentModelParser } from './parser/DeploymentModelParser'
|
|
10
|
-
import { DeploymentViewParser } from './parser/DeploymentViewParser'
|
|
11
|
-
import { ExpressionV2Parser } from './parser/FqnRefParser'
|
|
12
|
-
import { GlobalsParser } from './parser/GlobalsParser'
|
|
13
|
-
import { ModelParser } from './parser/ModelParser'
|
|
14
|
-
import { PredicatesParser } from './parser/PredicatesParser'
|
|
15
|
-
import { SpecificationParser } from './parser/SpecificationParser'
|
|
16
|
-
import { ViewsParser } from './parser/ViewsParser'
|
|
17
|
-
|
|
18
|
-
export type ModelParsedListener = () => void
|
|
19
|
-
|
|
20
|
-
const DocumentParserFromMixins = pipe(
|
|
21
|
-
BaseParser,
|
|
22
|
-
ExpressionV2Parser,
|
|
23
|
-
ModelParser,
|
|
24
|
-
DeploymentModelParser,
|
|
25
|
-
DeploymentViewParser,
|
|
26
|
-
PredicatesParser,
|
|
27
|
-
SpecificationParser,
|
|
28
|
-
ViewsParser,
|
|
29
|
-
GlobalsParser,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
export class DocumentParser extends DocumentParserFromMixins {
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class LikeC4ModelParser {
|
|
36
|
-
private cachedParsers: DocumentCache<string, DocumentParser>
|
|
37
|
-
|
|
38
|
-
constructor(private services: LikeC4Services) {
|
|
39
|
-
this.cachedParsers = new DocumentCache(services.shared, DocumentState.Validated)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
parse(doc: LangiumDocument): ParsedLikeC4LangiumDocument {
|
|
43
|
-
invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
|
|
44
|
-
try {
|
|
45
|
-
const props: Required<Omit<LikeC4DocumentProps, 'c4fqnIndex' | 'diagnostics'>> = {
|
|
46
|
-
c4Specification: {
|
|
47
|
-
tags: new Set(),
|
|
48
|
-
elements: {},
|
|
49
|
-
relationships: {},
|
|
50
|
-
colors: {},
|
|
51
|
-
deployments: {},
|
|
52
|
-
},
|
|
53
|
-
c4Elements: [],
|
|
54
|
-
c4ExtendElements: [],
|
|
55
|
-
c4Relations: [],
|
|
56
|
-
c4Deployments: [],
|
|
57
|
-
c4DeploymentRelations: [],
|
|
58
|
-
c4Globals: {
|
|
59
|
-
predicates: {},
|
|
60
|
-
dynamicPredicates: {},
|
|
61
|
-
styles: {},
|
|
62
|
-
},
|
|
63
|
-
c4Views: [],
|
|
64
|
-
}
|
|
65
|
-
doc = Object.assign(doc, props)
|
|
66
|
-
const parser = this.forDocument(doc)
|
|
67
|
-
parser.parseSpecification()
|
|
68
|
-
parser.parseModel()
|
|
69
|
-
parser.parseGlobals()
|
|
70
|
-
parser.parseDeployment()
|
|
71
|
-
parser.parseViews()
|
|
72
|
-
return parser.doc
|
|
73
|
-
} catch (cause) {
|
|
74
|
-
throw new Error(`Error parsing document ${doc.uri.toString()}`, { cause })
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
forDocument(doc: LangiumDocument): DocumentParser {
|
|
79
|
-
invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
|
|
80
|
-
return this.cachedParsers.get(
|
|
81
|
-
doc.uri,
|
|
82
|
-
'DocumentParser',
|
|
83
|
-
() => new DocumentParser(this.services, doc as ParsedLikeC4LangiumDocument),
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
}
|
package/src/model/parser/Base.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import type * as c4 from '@likec4/core'
|
|
2
|
-
import { invariant, isNonEmptyArray } from '@likec4/core'
|
|
3
|
-
import type { AstNode } from 'langium'
|
|
4
|
-
import { filter, flatMap, fromEntries, isEmpty, isNonNullish, isTruthy, map, pipe, unique } from 'remeda'
|
|
5
|
-
import stripIndent from 'strip-indent'
|
|
6
|
-
import { type ParsedLikeC4LangiumDocument, type ParsedLink, ast } from '../../ast'
|
|
7
|
-
import type { LikeC4Services } from '../../module'
|
|
8
|
-
import { getFqnElementRef } from '../../utils/elementRef'
|
|
9
|
-
import { type IsValidFn, checksFromDiagnostics } from '../../validation'
|
|
10
|
-
|
|
11
|
-
// the class which this mixin is applied to
|
|
12
|
-
export type GConstructor<T = {}> = new(...args: any[]) => T
|
|
13
|
-
|
|
14
|
-
export function toSingleLine<T extends string | undefined | null>(str: T): T {
|
|
15
|
-
return (isNonNullish(str) ? removeIndent(str).split('\n').join(' ') : undefined) as T
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function removeIndent<T extends string | undefined | null>(str: T): T {
|
|
19
|
-
return (isNonNullish(str) ? stripIndent(str).trim() : undefined) as T
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type Base = GConstructor<BaseParser>
|
|
23
|
-
|
|
24
|
-
export class BaseParser {
|
|
25
|
-
isValid: IsValidFn
|
|
26
|
-
|
|
27
|
-
constructor(
|
|
28
|
-
public readonly services: LikeC4Services,
|
|
29
|
-
public readonly doc: ParsedLikeC4LangiumDocument,
|
|
30
|
-
) {
|
|
31
|
-
// do nothing
|
|
32
|
-
this.isValid = checksFromDiagnostics(doc).isValid
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
resolveFqn(node: ast.FqnReferenceable): c4.Fqn {
|
|
36
|
-
if (ast.isDeploymentElement(node)) {
|
|
37
|
-
return this.services.likec4.DeploymentsIndex.getFqn(node)
|
|
38
|
-
}
|
|
39
|
-
if (ast.isExtendElement(node)) {
|
|
40
|
-
return getFqnElementRef(node.element)
|
|
41
|
-
}
|
|
42
|
-
const fqn = this.services.likec4.FqnIndex.getFqn(node)
|
|
43
|
-
invariant(fqn, `Not indexed element: ${this.getAstNodePath(node)}`)
|
|
44
|
-
return fqn
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
getAstNodePath(node: AstNode) {
|
|
48
|
-
return this.services.workspace.AstNodeLocator.getAstNodePath(node)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getMetadata(metadataAstNode: ast.MetadataProperty | undefined): { [key: string]: string } | undefined {
|
|
52
|
-
if (!metadataAstNode || !this.isValid(metadataAstNode) || isEmpty(metadataAstNode.props)) {
|
|
53
|
-
return undefined
|
|
54
|
-
}
|
|
55
|
-
const data = pipe(
|
|
56
|
-
metadataAstNode.props,
|
|
57
|
-
map(p => [p.key, removeIndent(p.value)] as [string, string]),
|
|
58
|
-
filter(([_, value]) => isTruthy(value)),
|
|
59
|
-
fromEntries(),
|
|
60
|
-
)
|
|
61
|
-
return isEmpty(data) ? undefined : data
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
convertTags<E extends { tags?: ast.Tags }>(withTags?: E) {
|
|
65
|
-
return this.parseTags(withTags)
|
|
66
|
-
}
|
|
67
|
-
parseTags<E extends { tags?: ast.Tags }>(withTags?: E): c4.NonEmptyArray<c4.Tag> | null {
|
|
68
|
-
let iter = withTags?.tags
|
|
69
|
-
if (!iter) {
|
|
70
|
-
return null
|
|
71
|
-
}
|
|
72
|
-
let tags = [] as c4.Tag[]
|
|
73
|
-
while (iter) {
|
|
74
|
-
try {
|
|
75
|
-
if (this.isValid(iter)) {
|
|
76
|
-
const values = iter.values.map(t => t.ref?.name).filter(isTruthy) as c4.Tag[]
|
|
77
|
-
if (values.length > 0) {
|
|
78
|
-
tags.push(...values)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch (e) {
|
|
82
|
-
// ignore
|
|
83
|
-
}
|
|
84
|
-
iter = iter.prev
|
|
85
|
-
}
|
|
86
|
-
tags = unique(tags.reverse())
|
|
87
|
-
return isNonEmptyArray(tags) ? tags : null
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
convertLinks(source?: ast.LinkProperty['$container']): ParsedLink[] | undefined {
|
|
91
|
-
return this.parseLinks(source)
|
|
92
|
-
}
|
|
93
|
-
parseLinks(source?: ast.LinkProperty['$container']): ParsedLink[] | undefined {
|
|
94
|
-
if (!source?.props || source.props.length === 0) {
|
|
95
|
-
return undefined
|
|
96
|
-
}
|
|
97
|
-
return pipe(
|
|
98
|
-
source.props,
|
|
99
|
-
filter(ast.isLinkProperty),
|
|
100
|
-
flatMap(p => {
|
|
101
|
-
if (!this.isValid(p)) {
|
|
102
|
-
return []
|
|
103
|
-
}
|
|
104
|
-
const url = p.value
|
|
105
|
-
if (isTruthy(url)) {
|
|
106
|
-
const title = isTruthy(p.title) ? toSingleLine(p.title) : undefined
|
|
107
|
-
return title ? { url, title } : { url }
|
|
108
|
-
}
|
|
109
|
-
return []
|
|
110
|
-
}),
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import type * as c4 from '@likec4/core'
|
|
2
|
-
import { FqnRef, isNonEmptyArray, nameFromFqn, nonexhaustive, nonNullable } from '@likec4/core'
|
|
3
|
-
import { filter, first, isTruthy, map, mapToObj, pipe } from 'remeda'
|
|
4
|
-
import {
|
|
5
|
-
type ParsedAstDeployment,
|
|
6
|
-
type ParsedAstDeploymentRelation,
|
|
7
|
-
ast,
|
|
8
|
-
streamDeploymentModel,
|
|
9
|
-
toElementStyle,
|
|
10
|
-
toRelationshipStyleExcludeDefaults,
|
|
11
|
-
} from '../../ast'
|
|
12
|
-
import { logWarnError } from '../../logger'
|
|
13
|
-
import { elementRef } from '../../utils/elementRef'
|
|
14
|
-
import { stringHash } from '../../utils/stringHash'
|
|
15
|
-
import { removeIndent, toSingleLine } from './Base'
|
|
16
|
-
import type { WithExpressionV2 } from './FqnRefParser'
|
|
17
|
-
|
|
18
|
-
export type WithDeploymentModel = ReturnType<typeof DeploymentModelParser>
|
|
19
|
-
|
|
20
|
-
export function DeploymentModelParser<TBase extends WithExpressionV2>(B: TBase) {
|
|
21
|
-
return class DeploymentModelParser extends B {
|
|
22
|
-
parseDeployment() {
|
|
23
|
-
const doc = this.doc
|
|
24
|
-
for (const el of streamDeploymentModel(doc)) {
|
|
25
|
-
try {
|
|
26
|
-
switch (true) {
|
|
27
|
-
case ast.isDeploymentRelation(el): {
|
|
28
|
-
if (this.isValid(el)) {
|
|
29
|
-
doc.c4DeploymentRelations.push(this.parseDeploymentRelation(el))
|
|
30
|
-
}
|
|
31
|
-
break
|
|
32
|
-
}
|
|
33
|
-
case ast.isDeployedInstance(el):
|
|
34
|
-
doc.c4Deployments.push(this.parseDeployedInstance(el))
|
|
35
|
-
break
|
|
36
|
-
case ast.isDeploymentNode(el): {
|
|
37
|
-
doc.c4Deployments.push(this.parseDeploymentNode(el))
|
|
38
|
-
break
|
|
39
|
-
}
|
|
40
|
-
default:
|
|
41
|
-
nonexhaustive(el)
|
|
42
|
-
}
|
|
43
|
-
} catch (e) {
|
|
44
|
-
logWarnError(e)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
parseDeploymentNode(astNode: ast.DeploymentNode): ParsedAstDeployment.Node {
|
|
50
|
-
const isValid = this.isValid
|
|
51
|
-
const id = this.resolveFqn(astNode)
|
|
52
|
-
const kind = nonNullable(astNode.kind.ref, 'DeploymentKind not resolved').name as c4.DeploymentNodeKind
|
|
53
|
-
const tags = this.convertTags(astNode.body)
|
|
54
|
-
const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
|
|
55
|
-
const style = toElementStyle(stylePropsAst, isValid)
|
|
56
|
-
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
|
|
57
|
-
|
|
58
|
-
const bodyProps = pipe(
|
|
59
|
-
astNode.body?.props ?? [],
|
|
60
|
-
filter(isValid),
|
|
61
|
-
filter(ast.isElementStringProperty),
|
|
62
|
-
mapToObj(p => [p.key, p.value || undefined]),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
const title = removeIndent(astNode.title ?? bodyProps.title)
|
|
66
|
-
const description = removeIndent(bodyProps.description)
|
|
67
|
-
const technology = toSingleLine(bodyProps.technology)
|
|
68
|
-
|
|
69
|
-
const links = this.convertLinks(astNode.body)
|
|
70
|
-
|
|
71
|
-
// Property has higher priority than from style
|
|
72
|
-
const iconProp = astNode.body?.props.find(ast.isIconProperty)
|
|
73
|
-
if (iconProp && isValid(iconProp)) {
|
|
74
|
-
const value = iconProp.libicon?.ref?.name ?? iconProp.value
|
|
75
|
-
if (isTruthy(value)) {
|
|
76
|
-
style.icon = value as c4.IconUrl
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
id,
|
|
82
|
-
kind,
|
|
83
|
-
title: title ?? nameFromFqn(id),
|
|
84
|
-
...(metadata && { metadata }),
|
|
85
|
-
...(tags && { tags }),
|
|
86
|
-
...(links && isNonEmptyArray(links) && { links }),
|
|
87
|
-
...(isTruthy(technology) && { technology }),
|
|
88
|
-
...(isTruthy(description) && { description }),
|
|
89
|
-
style,
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
parseDeployedInstance(astNode: ast.DeployedInstance): ParsedAstDeployment.Instance {
|
|
94
|
-
const isValid = this.isValid
|
|
95
|
-
const id = this.resolveFqn(astNode)
|
|
96
|
-
const element = this.resolveFqn(nonNullable(elementRef(astNode.element), 'DeployedInstance element not found'))
|
|
97
|
-
|
|
98
|
-
const tags = this.convertTags(astNode.body)
|
|
99
|
-
const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
|
|
100
|
-
const style = toElementStyle(stylePropsAst, isValid)
|
|
101
|
-
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
|
|
102
|
-
|
|
103
|
-
const bodyProps = pipe(
|
|
104
|
-
astNode.body?.props ?? [],
|
|
105
|
-
filter(isValid),
|
|
106
|
-
filter(ast.isElementStringProperty),
|
|
107
|
-
mapToObj(p => [p.key, p.value || undefined]),
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
const title = removeIndent(astNode.title ?? bodyProps.title)
|
|
111
|
-
const description = removeIndent(bodyProps.description)
|
|
112
|
-
const technology = toSingleLine(bodyProps.technology)
|
|
113
|
-
|
|
114
|
-
const links = this.convertLinks(astNode.body)
|
|
115
|
-
|
|
116
|
-
// Property has higher priority than from style
|
|
117
|
-
const iconProp = astNode.body?.props.find(ast.isIconProperty)
|
|
118
|
-
if (iconProp && isValid(iconProp)) {
|
|
119
|
-
const value = iconProp.libicon?.ref?.name ?? iconProp.value
|
|
120
|
-
if (isTruthy(value)) {
|
|
121
|
-
style.icon = value as c4.IconUrl
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
id,
|
|
127
|
-
element,
|
|
128
|
-
...(metadata && { metadata }),
|
|
129
|
-
...(title && { title }),
|
|
130
|
-
...(tags && { tags }),
|
|
131
|
-
...(links && isNonEmptyArray(links) && { links }),
|
|
132
|
-
...(isTruthy(technology) && { technology }),
|
|
133
|
-
...(isTruthy(description) && { description }),
|
|
134
|
-
style,
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
parseDeploymentRelation(astNode: ast.DeploymentRelation): ParsedAstDeploymentRelation {
|
|
139
|
-
const isValid = this.isValid
|
|
140
|
-
const astPath = this.getAstNodePath(astNode)
|
|
141
|
-
const source = FqnRef.toDeploymentRef(this.parseFqnRef(astNode.source))
|
|
142
|
-
const target = FqnRef.toDeploymentRef(this.parseFqnRef(astNode.target))
|
|
143
|
-
|
|
144
|
-
const tags = this.convertTags(astNode) ?? this.convertTags(astNode.body)
|
|
145
|
-
const links = this.convertLinks(astNode.body)
|
|
146
|
-
const kind = astNode.kind?.ref?.name as (c4.RelationshipKind | undefined)
|
|
147
|
-
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
|
|
148
|
-
|
|
149
|
-
const bodyProps = mapToObj(
|
|
150
|
-
astNode.body?.props.filter(ast.isRelationStringProperty) ?? [],
|
|
151
|
-
p => [p.key, p.value as string | undefined],
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
const navigateTo = pipe(
|
|
155
|
-
astNode.body?.props ?? [],
|
|
156
|
-
filter(ast.isRelationNavigateToProperty),
|
|
157
|
-
map(p => p.value.view.ref?.name),
|
|
158
|
-
filter(isTruthy),
|
|
159
|
-
first(),
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
const title = removeIndent(astNode.title ?? bodyProps.title)
|
|
163
|
-
const description = removeIndent(bodyProps.description)
|
|
164
|
-
const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology)
|
|
165
|
-
|
|
166
|
-
const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty)
|
|
167
|
-
|
|
168
|
-
const id = stringHash(
|
|
169
|
-
'deployment',
|
|
170
|
-
astPath,
|
|
171
|
-
source.id,
|
|
172
|
-
target.id,
|
|
173
|
-
) as c4.RelationId
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
id,
|
|
177
|
-
source,
|
|
178
|
-
target,
|
|
179
|
-
...title && { title },
|
|
180
|
-
...(metadata && { metadata }),
|
|
181
|
-
...(isTruthy(technology) && { technology }),
|
|
182
|
-
...(isTruthy(description) && { description }),
|
|
183
|
-
...(kind && { kind }),
|
|
184
|
-
...(tags && { tags }),
|
|
185
|
-
...(isNonEmptyArray(links) && { links }),
|
|
186
|
-
...toRelationshipStyleExcludeDefaults(styleProp?.props, isValid),
|
|
187
|
-
...(navigateTo && { navigateTo: navigateTo as c4.ViewId }),
|
|
188
|
-
astPath,
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|