@likec4/language-server 1.8.0 → 1.9.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.86lmJ8ZN.d.cts +1194 -0
- package/dist/shared/language-server.B1TZgyoH.cjs +5371 -0
- package/dist/shared/language-server.CCB4ESN5.mjs +1606 -0
- package/dist/shared/language-server.CFTY6j4e.d.mts +1194 -0
- package/dist/shared/language-server.D0bOlrCi.cjs +1619 -0
- package/dist/shared/language-server.Q-wtPShM.mjs +5360 -0
- package/dist/shared/language-server.RjhrBZS0.d.ts +1194 -0
- package/package.json +35 -20
- package/src/ast.ts +44 -32
- package/src/browser.ts +0 -3
- package/src/elementRef.ts +1 -1
- package/src/generated/ast.ts +105 -86
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +1 -1
- package/src/like-c4.langium +30 -18
- package/src/likec4lib.ts +2 -3
- package/src/logger.ts +9 -1
- package/src/lsp/RenameProvider.ts +8 -0
- package/src/lsp/SemanticTokenProvider.ts +19 -1
- package/src/lsp/index.ts +1 -0
- package/src/model/fqn-computation.ts +33 -23
- package/src/model/fqn-index.ts +5 -20
- package/src/model/model-builder.ts +147 -90
- package/src/model/model-locator.ts +1 -1
- package/src/model/model-parser-where.ts +3 -2
- package/src/model/model-parser.ts +57 -19
- 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 +9 -6
- package/src/model-graph/compute-view/predicates.ts +3 -3
- package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
- package/src/model-graph/dynamic-view/compute.ts +2 -1
- package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
- package/src/model-graph/utils/sortNodes.ts +2 -6
- package/src/module.ts +23 -3
- package/src/protocol.ts +4 -5
- package/src/references/scope-computation.ts +10 -1
- package/src/references/scope-provider.ts +22 -9
- package/src/shared/NodeKindProvider.ts +73 -34
- package/src/test/setup.ts +3 -8
- package/src/utils/graphlib.ts +11 -0
- package/src/validation/_shared.ts +24 -0
- package/src/validation/element.ts +9 -9
- package/src/validation/index.ts +2 -1
- package/src/validation/relation.ts +45 -39
- package/src/validation/specification.ts +15 -2
- package/src/validation/view.ts +7 -0
- 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 +5 -7
- package/src/view-utils/view-hash.ts +1 -1
- package/src/reset.d.ts +0 -2
|
@@ -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
|
})
|
|
@@ -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
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type AstNode, interruptAndCheck, type ValidationAcceptor, type ValidationCheck } from 'langium'
|
|
2
|
+
import type { CancellationToken } from 'vscode-jsonrpc'
|
|
3
|
+
import { logWarnError } from '../logger'
|
|
4
|
+
|
|
5
|
+
export const RESERVED_WORDS = [
|
|
6
|
+
'this',
|
|
7
|
+
'it',
|
|
8
|
+
'self',
|
|
9
|
+
'super',
|
|
10
|
+
'likec4lib'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export function tryOrLog<T extends AstNode>(fn: ValidationCheck<T>): ValidationCheck<T> {
|
|
14
|
+
return async (node: T, accept: ValidationAcceptor, cancelToken: CancellationToken) => {
|
|
15
|
+
if (cancelToken) {
|
|
16
|
+
await interruptAndCheck(cancelToken)
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
await fn(node, accept, cancelToken)
|
|
20
|
+
} catch (e) {
|
|
21
|
+
logWarnError(e)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { AstUtils, type ValidationCheck } from 'langium'
|
|
2
2
|
import type { ast } from '../ast'
|
|
3
3
|
import type { LikeC4Services } from '../module'
|
|
4
|
+
import { RESERVED_WORDS, tryOrLog } from './_shared'
|
|
4
5
|
|
|
5
6
|
const { getDocument } = AstUtils
|
|
6
7
|
|
|
7
8
|
export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Element> => {
|
|
8
9
|
const fqnIndex = services.likec4.FqnIndex
|
|
9
10
|
const locator = services.workspace.AstNodeLocator
|
|
10
|
-
return (el, accept) => {
|
|
11
|
+
return tryOrLog((el, accept) => {
|
|
11
12
|
const fqn = fqnIndex.getFqn(el)
|
|
12
13
|
if (!fqn) {
|
|
13
14
|
accept('error', 'Not indexed element', {
|
|
@@ -16,6 +17,12 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
|
|
|
16
17
|
})
|
|
17
18
|
return
|
|
18
19
|
}
|
|
20
|
+
if (RESERVED_WORDS.includes(el.name)) {
|
|
21
|
+
accept('error', `Reserved word: ${el.name}`, {
|
|
22
|
+
node: el,
|
|
23
|
+
property: 'name'
|
|
24
|
+
})
|
|
25
|
+
}
|
|
19
26
|
const doc = getDocument(el)
|
|
20
27
|
const docUri = doc.uri
|
|
21
28
|
const elPath = locator.getAstNodePath(el)
|
|
@@ -45,12 +52,5 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
|
|
|
45
52
|
}
|
|
46
53
|
)
|
|
47
54
|
}
|
|
48
|
-
|
|
49
|
-
// accept('error', `Too many properties, max 3 allowed`, {
|
|
50
|
-
// node: el,
|
|
51
|
-
// property: 'props',
|
|
52
|
-
// index: i
|
|
53
|
-
// })
|
|
54
|
-
// }
|
|
55
|
-
}
|
|
55
|
+
})
|
|
56
56
|
}
|
package/src/validation/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { dynamicViewRulePredicate } from './dynamic-view-rule'
|
|
|
5
5
|
import { dynamicViewStep } from './dynamic-view-step'
|
|
6
6
|
import { elementChecks } from './element'
|
|
7
7
|
import { iconPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
|
|
8
|
-
import { relationChecks } from './relation'
|
|
8
|
+
import { relationBodyChecks, relationChecks } from './relation'
|
|
9
9
|
import {
|
|
10
10
|
elementKindChecks,
|
|
11
11
|
modelRuleChecks,
|
|
@@ -37,6 +37,7 @@ export function registerValidationChecks(services: LikeC4Services) {
|
|
|
37
37
|
Element: elementChecks(services),
|
|
38
38
|
ElementKind: elementKindChecks(services),
|
|
39
39
|
Relation: relationChecks(services),
|
|
40
|
+
RelationBody: relationBodyChecks(services),
|
|
40
41
|
Tag: tagChecks(services),
|
|
41
42
|
DynamicViewPredicateIterator: dynamicViewRulePredicate(services),
|
|
42
43
|
ElementPredicateWith: elementPredicateWithChecks(services),
|
|
@@ -1,57 +1,63 @@
|
|
|
1
1
|
import { isSameHierarchy } from '@likec4/core'
|
|
2
2
|
import type { ValidationCheck } from 'langium'
|
|
3
|
+
import { isDefined } from 'remeda'
|
|
3
4
|
import { ast } from '../ast'
|
|
4
5
|
import { elementRef } from '../elementRef'
|
|
5
|
-
import { logError } from '../logger'
|
|
6
6
|
import type { LikeC4Services } from '../module'
|
|
7
|
+
import { tryOrLog } from './_shared'
|
|
7
8
|
|
|
8
9
|
export const relationChecks = (services: LikeC4Services): ValidationCheck<ast.Relation> => {
|
|
9
10
|
const fqnIndex = services.likec4.FqnIndex
|
|
10
|
-
return (el, accept) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
return tryOrLog((el, accept) => {
|
|
12
|
+
const targetEl: ast.Element | undefined = elementRef(el.target)
|
|
13
|
+
const target = targetEl && fqnIndex.getFqn(targetEl)
|
|
14
|
+
if (!target) {
|
|
15
|
+
accept('error', 'Target not resolved', {
|
|
16
|
+
node: el,
|
|
17
|
+
property: 'target'
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
let sourceEl
|
|
21
|
+
if (isDefined(el.source)) {
|
|
22
|
+
sourceEl = elementRef(el.source)
|
|
23
|
+
if (!sourceEl) {
|
|
24
|
+
return accept('error', 'Source not resolved', {
|
|
16
25
|
node: el,
|
|
17
|
-
property: '
|
|
26
|
+
property: 'source'
|
|
18
27
|
})
|
|
19
28
|
}
|
|
20
|
-
|
|
21
|
-
if (ast.
|
|
22
|
-
|
|
23
|
-
if (!sourceEl) {
|
|
24
|
-
return accept('error', 'Source not found (not parsed/indexed yet)', {
|
|
25
|
-
node: el,
|
|
26
|
-
property: 'source'
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
} else {
|
|
30
|
-
sourceEl = el.$container.$container
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const source = fqnIndex.getFqn(sourceEl)
|
|
34
|
-
|
|
35
|
-
if (!source) {
|
|
36
|
-
accept('error', 'Source not found (not parsed/indexed yet)', {
|
|
29
|
+
} else {
|
|
30
|
+
if (!ast.isElementBody(el.$container)) {
|
|
31
|
+
return accept('error', 'Sourceless relation must be nested', {
|
|
37
32
|
node: el
|
|
38
33
|
})
|
|
39
34
|
}
|
|
35
|
+
sourceEl = el.$container.$container
|
|
36
|
+
}
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
accept('error', 'Invalid parent-child relationship', {
|
|
43
|
-
node: el
|
|
44
|
-
})
|
|
45
|
-
}
|
|
38
|
+
const source = fqnIndex.getFqn(sourceEl)
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
if (!source) {
|
|
41
|
+
accept('error', 'Source not resolved', {
|
|
42
|
+
node: el
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (source && target && isSameHierarchy(source, target)) {
|
|
47
|
+
accept('error', 'Invalid parent-child relationship', {
|
|
48
|
+
node: el
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const relationBodyChecks = (_services: LikeC4Services): ValidationCheck<ast.RelationBody> => {
|
|
55
|
+
return tryOrLog((body, accept) => {
|
|
56
|
+
const relation = body.$container
|
|
57
|
+
if (relation.tags?.values && body.tags?.values) {
|
|
58
|
+
accept('error', 'Relation cannot have tags in both header and body', {
|
|
59
|
+
node: body.tags
|
|
60
|
+
})
|
|
55
61
|
}
|
|
56
|
-
}
|
|
62
|
+
})
|
|
57
63
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AstUtils, type ValidationCheck } from 'langium'
|
|
2
2
|
import { ast } from '../ast'
|
|
3
3
|
import type { LikeC4Services } from '../module'
|
|
4
|
+
import { RESERVED_WORDS, tryOrLog } from './_shared'
|
|
4
5
|
|
|
5
6
|
export const specificationRuleChecks = (
|
|
6
7
|
_: LikeC4Services
|
|
@@ -39,7 +40,13 @@ export const modelViewsChecks = (_: LikeC4Services): ValidationCheck<ast.ModelVi
|
|
|
39
40
|
|
|
40
41
|
export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast.ElementKind> => {
|
|
41
42
|
const index = services.shared.workspace.IndexManager
|
|
42
|
-
return (node, accept) => {
|
|
43
|
+
return tryOrLog((node, accept) => {
|
|
44
|
+
if (RESERVED_WORDS.includes(node.name)) {
|
|
45
|
+
accept('error', `Reserved word: ${node.name}`, {
|
|
46
|
+
node: node,
|
|
47
|
+
property: 'name'
|
|
48
|
+
})
|
|
49
|
+
}
|
|
43
50
|
const sameKind = index
|
|
44
51
|
.allElements(ast.ElementKind)
|
|
45
52
|
.filter(n => n.name === node.name && n.node !== node)
|
|
@@ -62,7 +69,7 @@ export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast
|
|
|
62
69
|
}
|
|
63
70
|
})
|
|
64
71
|
}
|
|
65
|
-
}
|
|
72
|
+
})
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
export const tagChecks = (services: LikeC4Services): ValidationCheck<ast.Tag> => {
|
|
@@ -103,6 +110,12 @@ export const relationshipChecks = (
|
|
|
103
110
|
): ValidationCheck<ast.RelationshipKind> => {
|
|
104
111
|
const index = services.shared.workspace.IndexManager
|
|
105
112
|
return (node, accept) => {
|
|
113
|
+
if (RESERVED_WORDS.includes(node.name)) {
|
|
114
|
+
accept('error', `Reserved word: ${node.name}`, {
|
|
115
|
+
node: node,
|
|
116
|
+
property: 'name'
|
|
117
|
+
})
|
|
118
|
+
}
|
|
106
119
|
const sameKinds = index
|
|
107
120
|
.allElements(ast.RelationshipKind)
|
|
108
121
|
.filter(n => n.name === node.name)
|
package/src/validation/view.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ValidationCheck } from 'langium'
|
|
2
2
|
import { ast } from '../ast'
|
|
3
3
|
import type { LikeC4Services } from '../module'
|
|
4
|
+
import { RESERVED_WORDS } from './_shared'
|
|
4
5
|
|
|
5
6
|
export const viewChecks = (services: LikeC4Services): ValidationCheck<ast.LikeC4View> => {
|
|
6
7
|
const index = services.shared.workspace.IndexManager
|
|
@@ -15,6 +16,12 @@ export const viewChecks = (services: LikeC4Services): ValidationCheck<ast.LikeC4
|
|
|
15
16
|
if (!el.name) {
|
|
16
17
|
return
|
|
17
18
|
}
|
|
19
|
+
if (RESERVED_WORDS.includes(el.name)) {
|
|
20
|
+
accept('error', `Reserved word: ${el.name}`, {
|
|
21
|
+
node: el,
|
|
22
|
+
property: 'name'
|
|
23
|
+
})
|
|
24
|
+
}
|
|
18
25
|
const anotherViews = index
|
|
19
26
|
.allElements(ast.LikeC4View)
|
|
20
27
|
.filter(n => n.name === el.name)
|
|
@@ -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
2
|
import { invariant } from '@likec4/core'
|
|
3
3
|
import { filter, hasAtLeast, isTruthy, map, pipe, unique, zip } from 'remeda'
|
|
4
|
+
import { parsePath } from 'ufo'
|
|
4
5
|
|
|
5
6
|
function commonAncestorPath(views: LikeC4View[], sep = '/') {
|
|
6
7
|
const uniqURIs = pipe(
|
|
@@ -11,20 +12,20 @@ function commonAncestorPath(views: LikeC4View[], sep = '/') {
|
|
|
11
12
|
)
|
|
12
13
|
if (uniqURIs.length === 0) return ''
|
|
13
14
|
if (hasAtLeast(uniqURIs, 1) && uniqURIs.length === 1) {
|
|
14
|
-
const parts =
|
|
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
|
|
@@ -50,7 +51,7 @@ export function resolveRelativePaths(views: LikeC4View[]): LikeC4View[] {
|
|
|
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),
|
|
@@ -71,9 +72,6 @@ export function resolveRelativePaths(views: LikeC4View[]): LikeC4View[] {
|
|
|
71
72
|
if (a.parts.length === 0) {
|
|
72
73
|
return 0
|
|
73
74
|
}
|
|
74
|
-
if (a.parts.length === 1 && hasAtLeast(a.parts, 1) && hasAtLeast(b.parts, 1)) {
|
|
75
|
-
return a.parts[0].localeCompare(b.parts[0])
|
|
76
|
-
}
|
|
77
75
|
for (const [_a, _b] of zip(a.parts, b.parts)) {
|
|
78
76
|
const compare = _a.localeCompare(_b)
|
|
79
77
|
if (compare !== 0) {
|
package/src/reset.d.ts
DELETED