Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TacticDDD dsl formatting improvements
Browse files Browse the repository at this point in the history
* Traits and BasicTypes are included in formatting rules
* Comments trigger a new-line
* references and traits receive an leading "@" even if created by API
astmuc committed Dec 11, 2024

Verified

This commit was signed with the committer’s verified signature.
mitchellh Mitchell Hashimoto
1 parent 981d5be commit 56bf203
Showing 8 changed files with 396 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ class ExtractIDValueObjectQuickFixTest extends AbstractCMLLanguageServerTest {
Entity Customer {
String firstname
String lastname
- CustomerId customerId
- @CustomerId customerId
}
ValueObject CustomerId {
String id
Original file line number Diff line number Diff line change
@@ -27,17 +27,17 @@ BoundedContext ClaimsManagement implements ClaimsManagement {
Double amountClaimed
String desc
String claimId key
- Agent agent
- @Agent agent
}
Entity Policy {
Date startDate
Date endDate
String policyId key
- List<Claim> claims
- List<@Claim> claims
}
Entity Contract {
String contractId key
- List<Policy> policies
- List<@Policy> policies
}
Entity Agent {
Long personalID
@@ -49,7 +49,7 @@ BoundedContext ClaimsManagement implements ClaimsManagement {
String firstName
String lastName
String claimantId key
- List<Claim> claims
- List<@Claim> claims
}
}
}
@@ -60,15 +60,15 @@ Domain Insurance_Application {
Date date
Double amountClaimed
String desc
- Agent agent
- @Agent agent
}
Entity Policy {
Date startDate
Date endDate
- List<Claim> claims
- List<@Claim> claims
}
Entity Contract {
- List<Policy> policies
- List<@Policy> policies
}
Entity Agent {
Long personalID
@@ -78,7 +78,7 @@ Domain Insurance_Application {
Entity Claimant {
String firstName
String lastName
- List<Claim> claims
- List<@Claim> claims
}
Service AccidentService {
submitClaim;
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright 2018 The Context Mapper Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.contextmapper.dsl

import com.google.inject.Inject
import org.contextmapper.dsl.contextMappingDSL.ContextMappingModel
import org.eclipse.xtext.testing.InjectWith
import org.eclipse.xtext.testing.extensions.InjectionExtension
import org.eclipse.xtext.testing.util.ParseHelper
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.^extension.ExtendWith

import static org.junit.jupiter.api.Assertions.*
import org.contextmapper.dsl.tests.ContextMappingDSLInjectorProvider
import org.eclipse.xtext.resource.SaveOptions
import org.eclipse.xtext.serializer.ISerializer
import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup
import org.contextmapper.dsl.contextMappingDSL.ContextMappingDSLFactory
import org.contextmapper.tactic.dsl.tacticdsl.TacticdslFactory

@ExtendWith(InjectionExtension)
@InjectWith(ContextMappingDSLInjectorProvider)
class ContextMapDSLFormattingTest {
@Inject
ParseHelper<ContextMappingModel> parseHelper
@Inject extension ISerializer

/**
* Aggregate with entities, traits, basic-types and enums with interspersed comments.
* The traits are used with the 'with' keyword
*/
@Test
def void formatsMapLoadedFromFile() {
val testInput = '''
BoundedContext BC1 { Aggregate A1 { Entity E1 { String prop1 "prop1Comment" String prop2 }
enum State1 { A, B } Trait T1 {}
"State2Comment" enum State2 { "AA" A, B } "E2Comment" Entity E2
with@T1 { package = abc validate = "validation" aggregateRoot "E2Comment"
String prop3 } Entity E3 { String prop1 }
Trait T2 { "prop3Comment" String prop3 "ref1Comment" - @E1 ref1 }
BasicType BT1 { "prop4Comment" int prop4 - List< @T2> prop5 }
"V1Comment" ValueObject V1 with@T1 with@T2 { int intProp }}} BoundedContext BC2
{ Aggregate A2 { Entity E3 extends @E2 { "prop5comment" String prop5 String prop6 "prop6comment" int prop6}}}
ContextMap TestContextMap { contains BC1 }
''';
val expectedResult = '''
ContextMap TestContextMap {
contains BC1
}
BoundedContext BC1 {
Aggregate A1 {
Entity E1 {
String prop1
"prop1Comment"
String prop2
}
enum State1 {
A, B
}
Trait T1 {
}
"State2Comment"
enum State2 {
"AA" A, B
}
"E2Comment"
Entity E2 with@T1 {
package = abc
validate = "validation"
aggregateRoot
"E2Comment"
String prop3
}
Entity E3 {
String prop1
}
Trait T2 {
"prop3Comment"
String prop3
"ref1Comment"
- @E1 ref1
}
BasicType BT1 {
"prop4Comment"
int prop4
- List<@T2> prop5
}
"V1Comment"
ValueObject V1 with@T1 with@T2 {
int intProp
}
}
}
BoundedContext BC2
{
Aggregate A2 {
Entity E3 extends @E2 {
"prop5comment"
String prop5
String prop6
"prop6comment"
int prop6
}
}
}
''';
// given
val model = parseHelper.parse(testInput)
// when, then
assertEquals(expectedResult,
model.serialize(SaveOptions.newBuilder.format().getOptions()))
}

@Test
def void formatsMapLoadedFromAPI() {
val contextMapper = ContextMapperStandaloneSetup.getStandaloneAPI();
val factory = ContextMappingDSLFactory.eINSTANCE;
val tacticdslFactory = TacticdslFactory.eINSTANCE;

val resource = contextMapper.createCML( "target/test-create-cm.cml" );

// given
val model = resource.getContextMappingModel();
val contextMap = factory.createContextMap();
contextMap.setName( "TestContextMap" );
model.setMap( contextMap );

val boundedContext = factory.createBoundedContext();
boundedContext.setName( "TestContext" );
contextMap.getBoundedContexts().add( boundedContext );

val aggregate = factory.createAggregate();
aggregate.setName( "A1" );
model.getBoundedContexts().add( boundedContext );
boundedContext.getAggregates().add( aggregate );

val trait1 = tacticdslFactory.createTrait();
trait1.setName( "T1" );
aggregate.getDomainObjects().add( trait1 );

val entity1 = tacticdslFactory.createEntity();
aggregate.getDomainObjects().add( entity1 );
entity1.setName( "E100" );
entity1.setAggregateRoot( true );

val entity2 = tacticdslFactory.createEntity();
entity2.setExtends( entity1 );
entity2.setName( "E2" );
entity2.getTraits().add( trait1 );
aggregate.getDomainObjects().add( entity2 );

val attribute1 = tacticdslFactory.createAttribute();
attribute1.setName( "prop1" );
attribute1.setType( "String" );
attribute1.doc = "prop1Comment"
entity2.getAttributes().add( attribute1 );

var reference1 = tacticdslFactory.createReference();
reference1.setName( "ref1" );
reference1.setDomainObjectType( entity2 );
entity2.getReferences().add( reference1 );

val entity3 = tacticdslFactory.createEntity();
entity3.setExtends( entity1 );
entity3.setName( "E3" );
aggregate.getDomainObjects().add( entity3 );


val expectedResult = '''
ContextMap TestContextMap {
contains TestContext
}
BoundedContext TestContext {
Aggregate A1 {
Trait T1
Entity E100 {
aggregateRoot
}
Entity E2 extends @E100 with@T1 {
"prop1Comment"
String prop1
- @E2 ref1
}
Entity E3 extends @E100
}
}
'''

// when, then
assertEquals(expectedResult,
model.serialize(SaveOptions.newBuilder.format().getOptions()))
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.contextmapper.dsl;

import com.google.inject.Inject;
import org.contextmapper.tactic.dsl.tacticdsl.Reference;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic;
import org.eclipse.xtext.serializer.tokens.CrossReferenceSerializer;
import org.eclipse.xtext.serializer.tokens.ICrossReferenceSerializer;

public class ContextMapperCrossReferenceSerializer implements ICrossReferenceSerializer {

private static final String REF_AT = "@";
private final ICrossReferenceSerializer delegate;

@Inject
public ContextMapperCrossReferenceSerializer(CrossReferenceSerializer delegate) {
this.delegate = delegate;
}

@Override
public boolean isValid(EObject context, CrossReference crossref, EObject target, INode node,
ISerializationDiagnostic.Acceptor errorAcceptor) {
return delegate.isValid(context, crossref, target, node, errorAcceptor);
}

@Override
public String serializeCrossRef(EObject context, CrossReference crossref, EObject target, INode node,
ISerializationDiagnostic.Acceptor errorAcceptor) {
var serialized = delegate.serializeCrossRef(context, crossref, target, node, errorAcceptor);
if (serialized != null && !serialized.startsWith(REF_AT)) {
if (node == null || !(node.hasPreviousSibling() && node.getPreviousSibling().getText().contains(REF_AT))) {
if (context instanceof Reference) {
serialized = REF_AT + serialized;
} else {
if (crossref.eContainer() instanceof Assignment) {
Assignment assignment = (Assignment) crossref.eContainer();
var feature = assignment.getFeature();
if (feature.equals("traits")) {
serialized = REF_AT + serialized;
}
}
}
}
}
return serialized;
}
}
Original file line number Diff line number Diff line change
@@ -31,4 +31,8 @@ class ContextMappingDSLRuntimeModule extends AbstractContextMappingDSLRuntimeMod
return ImportUriGlobalScopeProvider
}

override configure(com.google.inject.Binder binder) {
super.configure(binder)
binder.bind(org.eclipse.xtext.serializer.tokens.ICrossReferenceSerializer).to(org.contextmapper.dsl.ContextMapperCrossReferenceSerializer);
}
}
Original file line number Diff line number Diff line change
@@ -325,10 +325,12 @@ class ContextMappingDSLFormatter extends TacticDDDLanguageFormatter {

for (domainObject : aggregate.domainObjects) {
domainObject.format
domainObject.prepend[newLine]
}

for (service : aggregate.services) {
service.format
service.prepend[newLine]
}
}

Original file line number Diff line number Diff line change
@@ -576,3 +576,4 @@ terminal REF :

terminal ASC :
('--'|'association');

Original file line number Diff line number Diff line change
@@ -16,144 +16,192 @@
package org.contextmapper.tactic.dsl.formatting2

import org.contextmapper.tactic.dsl.tacticdsl.Attribute
import org.contextmapper.tactic.dsl.tacticdsl.CommandEvent
import org.contextmapper.tactic.dsl.tacticdsl.ComplexType
import org.contextmapper.tactic.dsl.tacticdsl.DomainEvent
import org.contextmapper.tactic.dsl.tacticdsl.DomainObject
import org.contextmapper.tactic.dsl.tacticdsl.DomainObjectOperation
import org.contextmapper.tactic.dsl.tacticdsl.Trait
import org.contextmapper.tactic.dsl.tacticdsl.BasicType
import org.contextmapper.tactic.dsl.tacticdsl.Entity
import org.contextmapper.tactic.dsl.tacticdsl.Parameter
import org.contextmapper.tactic.dsl.tacticdsl.Reference
import org.contextmapper.tactic.dsl.tacticdsl.Service
import org.contextmapper.tactic.dsl.tacticdsl.ValueObject
import org.contextmapper.tactic.dsl.tacticdsl.Enum
import org.eclipse.xtext.formatting2.AbstractFormatter2
import org.eclipse.xtext.formatting2.IFormattableDocument
import org.contextmapper.tactic.dsl.tacticdsl.ServiceOperation
import org.contextmapper.tactic.dsl.tacticdsl.TacticdslPackage
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EStructuralFeature

class TacticDDDLanguageFormatter extends AbstractFormatter2 {

def dispatch void format(Entity entity, extension IFormattableDocument document) {
val EClass clazz = TacticdslPackage.eINSTANCE.getEntity()
val EStructuralFeature docFeature = clazz.getEStructuralFeature(TacticdslPackage.ENTITY__DOC)
entity.regionFor.feature(docFeature).append[newLine]

interior(
entity.regionFor.keyword('{').append[newLine],
entity.regionFor.keyword('}').prepend[newLine].append[newLine]
entity.regionFor.keyword('{').prepend[oneSpace].append[newLine],
entity.regionFor.keyword('}').prepend[newLine]
)[indent]

entity.regionFor.keyword('aggregateRoot').append[newLine]
entity.prepend[newLines = 1]
entity.regionFor.keyword('Entity').prepend[newLine]
entity.regionFor.keyword('aggregateRoot').prepend[newLine]
entity.regionFor.keywords('hint', 'validate', 'belongsTo').forEach[
prepend[newLine]
]
entity.regionFor.keywords('with').forEach[
append[noSpace].prepend[oneSpace]
]
entity.regionFor.keywords('@').forEach[
append[noSpace]
]

for (attribute : entity.attributes) {
attribute.format
attribute.append[newLine]
attribute.prepend[newLine]
}
for (reference : entity.references) {
reference.format
reference.append[newLine]
reference.prepend[newLine]
}
for (operation : entity.operations) {
operation.format
operation.append[newLine]
operation.prepend[newLine]
}
}

def dispatch void format(DomainEvent domainEvent, extension IFormattableDocument document) {
interior(
domainEvent.regionFor.keyword('{').append[newLine],
domainEvent.regionFor.keyword('}').prepend[newLine].append[newLine]
)[indent]

domainEvent.regionFor.keyword('aggregateRoot').append[newLine]
domainEvent.prepend[newLines = 1]
domainEvent.regionFor.keyword('DomainEvent').prepend[newLine]
def dispatch void format(DomainObject domainObject, extension IFormattableDocument document) {
val EClass clazz = TacticdslPackage.eINSTANCE.getDomainObject()
val EStructuralFeature docFeature = clazz.getEStructuralFeature(TacticdslPackage.DOMAIN_OBJECT__DOC)
domainObject.regionFor.feature(docFeature).append[newLine]

for (attribute : domainEvent.attributes) {
attribute.format
attribute.append[newLine]
}
for (reference : domainEvent.references) {
reference.format
reference.append[newLine]
}
for (operation : domainEvent.operations) {
operation.format
operation.append[newLine]
}
}

def dispatch void format(CommandEvent commandEvent, extension IFormattableDocument document) {
interior(
commandEvent.regionFor.keyword('{').append[newLine],
commandEvent.regionFor.keyword('}').prepend[newLine].append[newLine]
interior(
domainObject.regionFor.keyword('{').prepend[oneSpace].append[newLine],
domainObject.regionFor.keyword('}').prepend[newLine]
)[indent]

commandEvent.regionFor.keyword('aggregateRoot').append[newLine]
commandEvent.prepend[newLines = 1]
commandEvent.regionFor.keyword('CommandEvent').prepend[newLine]
domainObject.regionFor.keyword('aggregateRoot').append[newLine]
domainObject.regionFor.keyword('hint').prepend[newLine]
domainObject.regionFor.keywords('with').forEach[
append[noSpace].prepend[oneSpace]
]
domainObject.regionFor.keywords('@').forEach[
append[noSpace]
]

for (attribute : commandEvent.attributes) {
for (attribute : domainObject.attributes) {
attribute.format
attribute.append[newLine]
attribute.prepend[newLine]
}
for (reference : commandEvent.references) {
for (reference : domainObject.references) {
reference.format
reference.append[newLine]
reference.prepend[newLine]
}
for (operation : commandEvent.operations) {
for (operation : domainObject.operations) {
operation.format
operation.append[newLine]
operation.prepend[newLine]
}
}

def dispatch void format(ValueObject valueObject, extension IFormattableDocument document) {

def dispatch void format(Trait trait, extension IFormattableDocument document) {
val EClass clazz = TacticdslPackage.eINSTANCE.getTrait()
val EStructuralFeature docFeature = clazz.getEStructuralFeature(TacticdslPackage.TRAIT__DOC)
trait.regionFor.feature(docFeature).append[newLine]

interior(
valueObject.regionFor.keyword('{').append[newLine],
valueObject.regionFor.keyword('}').prepend[newLine].append[newLine]
trait.regionFor.keyword('{').prepend[oneSpace].append[newLine],
trait.regionFor.keyword('}').prepend[newLine]
)[indent]

valueObject.prepend[newLines = 1]
valueObject.regionFor.keyword('ValueObject').prepend[newLine]
trait.regionFor.keyword('hint').prepend[newLine]

for (attribute : valueObject.attributes) {
for (attribute : trait.attributes) {
attribute.format
attribute.append[newLine]
attribute.prepend[newLine]
}
for (reference : valueObject.references) {
for (reference : trait.references) {
reference.format
reference.append[newLine]
reference.prepend[newLine]
}
for (operation : valueObject.operations) {
for (operation : trait.operations) {
operation.format
operation.append[newLine]
operation.prepend[newLine]
}
}


def dispatch void format(BasicType basicType, extension IFormattableDocument document) {
val EClass clazz = TacticdslPackage.eINSTANCE.getBasicType()
val EStructuralFeature docFeature = clazz.getEStructuralFeature(TacticdslPackage.BASIC_TYPE__DOC)
basicType.regionFor.feature(docFeature).append[newLine]

interior(
basicType.regionFor.keyword('{').prepend[oneSpace].append[newLine],
basicType.regionFor.keyword('}').prepend[newLine]
)[indent]

basicType.regionFor.keyword('hint').prepend[newLine]
basicType.regionFor.keywords('with').forEach[
append[noSpace].prepend[oneSpace]
]
basicType.regionFor.keywords('@').forEach[
append[noSpace]
]

for (attribute : basicType.attributes) {
attribute.format
attribute.prepend[newLine]
}
for (reference : basicType.references) {
reference.format
reference.prepend[newLine]
}
for (operation : basicType.operations) {
operation.format
operation.prepend[newLine]
}
}

def dispatch void format(Enum enumm, extension IFormattableDocument document) {
val EClass clazz = TacticdslPackage.eINSTANCE.getEnum()
val EStructuralFeature docFeature = clazz.getEStructuralFeature(TacticdslPackage.ENUM__DOC)
enumm.regionFor.feature(docFeature).append[newLine]

interior(
enumm.regionFor.keyword('{').append[newLine],
enumm.regionFor.keyword('}').prepend[newLine].append[newLine]
enumm.regionFor.keyword('{').prepend[oneSpace].append[newLine],
enumm.regionFor.keyword('}').prepend[newLine]
)[indent]

enumm.prepend[newLines = 1]
enumm.regionFor.keyword('enum').prepend[newLine]
enumm.regionFor.keywords(',').forEach [
prepend[noSpace]
append[oneSpace]
]

for (attribute : enumm.attributes) {
attribute.format
attribute.append[newLine]
attribute.prepend[newLine]
}
}

def dispatch void format(Attribute attribute, extension IFormattableDocument document) {
val EClass attributeClass = TacticdslPackage.eINSTANCE.getAttribute()
val EStructuralFeature docFeature = attributeClass.getEStructuralFeature(TacticdslPackage.ATTRIBUTE__DOC)

attribute.regionFor.feature(docFeature).append[newLine]
attribute.regionFor.keyword('<').surround[noSpace]
attribute.regionFor.keyword('>').prepend[noSpace]
}

def dispatch void format(Reference reference, extension IFormattableDocument document) {
val EClass referenceClass = TacticdslPackage.eINSTANCE.getReference()
val EStructuralFeature docFeature = referenceClass.getEStructuralFeature(TacticdslPackage.REFERENCE__DOC)

reference.regionFor.feature(docFeature).append[newLine]
reference.regionFor.keyword('<').surround[noSpace]
reference.regionFor.keyword('>').prepend[noSpace]
}

def dispatch void format(DomainObjectOperation operation, extension IFormattableDocument document) {
operation.prepend[newLines = 1]
operation.prepend[newLine]

operation.regionFor.keyword(';').prepend[noSpace]
operation.regionFor.keyword('(').append[noSpace]
@@ -187,11 +235,11 @@ class TacticDDDLanguageFormatter extends AbstractFormatter2 {

def dispatch void format(Service service, extension IFormattableDocument document) {
interior(
service.regionFor.keyword('{').append[newLine],
service.regionFor.keyword('{').prepend[oneSpace].append[newLine],
service.regionFor.keyword('}').prepend[newLine].append[newLine]
)[indent]

service.prepend[newLines = 1]
service.prepend[newLine]
service.regionFor.keyword('Service').prepend[newLine]

for (operation : service.operations) {
@@ -200,7 +248,7 @@ class TacticDDDLanguageFormatter extends AbstractFormatter2 {
}

def dispatch void format(ServiceOperation operation, extension IFormattableDocument document) {
operation.prepend[newLines = 1]
operation.prepend[newLine]

operation.regionFor.keyword(';').prepend[noSpace]
operation.regionFor.keyword('(').append[noSpace]

0 comments on commit 56bf203

Please sign in to comment.