From cfe0b000e3833df3b2385083b89cd4d405d66218 Mon Sep 17 00:00:00 2001 From: Lukas Streckeisen Date: Mon, 28 Apr 2025 10:45:45 +0200 Subject: [PATCH 1/5] add scope provider to fix linker errors --- src/language/ContextMapperDslModule.ts | 4 ++ .../ContextMapperDslScopeProvider.ts | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/language/references/ContextMapperDslScopeProvider.ts diff --git a/src/language/ContextMapperDslModule.ts b/src/language/ContextMapperDslModule.ts index fa8eb23..cf3853b 100644 --- a/src/language/ContextMapperDslModule.ts +++ b/src/language/ContextMapperDslModule.ts @@ -13,6 +13,7 @@ import { ContextMapperDslSemanticTokenProvider } from './semantictokens/ContextM 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. @@ -47,6 +48,9 @@ export const ContextMapperDslModule: Module new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry) + }, + references: { + ScopeProvider: (services) => new ContextMapperDslScopeProvider(services) } } diff --git a/src/language/references/ContextMapperDslScopeProvider.ts b/src/language/references/ContextMapperDslScopeProvider.ts new file mode 100644 index 0000000..53457ac --- /dev/null +++ b/src/language/references/ContextMapperDslScopeProvider.ts @@ -0,0 +1,49 @@ +import { AstUtils, DefaultScopeProvider, ReferenceInfo, Scope } from 'langium' +import { + AbstractStakeholder, + Aggregate, + DomainPart, + isContextMappingModel, + isStakeholderGroup, StakeholderGroup +} from '../generated/ast.js' + +export class ContextMapperDslScopeProvider extends DefaultScopeProvider { + 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) + } +} From 9af3cd011b4edade5cf0c390d94778538066931c Mon Sep 17 00:00:00 2001 From: Lukas Streckeisen Date: Mon, 28 Apr 2025 10:46:29 +0200 Subject: [PATCH 2/5] move parser helper to test directory --- test/{parsing => }/ParsingTestHelper.ts | 2 +- test/parsing/CommentParsing.test.ts | 2 +- .../ContextMappingModelParsing.test.ts | 2 +- test/parsing/ExampleFileParsing.test.ts | 2 +- .../boundedContext/AggregateParsing.test.ts | 2 +- .../BoundedContextParsing.test.ts | 2 +- .../SculptorModuleParsing.test.ts | 2 +- .../contextMap/ContextMapParsing.test.ts | 2 +- .../contextMap/RelationshipParsing.test.ts | 2 +- test/parsing/domain/DomainParsing.test.ts | 2 +- .../example-files/stakeholders-and-values.cml | 101 ++++++++++++++++++ .../UserRequirementParsing.test.ts | 2 +- test/parsing/vdad/StakeholdersParsing.test.ts | 2 +- .../parsing/vdad/ValueRegisterParsing.test.ts | 2 +- .../RelationshipSemanticTokenProvider.test.ts | 2 +- .../RequirementsSemanticTokenProvider.test.ts | 2 +- ...ValueRegisterSemanticTokenProvider.test.ts | 2 +- 17 files changed, 117 insertions(+), 16 deletions(-) rename test/{parsing => }/ParsingTestHelper.ts (92%) create mode 100644 test/parsing/example-files/stakeholders-and-values.cml diff --git a/test/parsing/ParsingTestHelper.ts b/test/ParsingTestHelper.ts similarity index 92% rename from test/parsing/ParsingTestHelper.ts rename to test/ParsingTestHelper.ts index 4873c86..db95007 100644 --- a/test/parsing/ParsingTestHelper.ts +++ b/test/ParsingTestHelper.ts @@ -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>, input: string): Promise> { diff --git a/test/parsing/CommentParsing.test.ts b/test/parsing/CommentParsing.test.ts index 63595dd..c21b17a 100644 --- a/test/parsing/CommentParsing.test.ts +++ b/test/parsing/CommentParsing.test.ts @@ -3,7 +3,7 @@ import { clearDocuments, parseHelper } from 'langium/test' import { ContextMappingModel } from '../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { afterEach, beforeAll, describe, test } from 'vitest' -import { parseValidInput } from './ParsingTestHelper.js' +import { parseValidInput } from '../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/ContextMappingModelParsing.test.ts b/test/parsing/ContextMappingModelParsing.test.ts index d6ed057..dad17a2 100644 --- a/test/parsing/ContextMappingModelParsing.test.ts +++ b/test/parsing/ContextMappingModelParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { ContextMappingModel } from '../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from './ParsingTestHelper.js' +import { parseValidInput } from '../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/ExampleFileParsing.test.ts b/test/parsing/ExampleFileParsing.test.ts index 02b6d59..1bc4d72 100644 --- a/test/parsing/ExampleFileParsing.test.ts +++ b/test/parsing/ExampleFileParsing.test.ts @@ -4,7 +4,7 @@ import { ContextMappingModel } from '../../src/language/generated/ast.js' import { EmptyFileSystem } from 'langium' import { beforeAll, describe, test } from 'vitest' import fs from 'fs' -import { parseValidInput } from './ParsingTestHelper.js' +import { parseValidInput } from '../ParsingTestHelper.js' import path from 'node:path' let services: ReturnType diff --git a/test/parsing/boundedContext/AggregateParsing.test.ts b/test/parsing/boundedContext/AggregateParsing.test.ts index d73d859..a9d0044 100644 --- a/test/parsing/boundedContext/AggregateParsing.test.ts +++ b/test/parsing/boundedContext/AggregateParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { Aggregate, ContextMappingModel } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/boundedContext/BoundedContextParsing.test.ts b/test/parsing/boundedContext/BoundedContextParsing.test.ts index 88d9a41..3c38145 100644 --- a/test/parsing/boundedContext/BoundedContextParsing.test.ts +++ b/test/parsing/boundedContext/BoundedContextParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { ContextMappingModel } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, test, expect } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/boundedContext/SculptorModuleParsing.test.ts b/test/parsing/boundedContext/SculptorModuleParsing.test.ts index 28d589e..549cbfe 100644 --- a/test/parsing/boundedContext/SculptorModuleParsing.test.ts +++ b/test/parsing/boundedContext/SculptorModuleParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { ContextMappingModel, SculptorModule } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/contextMap/ContextMapParsing.test.ts b/test/parsing/contextMap/ContextMapParsing.test.ts index 576d847..638b754 100644 --- a/test/parsing/contextMap/ContextMapParsing.test.ts +++ b/test/parsing/contextMap/ContextMapParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { ContextMappingModel } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/contextMap/RelationshipParsing.test.ts b/test/parsing/contextMap/RelationshipParsing.test.ts index 1361221..78151ca 100644 --- a/test/parsing/contextMap/RelationshipParsing.test.ts +++ b/test/parsing/contextMap/RelationshipParsing.test.ts @@ -8,7 +8,7 @@ import { } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/domain/DomainParsing.test.ts b/test/parsing/domain/DomainParsing.test.ts index 58b7479..e267618 100644 --- a/test/parsing/domain/DomainParsing.test.ts +++ b/test/parsing/domain/DomainParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { ContextMappingModel } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/example-files/stakeholders-and-values.cml b/test/parsing/example-files/stakeholders-and-values.cml new file mode 100644 index 0000000..751c3f0 --- /dev/null +++ b/test/parsing/example-files/stakeholders-and-values.cml @@ -0,0 +1,101 @@ + + +BoundedContext SameDayDelivery + + +Stakeholders of SameDayDelivery { + StakeholderGroup Online_Shopping_Company { + Stakeholder Development_Team { + influence MEDIUM + interest HIGH + } + Stakeholder Product_Management { + influence HIGH + interest HIGH + } + Stakeholder Customer_Relationship_Manager { + influence HIGH + interest MEDIUM + } + } + StakeholderGroup Product_Suppliers { + Stakeholder Managers + Stakeholder Logistics_Warehouse_Staff_of_Suppliers + Stakeholder Delivery_Staff_of_Suppliers + } + StakeholderGroup Delivery_Partners { + Stakeholder Route_Planners + Stakeholder Drivers + } + StakeholderGroup Competing_Companies + + StakeholderGroup Logistics_Team { + Stakeholder Logistics_Manager + Stakeholder Warehouse_Staff + } + Stakeholder Government + + StakeholderGroup Customers_and_Shoppers { + Stakeholder Shoppers_in_Emergency_Situations + Stakeholder Others + } +} +ValueRegister SD_Values for SameDayDelivery { + ValueCluster Autonomy { + core AUTONOMY + demonstrator "customer values potentially increased freedom" + demonstrator "delivery staff's freedom might suffer because of work-life-balance" + Value Freedom { + Stakeholder Customers_and_Shoppers { + priority HIGH + impact MEDIUM + consequences + good "increased freedom" + } + } + Value Quality_of_Life { + Stakeholder Customers_and_Shoppers { + consequences + good "less stress in emergendy situations" + } + } + Value Sustainability { + Stakeholder Customers_and_Shoppers { + priority HIGH + impact LOW + consequences + bad "fostering unsustainable behavior (always ordering last minute)" + action "Limit Availability of Feature" ACT + } + } + Value Ability_to_be_patient { + Stakeholder Customers_and_Shoppers { + priority MEDIUM + impact HIGH + consequences + bad "patience of customer will further increase" + action "Only allow feature for emergency (don't make it the default)" ACT + } + } + } + Value WorkLifeBalance { + isCore + demonstrator "Drivers value a healthy work-life-balance" + Stakeholder Drivers { + priority HIGH + impact HIGH + consequences + bad "SDD will harm work-life-balance of drivers" + action "hire more drivers, adjust availability to workload" ACT + } + } + Value Revenue { + demonstrator "Online shopping company needs revenue" + Stakeholder Online_Shopping_Company { + priority HIGH + impact MEDIUM + consequences + good "increased number of customers" + } + } +} diff --git a/test/parsing/requirements/UserRequirementParsing.test.ts b/test/parsing/requirements/UserRequirementParsing.test.ts index 7b6a8bd..f2e54b5 100644 --- a/test/parsing/requirements/UserRequirementParsing.test.ts +++ b/test/parsing/requirements/UserRequirementParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { ContextMappingModel, NormalFeature, StoryFeature, UseCase, UserStory } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/vdad/StakeholdersParsing.test.ts b/test/parsing/vdad/StakeholdersParsing.test.ts index 0b0970b..c701dcf 100644 --- a/test/parsing/vdad/StakeholdersParsing.test.ts +++ b/test/parsing/vdad/StakeholdersParsing.test.ts @@ -3,7 +3,7 @@ import { parseHelper } from 'langium/test' import { ContextMappingModel, Stakeholder, StakeholderGroup, Stakeholders } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseValidInput } from '../ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/parsing/vdad/ValueRegisterParsing.test.ts b/test/parsing/vdad/ValueRegisterParsing.test.ts index 276742f..89269f7 100644 --- a/test/parsing/vdad/ValueRegisterParsing.test.ts +++ b/test/parsing/vdad/ValueRegisterParsing.test.ts @@ -9,7 +9,7 @@ import { } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { beforeAll, describe, expect, test } from 'vitest' -import { parseInvalidInput, parseValidInput } from '../ParsingTestHelper.js' +import { parseInvalidInput, parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/semnantictokens/contextMap/RelationshipSemanticTokenProvider.test.ts b/test/semnantictokens/contextMap/RelationshipSemanticTokenProvider.test.ts index bb49ba6..cf2f006 100644 --- a/test/semnantictokens/contextMap/RelationshipSemanticTokenProvider.test.ts +++ b/test/semnantictokens/contextMap/RelationshipSemanticTokenProvider.test.ts @@ -4,7 +4,7 @@ import { ContextMappingModel } from '../../../src/language/generated/ast.js' import { EmptyFileSystem, LangiumDocument } from 'langium' import { SemanticTokenProvider } from 'langium/lsp' import { afterEach, beforeAll, describe, test } from 'vitest' -import { parseValidInput } from '../../parsing/ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' import { createSemanticTokenParams, expectSemanticTokensToEqual, expectSemanticTokensToHaveLength, diff --git a/test/semnantictokens/requirements/RequirementsSemanticTokenProvider.test.ts b/test/semnantictokens/requirements/RequirementsSemanticTokenProvider.test.ts index 915ef3b..26a5b5b 100644 --- a/test/semnantictokens/requirements/RequirementsSemanticTokenProvider.test.ts +++ b/test/semnantictokens/requirements/RequirementsSemanticTokenProvider.test.ts @@ -10,7 +10,7 @@ import { extractSemanticTokens } from '../SemanticTokenTestHelper.js' import { SemanticTokens } from 'vscode-languageserver-types' -import { parseValidInput } from '../../parsing/ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> diff --git a/test/semnantictokens/vdad/ValueRegisterSemanticTokenProvider.test.ts b/test/semnantictokens/vdad/ValueRegisterSemanticTokenProvider.test.ts index 30c90a3..a921d93 100644 --- a/test/semnantictokens/vdad/ValueRegisterSemanticTokenProvider.test.ts +++ b/test/semnantictokens/vdad/ValueRegisterSemanticTokenProvider.test.ts @@ -11,7 +11,7 @@ import { expectSemanticTokensToHaveLength, extractSemanticTokens } from '../SemanticTokenTestHelper.js' -import { parseValidInput } from '../../parsing/ParsingTestHelper.js' +import { parseValidInput } from '../../ParsingTestHelper.js' let services: ReturnType let parse: ReturnType> From 3c588d0d1a15e3fdf42a65bcff60b597bd90cf58 Mon Sep 17 00:00:00 2001 From: Lukas Streckeisen Date: Mon, 28 Apr 2025 10:46:40 +0200 Subject: [PATCH 3/5] add linking tests --- test/linking/AggregateLinking.test.ts | 87 ++++++++++ test/linking/BoundedContextLinking.test.ts | 179 +++++++++++++++++--- test/linking/DomainLinking.test.ts | 68 ++++++++ test/linking/StakeholderLinking.test.ts | 119 +++++++++++++ test/linking/UserRequirementLinking.test.ts | 107 ++++++++++++ 5 files changed, 532 insertions(+), 28 deletions(-) create mode 100644 test/linking/AggregateLinking.test.ts create mode 100644 test/linking/DomainLinking.test.ts create mode 100644 test/linking/StakeholderLinking.test.ts create mode 100644 test/linking/UserRequirementLinking.test.ts diff --git a/test/linking/AggregateLinking.test.ts b/test/linking/AggregateLinking.test.ts new file mode 100644 index 0000000..ee63e50 --- /dev/null +++ b/test/linking/AggregateLinking.test.ts @@ -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 +let parse: ReturnType> +let document: LangiumDocument | undefined + +beforeAll(async () => { + services = createContextMapperDslServices(EmptyFileSystem) + parse = parseHelper(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') + }) +}) diff --git a/test/linking/BoundedContextLinking.test.ts b/test/linking/BoundedContextLinking.test.ts index 6af3fef..2ace57b 100644 --- a/test/linking/BoundedContextLinking.test.ts +++ b/test/linking/BoundedContextLinking.test.ts @@ -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 let parse: ReturnType> @@ -12,9 +18,6 @@ let document: LangiumDocument | undefined beforeAll(async () => { services = createContextMapperDslServices(EmptyFileSystem) parse = parseHelper(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 () => { @@ -22,28 +25,148 @@ afterEach(async () => { }) 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 = [] - - 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') }) }) diff --git a/test/linking/DomainLinking.test.ts b/test/linking/DomainLinking.test.ts new file mode 100644 index 0000000..39b522b --- /dev/null +++ b/test/linking/DomainLinking.test.ts @@ -0,0 +1,68 @@ +import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js' +import { clearDocuments, parseHelper } from 'langium/test' +import { ContextMappingModel } 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 +let parse: ReturnType> +let document: LangiumDocument | undefined + +beforeAll(async () => { + services = createContextMapperDslServices(EmptyFileSystem) + parse = parseHelper(services.ContextMapperDsl) +}) + +afterEach(async () => { + document && await clearDocuments(services.shared, [document]) +}) + +describe('Domain linking tests', () => { + test('check linking of bounded context implementation with domain', async () => { + document = await parseValidInput(parse, ` + BoundedContext TestContext implements TestDomain + Domain TestDomain + `) + + const boundedContext = document.parseResult.value.boundedContexts[0] + expect(boundedContext.implementedDomainParts).toHaveLength(1) + expect(boundedContext.implementedDomainParts[0]).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[0].ref).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[0].ref?.name).toEqual('TestDomain') + }) + + test('check linking of bounded context implementation with subdomain', async () => { + document = await parseValidInput(parse, ` + BoundedContext TestContext implements TestSubdomain + Domain TestDomain { + Subdomain TestSubdomain + } + `) + + const boundedContext = document.parseResult.value.boundedContexts[0] + expect(boundedContext.implementedDomainParts).toHaveLength(1) + expect(boundedContext.implementedDomainParts[0]).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[0].ref).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[0].ref?.name).toEqual('TestSubdomain') + }) + + test('check linking of bounded context implementation with domain & subdomain', async () => { + document = await parseValidInput(parse, ` + BoundedContext TestContext implements TestSubdomain, AnotherDomain + Domain TestDomain { + Subdomain TestSubdomain + } + Domain AnotherDomain + `) + + const boundedContext = document.parseResult.value.boundedContexts[0] + expect(boundedContext.implementedDomainParts).toHaveLength(2) + expect(boundedContext.implementedDomainParts[0]).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[0].ref).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[0].ref?.name).toEqual('TestSubdomain') + expect(boundedContext.implementedDomainParts[1]).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[1].ref).not.toBeUndefined() + expect(boundedContext.implementedDomainParts[1].ref?.name).toEqual('AnotherDomain') + }) +}) diff --git a/test/linking/StakeholderLinking.test.ts b/test/linking/StakeholderLinking.test.ts new file mode 100644 index 0000000..90f58e1 --- /dev/null +++ b/test/linking/StakeholderLinking.test.ts @@ -0,0 +1,119 @@ +import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js' +import { clearDocuments, parseHelper } from 'langium/test' +import { ContextMappingModel } 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 +let parse: ReturnType> +let document: LangiumDocument | undefined + +beforeAll(async () => { + services = createContextMapperDslServices(EmptyFileSystem) + parse = parseHelper(services.ContextMapperDsl) +}) + +afterEach(async () => { + document && await clearDocuments(services.shared, [document]) +}) + +describe('Stakeholder linking tests', () => { + test('check linking of stakeholder in value elicitation', async () => { + document = await parseValidInput(parse, ` + Stakeholders { + Stakeholder TestStakeholder + } + ValueRegister TestRegister { + Value TestValue { + Stakeholder TestStakeholder + } + } + `) + + const value = document.parseResult.value.valueRegisters[0].values[0] + expect(value.elicitations).toHaveLength(1) + expect(value.elicitations[0].stakeholder).not.toBeUndefined() + expect(value.elicitations[0].stakeholder.ref).not.toBeUndefined() + expect(value.elicitations[0].stakeholder.ref?.name).toEqual('TestStakeholder') + }) + + test('check linking of stakeholder in value epic', async () => { + document = await parseValidInput(parse, ` + Stakeholders { + Stakeholder TestStakeholder + } + ValueRegister TestRegister { + ValueEpic TestEpic { + As a TestStakeholder I value "test" as demonstrated in realization of "realized" + } + } + `) + + const valueEpic = document.parseResult.value.valueRegisters[0].valueEpics[0] + expect(valueEpic.stakeholder).not.toBeUndefined() + expect(valueEpic.stakeholder?.ref).not.toBeUndefined() + expect(valueEpic.stakeholder?.ref?.name).toEqual('TestStakeholder') + }) + + test('check linking of stakeholder in value weighting', async () => { + document = await parseValidInput(parse, ` + Stakeholders { + Stakeholder TestStakeholder + } + ValueRegister TestRegister { + ValueWeighting TestWeighting { + In the context of the SOI, + stakeholder TestStakeholder values "val1" more than "val2" + expecting benefits such as "benefits" + running the risk of harms such as "harms" + } + } + `) + + const valueWeighting = document.parseResult.value.valueRegisters[0].valueWeightings[0] + expect(valueWeighting.stakeholder).not.toBeUndefined() + expect(valueWeighting.stakeholder.ref).not.toBeUndefined() + expect(valueWeighting.stakeholder.ref?.name).toEqual('TestStakeholder') + }) + + test('check linking of stakeholder group in value elicitation', async () => { + document = await parseValidInput(parse, ` + Stakeholders { + StakeholderGroup TestStakeholderGroup + } + ValueRegister TestRegister { + Value TestValue { + Stakeholder TestStakeholderGroup + } + } + `) + + const value = document.parseResult.value.valueRegisters[0].values[0] + expect(value.elicitations).toHaveLength(1) + expect(value.elicitations[0].stakeholder).not.toBeUndefined() + expect(value.elicitations[0].stakeholder.ref).not.toBeUndefined() + expect(value.elicitations[0].stakeholder.ref?.name).toEqual('TestStakeholderGroup') + }) + + test('check linking of stakeholder group stakeholder in value elicitation', async () => { + document = await parseValidInput(parse, ` + Stakeholders { + StakeholderGroup TestStakeholderGroup { + Stakeholder TestStakeholder + } + } + ValueRegister TestRegister { + Value TestValue { + Stakeholder TestStakeholder + } + } + `) + + const value = document.parseResult.value.valueRegisters[0].values[0] + expect(value.elicitations).toHaveLength(1) + expect(value.elicitations[0].stakeholder).not.toBeUndefined() + expect(value.elicitations[0].stakeholder.ref).not.toBeUndefined() + expect(value.elicitations[0].stakeholder.ref?.name).toEqual('TestStakeholder') + }) +}) diff --git a/test/linking/UserRequirementLinking.test.ts b/test/linking/UserRequirementLinking.test.ts new file mode 100644 index 0000000..15baff3 --- /dev/null +++ b/test/linking/UserRequirementLinking.test.ts @@ -0,0 +1,107 @@ +import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js' +import { clearDocuments, parseHelper } from 'langium/test' +import { ContextMappingModel, UserStory } 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 +let parse: ReturnType> +let document: LangiumDocument | undefined + +beforeAll(async () => { + services = createContextMapperDslServices(EmptyFileSystem) + parse = parseHelper(services.ContextMapperDsl) +}) + +afterEach(async () => { + document && await clearDocuments(services.shared, [document]) +}) + +describe('User requirement linking tests', () => { + test('check linking of user requirement in subdomain', async () => { + document = await parseValidInput(parse, ` + Domain TestDomain { + Subdomain TestSubdomain supports TestUseCase, TestUserStory + } + UserStory TestUserStory + UseCase TestUseCase + `) + + const subdomain = document.parseResult.value.domains[0].subdomains[0] + expect(subdomain.supportedFeatures).toHaveLength(2) + expect(subdomain.supportedFeatures[0]).not.toBeUndefined() + expect(subdomain.supportedFeatures[0].ref).not.toBeUndefined() + expect(subdomain.supportedFeatures[0].ref?.name).toEqual('TestUseCase') + expect(subdomain.supportedFeatures[1]).not.toBeUndefined() + expect(subdomain.supportedFeatures[1].ref).not.toBeUndefined() + expect(subdomain.supportedFeatures[1].ref?.name).toEqual('TestUserStory') + }) + + test('check linking of user requirement in aggregate', async () => { + document = await parseValidInput(parse, ` + BoundedContext TestContext { + Aggregate TestAggregate { + features = TestUseCase, TestUserStory + } + } + UserStory TestUserStory + UseCase TestUseCase + `) + + const aggregate = document.parseResult.value.boundedContexts[0].aggregates[0] + expect(aggregate.userRequirements).toHaveLength(2) + expect(aggregate.userRequirements[0]).not.toBeUndefined() + expect(aggregate.userRequirements[0].ref).not.toBeUndefined() + expect(aggregate.userRequirements[0].ref?.name).toEqual('TestUseCase') + expect(aggregate.userRequirements[1]).not.toBeUndefined() + expect(aggregate.userRequirements[1].ref).not.toBeUndefined() + expect(aggregate.userRequirements[1].ref?.name).toEqual('TestUserStory') + }) + + test('check linking of use case in aggregate', async () => { + document = await parseValidInput(parse, ` + BoundedContext TestContext { + Aggregate TestAggregate { + useCases TestUseCase + } + } + UseCase TestUseCase + `) + + const aggregate = document.parseResult.value.boundedContexts[0].aggregates[0] + expect(aggregate.useCases).toHaveLength(1) + expect(aggregate.useCases[0]).not.toBeUndefined() + expect(aggregate.useCases[0].ref).not.toBeUndefined() + expect(aggregate.useCases[0].ref?.name).toEqual('TestUseCase') + }) + + test('check linking of user story in aggregate', async () => { + document = await parseValidInput(parse, ` + BoundedContext TestContext { + Aggregate TestAggregate { + userStories TestUserStory + } + } + UserStory TestUserStory + `) + + const aggregate = document.parseResult.value.boundedContexts[0].aggregates[0] + expect(aggregate.userStories).toHaveLength(1) + expect(aggregate.userStories[0]).not.toBeUndefined() + expect(aggregate.userStories[0].ref).not.toBeUndefined() + expect(aggregate.userStories[0].ref?.name).toEqual('TestUserStory') + }) + + test('check linking of user story in userStory', async () => { + document = await parseValidInput(parse, ` + UserStory TestUserStory split by AnotherStory + UserStory AnotherStory + `) + + const userStory = document.parseResult.value.userRequirements[0] as UserStory + expect(userStory.splittingStory).not.toBeUndefined() + expect(userStory.splittingStory?.ref).not.toBeUndefined() + expect(userStory.splittingStory?.ref?.name).toEqual('AnotherStory') + }) +}) From 80075cebba2bf36a01d1cdb70f8dc16f34cf3933 Mon Sep 17 00:00:00 2001 From: Lukas Streckeisen Date: Mon, 28 Apr 2025 10:50:05 +0200 Subject: [PATCH 4/5] add comment explaining necessity of scope provider --- src/language/references/ContextMapperDslScopeProvider.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/language/references/ContextMapperDslScopeProvider.ts b/src/language/references/ContextMapperDslScopeProvider.ts index 53457ac..f6900d5 100644 --- a/src/language/references/ContextMapperDslScopeProvider.ts +++ b/src/language/references/ContextMapperDslScopeProvider.ts @@ -8,6 +8,11 @@ import { } 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 nested elements to not be in 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)! From f35fafb1a93ded9de393abba889f64ad2f3a1839 Mon Sep 17 00:00:00 2001 From: Lukas Streckeisen Date: Mon, 28 Apr 2025 10:51:35 +0200 Subject: [PATCH 5/5] add comment explaining necessity of scope provider --- src/language/references/ContextMapperDslScopeProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/references/ContextMapperDslScopeProvider.ts b/src/language/references/ContextMapperDslScopeProvider.ts index f6900d5..691f690 100644 --- a/src/language/references/ContextMapperDslScopeProvider.ts +++ b/src/language/references/ContextMapperDslScopeProvider.ts @@ -10,7 +10,7 @@ import { 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 nested elements to not be in global scope. + 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 {