Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/language/ContextMapperDslModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { SemanticTokenProviderRegistry } from './semantictokens/SemanticTokenProviderRegistry.js'
import { ContextMapperDslValidationRegistry } from './validation/ContextMapperDslValidationRegistry.js'
import { ContextMapperValidationProviderRegistry } from './validation/ContextMapperValidationProviderRegistry.js'
import { ContextMapperDslScopeProvider } from './references/ContextMapperDslScopeProvider.js'

/**
* Declaration of custom services - add your own service classes here.
Expand Down Expand Up @@ -47,6 +48,9 @@
},
lsp: {
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry)
},
references: {
ScopeProvider: (services) => new ContextMapperDslScopeProvider(services)
}
}

Expand Down Expand Up @@ -82,7 +86,7 @@
if (!context.connection) {
// We don't run inside a language server
// Therefore, initialize the configuration provider instantly
shared.workspace.ConfigurationProvider.initialized({})

Check notice on line 89 in src/language/ContextMapperDslModule.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

Result of method call returning a promise is ignored

Promise returned from initialized is ignored
}
return { shared, ContextMapperDsl }
}
54 changes: 54 additions & 0 deletions src/language/references/ContextMapperDslScopeProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { AstUtils, DefaultScopeProvider, ReferenceInfo, Scope } from 'langium'
import {
AbstractStakeholder,
Aggregate,
DomainPart,
isContextMappingModel,
isStakeholderGroup, StakeholderGroup
} from '../generated/ast.js'

export class ContextMapperDslScopeProvider extends DefaultScopeProvider {
/*
Some ContextMapper elements are not defined on the top-level of the document and should still be referencable.
Langium assumes that nested elements don't belong in the global scope.
Therefore, nested elements are explicitly added to the scope, while for other elements the Langium default behavior applies.
*/
override getScope (context: ReferenceInfo): Scope {
const referenceType = this.reflection.getReferenceType(context)
const model = AstUtils.getContainerOfType(context.container, isContextMappingModel)!

if (referenceType === DomainPart) {
const domainPartDescriptions = model.domains.map(d => this.descriptions.createDescription(d, d.name))
.concat(
model.domains.flatMap(d => d.subdomains)
.map(sd => this.descriptions.createDescription(sd, sd.name))
)
return this.createScope(domainPartDescriptions)
}

if (referenceType === Aggregate) {
const aggregateDescriptions = model.boundedContexts.flatMap(bc => bc.aggregates)
.map(a => this.descriptions.createDescription(a, a.name))
.concat(
model.boundedContexts.flatMap(bc => bc.modules)
.flatMap(m => m.aggregates)
.map(a => this.descriptions.createDescription(a, a.name))
)
return this.createScope(aggregateDescriptions)
}

if (referenceType === AbstractStakeholder) {
const stakeholderDescriptions = model.stakeholders.flatMap(s => s.stakeholders)
.map(s => this.descriptions.createDescription(s, s.name))
.concat(
model.stakeholders.flatMap(s => s.stakeholders)
.filter(s => isStakeholderGroup(s))
.flatMap(sg => (sg as StakeholderGroup).stakeholders)
.map(s => this.descriptions.createDescription(s, s.name))
)
return this.createScope(stakeholderDescriptions)
}

return super.getScope(context)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'vitest'
import { LangiumDocument } from 'langium'
import { ContextMappingModel } from '../../src/language/generated/ast.js'
import { ContextMappingModel } from '../src/language/generated/ast.js'
import { parseHelper } from 'langium/test'

export async function parseValidInput (parse: ReturnType<typeof parseHelper<ContextMappingModel>>, input: string): Promise<LangiumDocument<ContextMappingModel>> {
Expand Down
87 changes: 87 additions & 0 deletions test/linking/AggregateLinking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js'
import { clearDocuments, parseHelper } from 'langium/test'
import {
ContextMappingModel,
CustomerSupplierRelationship,
UpstreamDownstreamRelationship
} from '../../src/language/generated/ast.js'
import { EmptyFileSystem, LangiumDocument } from 'langium'
import { afterEach, beforeAll, describe, expect, test } from 'vitest'
import { parseValidInput } from '../ParsingTestHelper.js'

let services: ReturnType<typeof createContextMapperDslServices>
let parse: ReturnType<typeof parseHelper<ContextMappingModel>>
let document: LangiumDocument<ContextMappingModel> | undefined

beforeAll(async () => {
services = createContextMapperDslServices(EmptyFileSystem)
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)
})

afterEach(async () => {
document && await clearDocuments(services.shared, [document])
})

describe('Aggregate linking tests', () => {
test('check linking of aggregate in UpstreamDownstream relationship', async () => {
document = await parseValidInput(parse, `
ContextMap {
TestContext -> FirstContext {
exposedAggregates TestAggregate
}
}
BoundedContext FirstContext
BoundedContext TestContext {
Aggregate TestAggregate
}
`)

const relationship = document.parseResult.value.contextMaps[0].relationships[0] as UpstreamDownstreamRelationship
expect(relationship.upstreamExposedAggregates).toHaveLength(1)
expect(relationship.upstreamExposedAggregates[0]).not.toBeUndefined()
expect(relationship.upstreamExposedAggregates[0].ref).not.toBeUndefined()
expect(relationship.upstreamExposedAggregates[0].ref?.name).toEqual('TestAggregate')
})

test('check linking of aggregate in CustomerSupplier relationship', async () => {
document = await parseValidInput(parse, `
ContextMap {
TestContext [S] -> [C] FirstContext {
exposedAggregates TestAggregate
}
}
BoundedContext FirstContext
BoundedContext TestContext {
Aggregate TestAggregate
}
`)

const relationship = document.parseResult.value.contextMaps[0].relationships[0] as CustomerSupplierRelationship
expect(relationship.upstreamExposedAggregates).toHaveLength(1)
expect(relationship.upstreamExposedAggregates[0]).not.toBeUndefined()
expect(relationship.upstreamExposedAggregates[0].ref).not.toBeUndefined()
expect(relationship.upstreamExposedAggregates[0].ref?.name).toEqual('TestAggregate')
})

test('check linking of module aggregate in CustomerSupplier relationship', async () => {
document = await parseValidInput(parse, `
ContextMap {
TestContext [S] -> [C] FirstContext {
exposedAggregates TestAggregate
}
}
BoundedContext FirstContext
BoundedContext TestContext {
Module TestModule {
Aggregate TestAggregate
}
}
`)

const relationship = document.parseResult.value.contextMaps[0].relationships[0] as CustomerSupplierRelationship
expect(relationship.upstreamExposedAggregates).toHaveLength(1)
expect(relationship.upstreamExposedAggregates[0]).not.toBeUndefined()
expect(relationship.upstreamExposedAggregates[0].ref).not.toBeUndefined()
expect(relationship.upstreamExposedAggregates[0].ref?.name).toEqual('TestAggregate')
})
})
179 changes: 151 additions & 28 deletions test/linking/BoundedContextLinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ import { afterEach, beforeAll, describe, expect, test } from 'vitest'
import { EmptyFileSystem, type LangiumDocument } from 'langium'
import { clearDocuments, parseHelper } from 'langium/test'
import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js'
import { ContextMappingModel, isSharedKernel } from '../../src/language/generated/ast.js'
import { checkDocumentValid } from '../TestHelper.js'
import {
ContextMappingModel,
CustomerSupplierRelationship,
Partnership,
SharedKernel,
UpstreamDownstreamRelationship
} from '../../src/language/generated/ast.js'
import { parseValidInput } from '../ParsingTestHelper.js'

let services: ReturnType<typeof createContextMapperDslServices>
let parse: ReturnType<typeof parseHelper<ContextMappingModel>>
Expand All @@ -12,38 +18,155 @@ let document: LangiumDocument<ContextMappingModel> | undefined
beforeAll(async () => {
services = createContextMapperDslServices(EmptyFileSystem)
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)

// activate the following if your linking test requires elements from a built-in library, for example
// await services.shared.workspace.WorkspaceManager.initializeWorkspace([]);
})

