diff --git a/packages/concerto-analysis/src/compare-config.ts b/packages/concerto-analysis/src/compare-config.ts index 75c725e91..511f660dc 100644 --- a/packages/concerto-analysis/src/compare-config.ts +++ b/packages/concerto-analysis/src/compare-config.ts @@ -57,6 +57,7 @@ export const defaultCompareConfig: CompareConfig = { 'enum-value-added': CompareResult.PATCH, 'enum-value-removed': CompareResult.MAJOR, 'property-type-changed': CompareResult.MAJOR, + 'property-type-aliased':CompareResult.PATCH, 'property-validator-added': CompareResult.MAJOR, 'property-validator-removed': CompareResult.PATCH, 'property-validator-changed': CompareResult.MAJOR, diff --git a/packages/concerto-analysis/src/comparers/properties.ts b/packages/concerto-analysis/src/comparers/properties.ts index 753bff6bb..107d44105 100644 --- a/packages/concerto-analysis/src/comparers/properties.ts +++ b/packages/concerto-analysis/src/comparers/properties.ts @@ -173,9 +173,7 @@ const propertyTypeChanged: ComparerFactory = (context) => ({ // return; // } const versionDiff = semver.diff(aTypeVersion, bTypeVersion); - if (!versionDiff) { - return; - } else if (versionDiff === 'major' || versionDiff === 'premajor') { + if (versionDiff === 'major' || versionDiff === 'premajor') { context.report({ key: 'property-type-changed', message: `The ${aType} "${a.getName()}" in the ${classDeclarationType} "${a.getParent().getName()}" changed type from "${aFQTN}" to "${bFQTN}" (type version incompatible)`, @@ -183,6 +181,16 @@ const propertyTypeChanged: ComparerFactory = (context) => ({ }); return; } + // Up to this point, 'a' and 'b' have identical fully qualified type names with matching major and premajor versions. + // Next, we verify whether their types differ locally, which would indicate aliasing. + if (!versionDiff && (a.getType() !== b.getType())) { + context.report({ + key: 'property-type-aliased', + message: `The local type name for "${a.getName()}" in the ${classDeclarationType} "${a.getParent().getName()}" is changed from ${a.getType()} to ${b.getType()}`, + element: a + }); + return; + } }, }); diff --git a/packages/concerto-analysis/test/fixtures/field-local-type-change-a.cto b/packages/concerto-analysis/test/fixtures/field-local-type-change-a.cto new file mode 100644 index 000000000..591d49c4c --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/field-local-type-change-a.cto @@ -0,0 +1,7 @@ +namespace org.accordproject.concerto.test@1.2.3 + +import org.accordproject.concerto.test.other@2.3.4.{ Bar } + +concept Thing { + o Bar bar +} diff --git a/packages/concerto-analysis/test/fixtures/field-local-type-change-b.cto b/packages/concerto-analysis/test/fixtures/field-local-type-change-b.cto new file mode 100644 index 000000000..81ca51f46 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/field-local-type-change-b.cto @@ -0,0 +1,7 @@ +namespace org.accordproject.concerto.test@1.2.3 + +import org.accordproject.concerto.test.other@2.3.4.{ Bar as b } + +concept Thing { + o b bar +} diff --git a/packages/concerto-analysis/test/unit/compare-config.test.ts b/packages/concerto-analysis/test/unit/compare-config.test.ts index d37fcba63..ca9115d26 100644 --- a/packages/concerto-analysis/test/unit/compare-config.test.ts +++ b/packages/concerto-analysis/test/unit/compare-config.test.ts @@ -16,7 +16,7 @@ describe('CompareConfigBuilder', () => { const actual = builder.default().build(); expect(actual.comparerFactories.length).toEqual(11); - expect(Object.keys(actual.rules).length).toEqual(20); + expect(Object.keys(actual.rules).length).toEqual(21); expect(actual.rules['class-declaration-added']).toEqual(CompareResult.MINOR); expect(actual.rules['optional-property-added']).toEqual(CompareResult.PATCH); expect(actual.rules['map-value-type-changed']).toEqual(CompareResult.MAJOR); @@ -37,7 +37,7 @@ describe('CompareConfigBuilder', () => { const actual = builder.default().extend(toExtend).build(); expect(actual.comparerFactories.length).toEqual(12); - expect(Object.keys(actual.rules).length).toEqual(21); + expect(Object.keys(actual.rules).length).toEqual(22); expect(actual.rules['a-new-rule']).toEqual(CompareResult.MAJOR); }); @@ -47,7 +47,7 @@ describe('CompareConfigBuilder', () => { const actual = builder.default().addComparerFactory(() => ({})).build(); expect(actual.comparerFactories.length).toEqual(12); - expect(Object.keys(actual.rules).length).toEqual(20); + expect(Object.keys(actual.rules).length).toEqual(21); }); it('Should add a new rule', () => { @@ -56,7 +56,7 @@ describe('CompareConfigBuilder', () => { const actual = builder.default().addRule('a-new-rule', CompareResult.MAJOR).build(); expect(actual.comparerFactories.length).toEqual(11); - expect(Object.keys(actual.rules).length).toEqual(21); + expect(Object.keys(actual.rules).length).toEqual(22); expect(actual.rules['a-new-rule']).toEqual(CompareResult.MAJOR); }); @@ -66,7 +66,7 @@ describe('CompareConfigBuilder', () => { const actual = builder.default().removeRule('optional-property-added').build(); expect(actual.comparerFactories.length).toEqual(11); - expect(Object.keys(actual.rules).length).toEqual(19); + expect(Object.keys(actual.rules).length).toEqual(20); expect(actual.rules['optional-property-added']).toBeFalsy(); }); diff --git a/packages/concerto-analysis/test/unit/compare.test.ts b/packages/concerto-analysis/test/unit/compare.test.ts index 670fbfe7c..e694c2826 100644 --- a/packages/concerto-analysis/test/unit/compare.test.ts +++ b/packages/concerto-analysis/test/unit/compare.test.ts @@ -15,8 +15,9 @@ async function getModelFile(modelManager: ModelManager, fileName: string) { async function getModelFiles( aFileName: string, bFileName: string, + importAliasing = false ): Promise<[a: ModelFile, b: ModelFile]> { - const modelManager = new ModelManager({ strict: true }); + const modelManager = new ModelManager({ strict: true, importAliasing: importAliasing }); const a = await getModelFile(modelManager, aFileName); const b = await getModelFile(modelManager, bFileName); return [a, b]; @@ -255,6 +256,18 @@ test('should detect an array changing to a property', async () => { expect(results.result).toBe(CompareResult.MAJOR); }); +test('should detect a field local type name change', async () => { + const [a, b] = await getModelFiles('field-local-type-change-a.cto', 'field-local-type-change-b.cto',true); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'property-type-aliased', + message: 'The local type name for "bar" in the concept "Thing" is changed from Bar to b' + }) + ])); + expect(results.result).toBe(CompareResult.PATCH); +}); + test('should detect a map key type changing from x to y', async () => { process.env.ENABLE_MAP_TYPE = 'true'; // TODO Remove on release of MapType const [a, b] = await getModelFiles('map-added.cto', 'map-changed-key.cto'); diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 0e73ce1a9..9c87c9060 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -334,7 +334,7 @@ class ModelLoader { + ModelManager[] loadModelManagerFromModelFiles(object[],string[],object,boolean?,boolean?,number?) } class ModelManager extends BaseModelManager { - + void constructor(object?,boolean?,Object?,boolean?) + + void constructor(object?,boolean?,Object?,boolean?,boolean?) + ModelFile addCTOModel(string,string?,boolean?) throws IllegalModelException } + object getRootModel() diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index a3df38a6a..c0bebf00b 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,6 +24,9 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 3.17.5 {9bd69f9522c14a99a085f077e12ac4b2} 2024-08-29 +- importAliasing added to ModelManager parameters + Version 3.17.4 {8cb49c7092c48b568da3b0a3eeab2777} 2024-08-01 - Added api getImportedType() in modeFile diff --git a/packages/concerto-core/lib/basemodelmanager.js b/packages/concerto-core/lib/basemodelmanager.js index 75578388b..037446a6e 100644 --- a/packages/concerto-core/lib/basemodelmanager.js +++ b/packages/concerto-core/lib/basemodelmanager.js @@ -98,8 +98,7 @@ class BaseModelManager { // TODO Remove on release of MapType // Supports both env var and property based flag this.enableMapType = !!options?.enableMapType; - this.importAliasing = !!options?.importAliasing; - + this.importAliasing = process?.env?.IMPORT_ALIASING === 'true' || !!options?.importAliasing; // Cache a copy of the Metamodel ModelFile for use when validating the structure of ModelFiles later. this.metamodelModelFile = new ModelFile(this, MetaModelUtil.metaModelAst, undefined, MetaModelNamespace); diff --git a/packages/concerto-core/lib/modelmanager.js b/packages/concerto-core/lib/modelmanager.js index 05d7c14a7..ddedcfb4c 100644 --- a/packages/concerto-core/lib/modelmanager.js +++ b/packages/concerto-core/lib/modelmanager.js @@ -60,6 +60,7 @@ class ModelManager extends BaseModelManager { * @param {boolean} [options.strict] - require versioned namespaces and imports * @param {Object} [options.regExp] - An alternative regular expression engine. * @param {boolean} [options.enableMapType] - When true, the Concerto Map Type feature is enabled + * @param {boolean} [options.importAliasing] - When true, the Concerto Map Type feature is enabled */ constructor(options) { super(options, ctoProcessFile(options)); diff --git a/packages/concerto-core/types/lib/modelmanager.d.ts b/packages/concerto-core/types/lib/modelmanager.d.ts index 8a5d1e81a..051d8d4dc 100644 --- a/packages/concerto-core/types/lib/modelmanager.d.ts +++ b/packages/concerto-core/types/lib/modelmanager.d.ts @@ -20,11 +20,13 @@ declare class ModelManager extends BaseModelManager { * @param {boolean} [options.strict] - require versioned namespaces and imports * @param {Object} [options.regExp] - An alternative regular expression engine. * @param {boolean} [options.enableMapType] - When true, the Concerto Map Type feature is enabled + * @param {boolean} [options.importAliasing] - When true, the Concerto Map Type feature is enabled */ constructor(options?: { strict?: boolean; regExp?: any; enableMapType?: boolean; + importAliasing?: boolean; }); /** * Adds a model in CTO format to the ModelManager.