From 399df62f69d212b1cedefd8dc504032aeb6031eb Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Wed, 30 Aug 2017 15:05:25 -0400 Subject: [PATCH] 1756 - Fix TypeScript Namespace Import Generation (#1757) * Fix copy and paste error in TypeScript generator. * TypeScript Code Gen - Import property types that are imported from other cto files. * TypeScript Code Gen - Code cleanup. * TypeScript Code Gen - Fix eslint errors. * TypeScript Code Gen - Remove spaces around imports since they break the automated tests. * TypeScript Code Gen - Add missing semicolon from import statement. * TypeScript Code Gen - Add unit test to test TypeScript import generation across CTO files. * TypeScript Code Gen - Add test for the output of the import statement that should be generated. * TypeScript Code Gen - Remove previous tests based on pull request request. --- .../fromcto/typescript/typescriptvisitor.js | 40 ++++++++++++++++--- .../test/codegen/typescriptvisitor.js | 40 ++++++++++++------- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/packages/composer-common/lib/codegen/fromcto/typescript/typescriptvisitor.js b/packages/composer-common/lib/codegen/fromcto/typescript/typescriptvisitor.js index f2180aaa8f..a7572388be 100644 --- a/packages/composer-common/lib/codegen/fromcto/typescript/typescriptvisitor.js +++ b/packages/composer-common/lib/codegen/fromcto/typescript/typescriptvisitor.js @@ -27,7 +27,7 @@ const FunctionDeclaration = require('../../../introspect/functiondeclaration'); const util = require('util'); /** - * Convert the contents of a ModelManager to Go Lang code. + * Convert the contents of a ModelManager to TypeScript code. * All generated code is placed into the 'main' package. Set a * fileWriter property (instance of FileWriter) on the parameters * object to control where the generated code is written to disk. @@ -108,12 +108,42 @@ class TypescriptVisitor { // so that they can be extended if( !modelFile.isSystemModelFile() ) { const systemTypes = modelFile.getModelManager().getSystemTypes(); + systemTypes.forEach(systemType => + parameters.fileWriter.writeLine(0, `import {${systemType.getName()}} from './org.hyperledger.composer.system';`)); + } - for(let n=0; n < systemTypes.length; n++) { - const systemType = systemTypes[n]; - parameters.fileWriter.writeLine(0, 'import {' + systemType.getName() + '} from \'./org.hyperledger.composer.system\';'); + // Import property types that are imported from other cto files. + const dot = '.'; + const properties = new Map(); + modelFile.getAllDeclarations() + .filter(v => !v.isEnum()) + .forEach(classDeclaration => classDeclaration.getProperties().forEach(property => { + if(!property.isPrimitive()){ + const fullyQualifiedTypeName = property.getFullyQualifiedTypeName(); + const lastIndexOfDot = fullyQualifiedTypeName.lastIndexOf(dot); + const propertyNamespace = fullyQualifiedTypeName.substring(0, lastIndexOfDot); + const propertyTypeName = fullyQualifiedTypeName.substring(lastIndexOfDot + 1); + if(!properties.has(propertyNamespace)) { + properties.set(propertyNamespace, new Set()); + } + properties.get(propertyNamespace).add(propertyTypeName); } - } + })); + + modelFile.getImports().map(importString => { + const lastIndexOfDot = importString.lastIndexOf(dot); + const namespace = importString.substring(0, lastIndexOfDot); + return namespace; + }).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}';`); + } + }); + parameters.fileWriter.writeLine(0, '// export namespace ' + modelFile.getNamespace() + '{'); modelFile.getAllDeclarations().forEach((decl) => { diff --git a/packages/composer-common/test/codegen/typescriptvisitor.js b/packages/composer-common/test/codegen/typescriptvisitor.js index 262b2bcbc7..f6ddf24ef0 100644 --- a/packages/composer-common/test/codegen/typescriptvisitor.js +++ b/packages/composer-common/test/codegen/typescriptvisitor.js @@ -23,6 +23,22 @@ const fs = require('fs'); const path = require('path'); const sinon = require('sinon'); +const initSampleNetworkModel = (mockFileWriter) => { + const carleaseModel = fs.readFileSync(path.resolve(__dirname, '../data/model/carlease.cto'), 'utf8'); + const concertoModel = fs.readFileSync(path.resolve(__dirname, '../data/model/concerto.cto'), 'utf8'); + + // create and populate the ModelManager with a model file + let modelManager = new ModelManager(); + modelManager.should.not.be.null; + modelManager.clearModelFiles(); + modelManager.addModelFiles([carleaseModel,concertoModel], ['carlease.cto', 'concerto.cto']); + + let visitor = new TypescriptVisitor(); + let parameters = {}; + parameters.fileWriter = mockFileWriter; + modelManager.accept(visitor, parameters); +}; + describe('TypescriptVisitor', function(){ let mockFileWriter; @@ -33,24 +49,18 @@ describe('TypescriptVisitor', function(){ describe('#visit', function() { it('should generate Typescript code', function() { + initSampleNetworkModel(mockFileWriter); - const carleaseModel = fs.readFileSync(path.resolve(__dirname, '../data/model/carlease.cto'), 'utf8'); - const concertoModel = fs.readFileSync(path.resolve(__dirname, '../data/model/concerto.cto'), 'utf8'); - - // create and populate the ModelManager with a model file - let modelManager = new ModelManager(); - modelManager.should.not.be.null; - modelManager.clearModelFiles(); - modelManager.addModelFiles([carleaseModel,concertoModel], ['carlease.cto', 'concerto.cto']); - - let visitor = new TypescriptVisitor(); - let parameters = {}; - parameters.fileWriter = mockFileWriter; - modelManager.accept(visitor, parameters); - - // check 3 files where generated + // check 2 files where generated sinon.assert.calledWith(mockFileWriter.openFile, 'concerto.ts'); sinon.assert.calledWith(mockFileWriter.openFile, 'org.acme.ts'); }); + + it('should generate an import statement referencing the imported namespace from a separate file', function() { + initSampleNetworkModel(mockFileWriter); + + // check import was generated linking to the other file/namespace + sinon.assert.calledWith(mockFileWriter.writeLine, 0 , 'import {MyParticipant} from \'./concerto\';'); + }); }); });