afterEach(async () => {
document && await clearDocuments(services.shared, [document])
})

describe('Bounded context linking tests', () => {
test('linking of bounded contexts in context map', async () => {
document = await parse(`
ContextMap {
TestContext [SK] <-> [SK] FirstContext
}
BoundedContext FirstContext
BoundedContext TestContext
`)

const errors = checkDocumentValid(document)
expect(errors == null).toBeTruthy()

const referencedContexts: Array<string | undefined> = []

document.parseResult.value.contextMaps[0].relationships.forEach(r => {
if (isSharedKernel(r)) {
referencedContexts.push(r.participant1.ref?.name)
referencedContexts.push(r.participant2.ref?.name)
}
})

expect(referencedContexts.length).toBe(2)
expect(referencedContexts).toEqual(['TestContext', 'FirstContext'])
test('check linking of bounded contexts in context map SharedKernel relationship', async () => {
document = await parseValidInput(parse, `
ContextMap {
TestContext [SK] <-> [SK] FirstContext
}
BoundedContext FirstContext
BoundedContext TestContext
`)

const relationship = document.parseResult.value.contextMaps[0].relationships[0] as SharedKernel
expect(relationship.participant1.ref).not.toBeUndefined()
expect(relationship.participant1.ref?.name).toEqual('TestContext')
expect(relationship.participant2.ref).not.toBeUndefined()
expect(relationship.participant2.ref?.name).toEqual('FirstContext')
})

test('check linking of bounded contexts in context map Partnership relationship', async () => {
document = await parseValidInput(parse, `
ContextMap {
FirstContext Partnership TestContext
}
BoundedContext FirstContext
BoundedContext TestContext
`)

const relationship = document.parseResult.value.contextMaps[0].relationships[0] as Partnership
expect(relationship.participant1.ref).not.toBeUndefined()
expect(relationship.participant1.ref?.name).toEqual('FirstContext')
expect(relationship.participant2.ref).not.toBeUndefined()
expect(relationship.participant2.ref?.name).toEqual('TestContext')
})

test('check linking of bounded contexts in context map UpstreamDownstream relationship', async () => {
document = await parseValidInput(parse, `
ContextMap {
FirstContext -> TestContext
}
BoundedContext FirstContext
BoundedContext TestContext
`)

const relationship = document.parseResult.value.contextMaps[0].relationships[0] as UpstreamDownstreamRelationship
expect(relationship.upstream.ref).not.toBeUndefined()
expect(relationship.upstream.ref?.name).toEqual('FirstContext')
expect(relationship.downstream.ref).not.toBeUndefined()
expect(relationship.downstream.ref?.name).toEqual('TestContext')
})

test('check linking of bounded contexts in context map CustomerSupplier relationship', async () => {
document = await parseValidInput(parse, `
ContextMap {
FirstContext [C] <- [S] TestContext
}
BoundedContext FirstContext
BoundedContext TestContext
`)

const relationship = document.parseResult.value.contextMaps[0].relationships[0] as CustomerSupplierRelationship
expect(relationship.downstream.ref).not.toBeUndefined()
expect(relationship.downstream.ref?.name).toEqual('FirstContext')
expect(relationship.upstream.ref).not.toBeUndefined()
expect(relationship.upstream.ref?.name).toEqual('TestContext')
})

test('check linking of bounded contexts in context map', async () => {
document = await parseValidInput(parse, `
ContextMap {
contains FirstContext
contains SecondContext
}
BoundedContext FirstContext
BoundedContext SecondContext
`)

const contextMap = document.parseResult.value.contextMaps[0]
expect(contextMap.boundedContexts).toHaveLength(2)
expect(contextMap.boundedContexts[0].ref).not.toBeUndefined()
expect(contextMap.boundedContexts[0].ref?.name).toEqual('FirstContext')
expect(contextMap.boundedContexts[1].ref).not.toBeUndefined()
expect(contextMap.boundedContexts[1].ref?.name).toEqual('SecondContext')
})

test('check linking of bounded context from bounded context', async () => {
document = await parseValidInput(parse, `
BoundedContext TestContext
refines RefinedContext
realizes RealizedContext

BoundedContext RealizedContext
BoundedContext RefinedContext
`)

const boundedContext = document.parseResult.value.boundedContexts[0]
expect(boundedContext.refinedBoundedContext).not.toBeUndefined()
expect(boundedContext.refinedBoundedContext.ref).not.toBeUndefined()
expect(boundedContext.refinedBoundedContext.ref?.name).toEqual('RefinedContext')
expect(boundedContext.realizedBoundedContexts).toHaveLength(1)
expect(boundedContext.realizedBoundedContexts[0].ref).not.toBeUndefined()
expect(boundedContext.realizedBoundedContexts[0].ref?.name).toEqual('RealizedContext')
})

test('check linking of aggregate owner', async () => {
document = await parseValidInput(parse, `
BoundedContext TestContext {
Aggregate TestAggregate {
owner TestContext
}
}
`)

const aggregate = document.parseResult.value.boundedContexts[0].aggregates[0]
expect(aggregate.owner).not.toBeUndefined()
expect(aggregate.owner?.ref).not.toBeUndefined()
expect(aggregate.owner?.ref?.name).toEqual('TestContext')
})

test('check linking of shareholders context', async () => {
document = await parseValidInput(parse, `
BoundedContext FirstContext
BoundedContext SecondContext
Stakeholders of SecondContext, FirstContext
`)

const stakeholders = document.parseResult.value.stakeholders[0]
expect(stakeholders.contexts).toHaveLength(2)
expect(stakeholders.contexts[0]).not.toBeUndefined()
expect(stakeholders.contexts[0].ref).not.toBeUndefined()
expect(stakeholders.contexts[0].ref?.name).toEqual('SecondContext')
expect(stakeholders.contexts[1]).not.toBeUndefined()
expect(stakeholders.contexts[1].ref).not.toBeUndefined()
expect(stakeholders.contexts[1].ref?.name).toEqual('FirstContext')
})

test('check linking of ValueRegister context', async () => {
document = await parseValidInput(parse, `
BoundedContext SecondContext
ValueRegister TestRegister for SecondContext
`)

const valueRegister = document.parseResult.value.valueRegisters[0]
expect(valueRegister.context).not.toBeUndefined()
expect(valueRegister.context?.ref).not.toBeUndefined()
expect(valueRegister.context?.ref?.name).toEqual('SecondContext')
})
})
Loading
Loading