Skip to content

Commit 09b3d15

Browse files
feat(map): Adds map declaration analysis to concerto-analysis (#691)
* feat(maps): export MapDeclaration type definitions Signed-off-by: jonathan.casey <[email protected]> * feat(maps): update JSDoc and type definition Signed-off-by: jonathan.casey <[email protected]> * feat(maps): adds map-declaration comparer Signed-off-by: jonathan.casey <[email protected]> * feat(maps): extends comparer for map declaration Signed-off-by: jonathan.casey <[email protected]> * feat(maps): refactor util function Signed-off-by: jonathan.casey <[email protected]> * feat(maps): adds test coverage Signed-off-by: jonathan.casey <[email protected]> * feat(maps): JSDoc Signed-off-by: jonathan.casey <[email protected]> * feat(maps): adds changelog hash Signed-off-by: jonathan.casey <[email protected]> * feat(maps): add ENABLE_MAP_TYPE env var for test run Signed-off-by: jonathan.casey <[email protected]> * feat(maps): remove improbable branch Signed-off-by: jonathan.casey <[email protected]> * feat(maps): tighten optional chain call Signed-off-by: jonathan.casey <[email protected]> * feat(maps): adds additional assertion to satisfy coveralls Signed-off-by: jonathan.casey <[email protected]> * feat(maps): add more cov Signed-off-by: jonathan.casey <[email protected]> * feat(maps): remove optional chaining Signed-off-by: jonathan.casey <[email protected]> --------- Signed-off-by: jonathan.casey <[email protected]>
1 parent 43c96e0 commit 09b3d15

File tree

19 files changed

+196
-42
lines changed

19 files changed

+196
-42
lines changed

packages/concerto-analysis/src/compare-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,7 @@ export const defaultCompareConfig: CompareConfig = {
6060
'property-validator-added': CompareResult.MAJOR,
6161
'property-validator-removed': CompareResult.PATCH,
6262
'property-validator-changed': CompareResult.MAJOR,
63+
'map-key-type-changed': CompareResult.MAJOR,
64+
'map-value-type-changed': CompareResult.MAJOR
6365
}
6466
};

packages/concerto-analysis/src/compare-utils.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,28 @@
1212
* limitations under the License.
1313
*/
1414

15-
import { ClassDeclaration, EnumValueDeclaration, Field, NumberValidator, Property, RelationshipDeclaration, StringValidator, Validator } from '@accordproject/concerto-core';
15+
import { ClassDeclaration, EnumValueDeclaration, Field, MapDeclaration, NumberValidator, Property, RelationshipDeclaration, StringValidator, Validator } from '@accordproject/concerto-core';
16+
import Declaration from '@accordproject/concerto-core/types/lib/introspect/declaration';
1617

