diff --git a/lib/codegen/fromcto/typescript/typescriptvisitor.js b/lib/codegen/fromcto/typescript/typescriptvisitor.js index 0d79460f..5ffd94d4 100644 --- a/lib/codegen/fromcto/typescript/typescriptvisitor.js +++ b/lib/codegen/fromcto/typescript/typescriptvisitor.js @@ -89,6 +89,15 @@ class TypescriptVisitor { // Compute the types we need to import (based on all the types of the properites // as well as all the super types) for all the classes in this model file parameters.fileWriter.writeLine(0, '\n// imports'); + const aliasedTypesMap = new Map(); + modelFile.imports.forEach((imp) => { + if (imp.$class === 'concerto.metamodel@1.0.0.ImportTypes' && imp.aliasedTypes) { + imp.aliasedTypes.forEach(type => { + aliasedTypesMap.set(`${imp.namespace}.I${type.name}`, `I${type.aliasedName}`); + }); + } + }); + const properties = new Map(); modelFile.getAllDeclarations() .filter(declaration => !declaration.isScalarDeclaration?.()) @@ -138,17 +147,25 @@ class TypescriptVisitor { }); - modelFile.getImports().map(importString => ModelUtil.getNamespace(importString)).filter(namespace => namespace !== modelFile.getNamespace()) // Skip own namespace. - .filter((v, i, a) => a.indexOf(v) === i) // Remove any duplicates from direct imports - .forEach(namespace => { - const propertyTypeNames = properties.get(namespace); - if (propertyTypeNames) { - const csvPropertyTypeNames = Array.from(propertyTypeNames).join(); - parameters.fileWriter.writeLine(0, `import {${csvPropertyTypeNames}} from './${namespace}';`); - } - }); + const uniqueNamespaces = new Set( + modelFile.getImports() + .map(importString => ModelUtil.getNamespace(importString)) + .filter(namespace => namespace !== modelFile.getNamespace()) + ); + uniqueNamespaces.forEach(namespace => { + const propertyTypeNames = properties.get(namespace); + if (propertyTypeNames) { + const csvPropertyTypeNames = Array.from(propertyTypeNames).map(type => { + const fqnType = `${namespace}.${type}`; + return aliasedTypesMap.has(fqnType) ? `${type} as ${aliasedTypesMap.get(fqnType)}` : type; + }).join(','); + + parameters.fileWriter.writeLine(0, `import {${csvPropertyTypeNames}} from './${namespace}';`); + } + }); parameters.fileWriter.writeLine(0, '\n// interfaces'); + parameters.aliasedTypesMap = aliasedTypesMap; modelFile.getAllDeclarations() .filter(declaration => !declaration.isScalarDeclaration?.()).forEach((decl) => { decl.accept(this, parameters); @@ -187,13 +204,15 @@ class TypescriptVisitor { */ visitClassDeclaration(classDeclaration, parameters) { - let superType = ' '; - if (classDeclaration.getSuperType()) { - superType = ` extends I${ModelUtil.getShortName(classDeclaration.getSuperType())} `; + let superType = ''; + const type = classDeclaration.getSuperType(); + if (type) { + const namespace = ModelUtil.getNamespace(type); + const shortName = `I${ModelUtil.getShortName(type)}`; + const fullType = `${namespace}.${shortName}`; + superType = ` extends ${parameters.aliasedTypesMap.get(fullType) || shortName}`; } - - parameters.fileWriter.writeLine(0, 'export interface I' + classDeclaration.getName() + superType + '{'); - + parameters.fileWriter.writeLine(0, `export interface I${classDeclaration.getName()}${superType} {`); if(!classDeclaration.getSuperType()) { parameters.fileWriter.writeLine(1, '$class: string;'); } diff --git a/test/codegen/fromcto/typescript/typescriptvisitor.js b/test/codegen/fromcto/typescript/typescriptvisitor.js index 26fbcb79..39fe80b9 100644 --- a/test/codegen/fromcto/typescript/typescriptvisitor.js +++ b/test/codegen/fromcto/typescript/typescriptvisitor.js @@ -232,6 +232,7 @@ describe('TypescriptVisitor', function () { 'super.Property3', 'super.Parent' ]); + mockModelFile.imports= []; typescriptVisitor.visitModelFile(mockModelFile, param); @@ -302,7 +303,7 @@ describe('TypescriptVisitor', function () { 'org.org2.Import1' ]); mockModelFile.getModelManager.returns(mockModelManager); - + mockModelFile.imports= []; typescriptVisitor.visitModelFile(mockModelFile, param); param.fileWriter.openFile.withArgs('org.acme.ts').calledOnce.should.be.ok; @@ -358,7 +359,7 @@ describe('TypescriptVisitor', function () { ]); mockModelFile.getImports.returns([]); mockModelFile.getModelManager.returns(mockModelManager); - + mockModelFile.imports = []; typescriptVisitor.visitModelFile(mockModelFile, param); param.fileWriter.openFile.withArgs('org.acme.ts').calledOnce.should.be.ok; @@ -373,6 +374,71 @@ describe('TypescriptVisitor', function () { acceptSpy.withArgs(typescriptVisitor, param).calledOnce.should.be.ok; }); + + it('should write lines for the aliased imports from other namespace ', () => { + + let property1 = { + isPrimitive: () => { + return false; + }, + getFullyQualifiedTypeName: () => { + return 'org.test.basic.file'; + } + }; + + let property2 = { + isPrimitive: () => { + return false; + }, + getFullyQualifiedTypeName: () => { + return 'org.test.complex.file'; + } + }; + + let mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + + mockClassDeclaration.isClassDeclaration.returns(true); + mockClassDeclaration.getProperties.returns([property1, property2]); + mockClassDeclaration.getNamespace.returns('org.test.collection'); + mockClassDeclaration.getName.returns('folder'); + + mockClassDeclaration.getDirectSubclasses.returns([]); + + let mockModelFile = sinon.createStubInstance(ModelFile); + mockModelFile.getNamespace.returns('org.test.collection'); + mockModelFile.imports = [ + { + '$class': 'concerto.metamodel@1.0.0.ImportTypes', + types: ['document', 'file'], + namespace: 'org.test.basic', + aliasedTypes: [ + { + name: 'file', + aliasedName: 'f' + } + ] + }, + { + '$class': 'concerto.metamodel@1.0.0.ImportTypes', + types: ['file'], + namespace: 'org.test.complex', + } + ]; + + mockModelFile.getAllDeclarations.returns([mockClassDeclaration]); + mockModelFile.getImports.returns(['org.test.basic.file', 'org.test.basic.document', 'org.test.complex.file']); + typescriptVisitor.visitModelFile(mockModelFile,param); + + param.fileWriter.writeLine.callCount.should.deep.equal(6); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, '/* eslint-disable @typescript-eslint/no-empty-interface */']); + param.fileWriter.writeLine.getCall(1).args.should.deep.equal([0, '// Generated code for namespace: org.test.collection']); + param.fileWriter.writeLine.getCall(2).args.should.deep.equal([0, '\n// imports']); + param.fileWriter.writeLine.getCall(3).args.should.deep.equal([0, 'import {Ifile as If} from \'./org.test.basic\';']); + param.fileWriter.writeLine.getCall(4).args.should.deep.equal([0, 'import {Ifile} from \'./org.test.complex\';']); + param.fileWriter.writeLine.getCall(5).args.should.deep.equal([0, '\n// interfaces']); + param.fileWriter.closeFile.calledOnce.should.be.ok; + + }); }); describe('visitEnumDeclaration', () => { @@ -444,7 +510,7 @@ describe('TypescriptVisitor', function () { mockClassDeclaration.getName.returns('Bob'); mockClassDeclaration.isAbstract.returns(true); mockClassDeclaration.getSuperType.returns('org.acme.Person'); - + param.aliasedTypesMap=new Map(); typescriptVisitor.visitClassDeclaration(mockClassDeclaration, param); param.fileWriter.writeLine.callCount.should.deep.equal(2); @@ -512,6 +578,34 @@ describe('TypescriptVisitor', function () { param.fileWriter.writeLine.getCall(1).args.should.deep.equal([1, '$class: string;']); param.fileWriter.writeLine.getCall(2).args.should.deep.equal([0, '}\n']); }); + + it('should write lines for the extending class declaration using aliased types ', () => { + + let property = { + isPrimitive: () => { + return false; + }, + getFullyQualifiedTypeName: () => { + return 'org.test.basic.file'; + } + }; + + let mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + + mockClassDeclaration.isClassDeclaration.returns(true); + mockClassDeclaration.getProperties.returns([property]); + mockClassDeclaration.getNamespace.returns('org.test.collection'); + mockClassDeclaration.getName.returns('bigFile'); + mockClassDeclaration.getSuperType.returns('org.basic.file'); + mockClassDeclaration.isScalarDeclaration.returns(false); + mockClassDeclaration.getDirectSubclasses.returns([]); + mockClassDeclaration.getOwnProperties.returns([]); + param.aliasedTypesMap = new Map([['org.basic.Ifile', 'If']]); + typescriptVisitor.visitClassDeclaration(mockClassDeclaration,param); + + param.fileWriter.writeLine.callCount.should.deep.equal(2); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, 'export interface IbigFile extends If {']); + }); }); describe('visitField', () => {