17-
export function getClassDeclarationType(classDeclaration: ClassDeclaration) {
18-
if (classDeclaration.isAsset()) {
19-
return 'asset';
20-
} else if (classDeclaration.isConcept()) {
21-
return 'concept';
22-
} else if (classDeclaration.isEnum()) {
23-
return 'enum';
24-
} else if (classDeclaration.isEvent()) {
25-
return 'event';
26-
} else if (classDeclaration.isParticipant()) {
27-
return 'participant';
28-
} else if (classDeclaration.isTransaction()) {
29-
return 'transaction';
30-
} else {
31-
throw new Error(`unknown class declaration type "${classDeclaration}"`);
18+
export function getDeclarationType(declaration: Declaration) {
19+
if (declaration instanceof ClassDeclaration) {
20+
if (declaration.isAsset()) {
21+
return 'asset';
22+
} else if (declaration.isConcept()) {
23+
return 'concept';
24+
} else if (declaration.isEnum()) {
25+
return 'enum';
26+
} else if (declaration.isEvent()) {
27+
return 'event';
28+
} else if (declaration.isParticipant()) {
29+
return 'participant';
30+
} else if (declaration.isTransaction()) {
31+
return 'transaction';
32+
} else {
33+
throw new Error(`unknown class declaration type "${declaration}"`);
34+
}
35+
} else if (declaration instanceof MapDeclaration) {
36+
return 'map';
3237
}
3338
}
3439

packages/concerto-analysis/src/compare.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* limitations under the License.
1313
*/
1414

15-
import { ClassDeclaration, ModelFile, Property } from '@accordproject/concerto-core';
15+
import { ClassDeclaration, MapDeclaration, ModelFile, Property } from '@accordproject/concerto-core';
1616
import { CompareConfig, CompareResult, defaultCompareConfig } from './compare-config';
1717
import { CompareFinding } from './compare-message';
1818
import { CompareResults } from './compare-results';
@@ -65,6 +65,18 @@ export class Compare {
6565
removed.forEach(a => comparers.forEach(comparer => comparer.compareProperty?.(a, undefined)));
6666
}
6767

68+
private getAddedMapDeclarations(a: MapDeclaration[], b: MapDeclaration[]): MapDeclaration[] {
69+
return b.filter(bItem => !a.some(aItem => bItem.getName() === aItem.getName()));
70+
}
71+
72+
private getMatchingMapDeclarations(a: MapDeclaration[], b: MapDeclaration[]): [a: MapDeclaration, b: MapDeclaration][] {
73+
return a.map(aItem => [aItem, b.find(bItem => aItem.getName() === bItem.getName())]).filter(([, b]) => !!b) as [MapDeclaration, MapDeclaration][];
74+
}
75+
76+
private getRemovedMapDeclarations(a: MapDeclaration[], b: MapDeclaration[]): MapDeclaration[] {
77+
return a.filter(aItem => !b.some(bItem => aItem.getName() === bItem.getName()));
78+
}
79+
6880
private getAddedClassDeclarations(a: ClassDeclaration[], b: ClassDeclaration[]): ClassDeclaration[] {
6981
return b.filter(bItem => !a.some(aItem => bItem.getName() === aItem.getName()));
7082
}
@@ -77,8 +89,21 @@ export class Compare {
7789
return a.filter(aItem => !b.some(bItem => aItem.getName() === bItem.getName()));
7890
}
7991

92+
private compareMapDeclarations(comparers: Comparer[], a: MapDeclaration[], b: MapDeclaration[]) {
93+
const added = this.getAddedMapDeclarations(a, b);
94+
const matching = this.getMatchingMapDeclarations(a, b);
95+
const removed = this.getRemovedMapDeclarations(a, b);
96+
added.forEach(b => comparers.forEach(comparer => comparer.compareMapDeclaration?.(undefined, b)));
97+
matching.forEach(([a, b]) => comparers.forEach(comparer => comparer.compareMapDeclaration?.(a, b)));
98+
removed.forEach(a => comparers.forEach(comparer => comparer.compareMapDeclaration?.(a, undefined)));
99+
}
100+
80101
private compareClassDeclaration(comparers: Comparer[], a: ClassDeclaration, b: ClassDeclaration) {
81102
comparers.forEach(comparer => comparer.compareClassDeclaration?.(a, b));
103+
// MapDeclarations do not contain properties, nothing to compare.
104+
if(a instanceof MapDeclaration || b instanceof MapDeclaration) {
105+
return;
106+
}
82107
this.compareProperties(comparers, a.getOwnProperties(), b.getOwnProperties());
83108
}
84109

@@ -94,6 +119,7 @@ export class Compare {
94119
private compareModelFiles(comparers: Comparer[], a: ModelFile, b: ModelFile) {
95120
comparers.forEach(comparer => comparer.compareModelFiles?.(a, b));
96121
this.compareClassDeclarations(comparers, a.getAllDeclarations(), b.getAllDeclarations());
122+
this.compareMapDeclarations(comparers, a.getMapDeclarations(), b.getMapDeclarations());
97123
}
98124

99125
private buildResults(findings: CompareFinding[]) {

packages/concerto-analysis/src/comparer.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* limitations under the License.
1313
*/
1414

15-
import { ClassDeclaration, ModelFile, Property } from '@accordproject/concerto-core';
15+
import { ClassDeclaration, MapDeclaration, ModelFile, Property } from '@accordproject/concerto-core';
1616
import { CompareContext } from './compare-context';
1717

1818
/**
@@ -34,7 +34,16 @@ export type Comparer = {
3434
compareClassDeclaration?: (a: ClassDeclaration | undefined, b: ClassDeclaration | undefined) => void;
3535

3636
/**
37-
* Called to compare two properties. If a is undefined, but b is defined, then this property was
37+
* Called to compare two map declarations. If a is undefined, but b is defined, then this map declaration was
38+
* created in the second model. If a is defined, but b is undefined, then this map declaration was removed in the
39+
* second model.
40+
* @param a The first map declaration for comparision, or undefined if it is undefined in the first model.
41+
* @param b The second map declaration for comparision, or undefined if it is undefined in the second model.
42+
*/
43+
compareMapDeclaration?: (a: MapDeclaration | undefined, b: MapDeclaration | undefined) => void;
44+
45+
/**
46+
* Called to compare two properties. If a is undefined, but b is definecd, then this property was
3847
* created in the second model. If a is defined, but b is undefined, then this property was removed in the
3948
* second model.
4049
* @param a The first property for comparision, or undefined if it is undefined in the first model.

packages/concerto-analysis/src/comparers/class-declarations.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@
1212
* limitations under the License.
1313
*/
1414

15-
import { getClassDeclarationType } from '../compare-utils';
15+
import { getDeclarationType } from '../compare-utils';
1616
import { ComparerFactory } from '../comparer';
1717

18+
// todo rename to declaration.ts , rename all classDeclaration -> declaration
1819
const classDeclarationAdded: ComparerFactory = (context) => ({
1920
compareClassDeclaration: (a, b) => {
2021
if (a || !b) {
2122
return;
2223
}
23-
const type = getClassDeclarationType(b);
24+
const type = getDeclarationType(b);
2425
context.report({
2526
key: 'class-declaration-added',
2627
message: `The ${type} "${b.getName()}" was added`,
@@ -34,7 +35,7 @@ const classDeclarationRemoved: ComparerFactory = (context) => ({
3435
if (!a || b) {
3536
return;
3637
}
37-
const type = getClassDeclarationType(a);
38+
const type = getDeclarationType(a);
3839
context.report({
3940
key: 'class-declaration-removed',
4041
message: `The ${type} "${a.getName()}" was removed`,
@@ -48,8 +49,8 @@ const classDeclarationTypeChanged: ComparerFactory = (context) => ({
4849
if (!a || !b) {
4950
return;
5051
}
51-
const aType = getClassDeclarationType(a);
52-
const bType = getClassDeclarationType(b);
52+
const aType = getDeclarationType(a);
53+
const bType = getDeclarationType(b);
5354
if (aType === bType) {
5455
return;
5556
}

packages/concerto-analysis/src/comparers/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
import { classDeclarationComparerFactories } from './class-declarations';
1616
import { modelFileComparerFactories } from './model-files';
1717
import { propertyComparerFactories } from './properties';
18+
import { mapDeclarationComparerFactories } from './map-declarations';
1819

1920
export const comparerFactories = [
2021
...classDeclarationComparerFactories,
2122
...propertyComparerFactories,
22-
...modelFileComparerFactories
23+
...modelFileComparerFactories,
24+
...mapDeclarationComparerFactories
2325
];
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
import { ComparerFactory } from '../comparer';
16+
17+
const mapDeclarationTypeChanged: ComparerFactory = (context) => ({
18+
compareMapDeclaration: (a, b) => {
19+
20+
if (!a || !b) {
21+
return;
22+
}
23+
24+
if(a.getKey().getType() !== b.getKey().getType()) {
25+
context.report({
26+
key: 'map-key-type-changed',
27+
message: `The map key type was changed to "${b.getKey().getType()}"`,
28+
element: b
29+
});
30+
}
31+
32+
if(a.getValue().getType() !== b.getValue().getType()) {
33+
context.report({
34+
key: 'map-value-type-changed',
35+
message: `The map value type was changed to "${b.getValue().getType()}"`,
36+
element: b
37+
});
38+
}
39+
},
40+
});
41+
42+
export const mapDeclarationComparerFactories = [mapDeclarationTypeChanged];

packages/concerto-analysis/src/comparers/properties.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414

1515
import { EnumValueDeclaration, Field, ModelUtil } from '@accordproject/concerto-core';
1616
import * as semver from 'semver';
17-
import { getClassDeclarationType, getPropertyType, getValidatorType } from '../compare-utils';
17+
import { getDeclarationType, getPropertyType, getValidatorType } from '../compare-utils';
1818
import { ComparerFactory } from '../comparer';
1919

2020
const propertyAdded: ComparerFactory = (context) => ({
2121
compareProperty: (a, b) => {
2222
if (a || !b) {
2323
return;
2424
}
25-
const classDeclarationType = getClassDeclarationType(b.getParent());
25+
const classDeclarationType = getDeclarationType(b.getParent());
2626
if (b instanceof EnumValueDeclaration) {
2727
context.report({
2828
key: 'enum-value-added',
@@ -58,7 +58,7 @@ const propertyRemoved: ComparerFactory = (context) => ({
5858
if (!a || b) {
5959
return;
6060
}
61-
const classDeclarationType = getClassDeclarationType(a.getParent());
61+
const classDeclarationType = getDeclarationType(a.getParent());
6262
if (a instanceof EnumValueDeclaration) {
6363
context.report({
6464
key: 'enum-value-removed',
@@ -96,7 +96,7 @@ const propertyTypeChanged: ComparerFactory = (context) => ({
9696
}
9797
const aType = getPropertyType(a);
9898
const bType = getPropertyType(b);
99-
const classDeclarationType = getClassDeclarationType(a.getParent());
99+
const classDeclarationType = getDeclarationType(a.getParent());
100100
if (aType !== bType) {
101101
context.report({
102102
key: 'property-type-changed',
@@ -197,7 +197,7 @@ const propertyValidatorChanged: ComparerFactory = (context) => ({
197197
}
198198
const aValidator = a.getValidator();
199199
const bValidator = b.getValidator();
200-
const classDeclarationType = getClassDeclarationType(a.getParent());
200+
const classDeclarationType = getDeclarationType(a.getParent());
201201
if (!aValidator && !bValidator) {
202202
return;
203203
} else if (!aValidator && bValidator) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
3+
map Thing {
4+
o String
5+
o String
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
3+
map Thing {
4+
o DateTime
5+
o String
6+
}

0 commit comments

Comments
 (0)