UNPKG

@loopback/cli

Version:
264 lines (228 loc) 7.44 kB
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved. // Node module: @loopback/cli // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT 'use strict'; const ast = require('ts-morph'); const path = require('path'); const tsquery = require('../../lib/ast-helper'); const utils = require('../../lib/utils'); exports.relationType = { belongsTo: 'belongsTo', hasMany: 'hasMany', hasManyThrough: 'hasManyThrough', hasOne: 'hasOne', referencesMany: 'referencesMany', }; class AstLoopBackProject extends ast.Project { constructor() { super({ manipulationSettings: { indentationText: ast.IndentationText.TwoSpaces, insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: false, newLineKind: ast.NewLineKind.LineFeed, quoteKind: ast.QuoteKind.Single, }, }); } } exports.AstLoopBackProject = AstLoopBackProject; exports.getModelPrimaryKeyProperty = async function (fs, modelDir, modelName) { const modelFile = path.join(modelDir, utils.getModelFileName(modelName)); const fileContent = await fs.read(modelFile, {}); return tsquery.getIdFromModel(fileContent); }; exports.getModelPropertyType = function (modelDir, modelName, propertyName) { const project = new this.AstLoopBackProject(); const modelFile = path.join(modelDir, utils.getModelFileName(modelName)); const sf = project.addSourceFileAtPath(modelFile); const co = this.getClassObj(sf, modelName); return this.getPropertyType(co, propertyName); }; exports.addFileToProject = function (project, dir, modelName) { const fileName = path.resolve(dir, utils.getModelFileName(modelName)); return project.addSourceFileAtPath(fileName); }; exports.getClassObj = function (fileName, modelName) { return fileName.getClassOrThrow(modelName); }; exports.getClassConstructor = function (classObj) { return classObj.getConstructors()[0]; }; exports.addExportController = async function ( generator, fileName, controllerClassName, controllerFileName, ) { const project = new this.AstLoopBackProject(); let pFile; const exportDeclaration = { kind: ast.StructureKind.ExportDeclaration, moduleSpecifier: './' + controllerFileName, }; if (generator.fs.exists(fileName)) { pFile = project.addSourceFileAtPath(fileName); // Exported declarations is now a `Map<string, Declaration[]>` const exportedDeclarations = pFile.getExportedDeclarations(); for (const declarations of exportedDeclarations.values()) { for (const declaration of declarations) { if ( ast.Node.isClassDeclaration(declaration) && controllerClassName === declaration.getName() ) { return; } } } pFile.addExportDeclaration(exportDeclaration); } else { pFile = project.createSourceFile(fileName, { statements: [exportDeclaration], }); } await pFile.save(); }; /** * Validate if property exist in class. * * @param {classObj} * @param {propertyName} string * * @returns bool true on success, false on failure. */ exports.doesPropertyExist = function (classObj, propertyName) { return classObj .getProperties() .map(x => x.getName()) .includes(propertyName); }; exports.doesRelationExist = function (classObj, propertyName) { if (this.doesPropertyExist(classObj, propertyName)) { // If the property is decorated by `@property()`, // turn it to be a relational property decorated by `@belongsTo()` const decorators = classObj.getProperty(propertyName).getDecorators(); const hasPropertyDecorator = decorators.length > 0 && decorators[0].getName() === 'property'; // If it's already decorated by a relational decorator, // throw error if (!hasPropertyDecorator) { throw new Error( 'relational property ' + propertyName + ' already exist in the model ' + classObj.getName(), ); } this.deleteProperty(classObj.getProperty(propertyName)); } }; /** * Get property type in class. * * @param {classObj} * @param {propertyName} string * * @returns string */ exports.getPropertyType = function (classObj, propertyName) { return classObj.getProperty(propertyName).getType().getText(); }; /** * Validate if property with specific type exist in class. * * @param {classObj} * @param {propertyName} string * @param {propertyType} string * * @returns bool true on success, false on failure. */ exports.isValidPropertyType = function (classObj, propertyName, propertyType) { return this.getPropertyType(classObj, propertyName) === propertyType; }; exports.doesParameterExist = function (classConstructor, parameterName) { return classConstructor .getParameters() .map(x => x.getName()) .includes(parameterName); }; exports.addForeignKey = function (foreignKey, sourceModelPrimaryKeyType) { return { decorators: [ { name: 'property', arguments: ["{\n type : '" + sourceModelPrimaryKeyType + "',\n}"], }, ], name: foreignKey + '?', type: sourceModelPrimaryKeyType, }; }; exports.addProperty = function (classOBj, property) { classOBj.insertProperty(this.getPropertiesCount(classOBj), property); classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); }; exports.deleteProperty = function (propObj) { propObj.remove(); }; exports.getPropertiesCount = function (classObj) { return classObj.getProperties().length; }; exports.getPropertyStartPos = function (classObj) { return classObj .getChildSyntaxList() .getChildAtIndex(this.getPropertiesCount(classObj) - 1) .getPos(); }; exports.addRequiredImports = function (sourceFile, imports) { for (const currentImport of imports) { this.addCurrentImport(sourceFile, currentImport); } }; exports.getRequiredImports = function (targetModel, relationType, sourceModel) { const requiredImports = [ { name: relationType, module: '@loopback/repository', }, ]; if (sourceModel !== targetModel) { requiredImports.push({ name: targetModel, module: './' + utils.toFileName(targetModel) + '.model', }); } return requiredImports; }; exports.addCurrentImport = function (sourceFile, currentImport) { if (!this.doesModuleExists(sourceFile, currentImport.module)) { sourceFile.addImportDeclaration({ moduleSpecifier: currentImport.module, }); } if (!this.doesImportExistInModule(sourceFile, currentImport)) { sourceFile .getImportDeclarationOrThrow(currentImport.module) .addNamedImport(currentImport.name); } }; exports.doesModuleExists = function (sourceFile, moduleName) { return sourceFile.getImportDeclaration(moduleName); }; exports.doesImportExistInModule = function (sourceFile, currentImport) { let identicalImport; const relevantImports = this.getNamedImportsFromModule( sourceFile, currentImport.module, ); if (relevantImports.length > 0) { identicalImport = relevantImports[0] .getNamedImports() .filter(imp => imp.getName() === currentImport.name); } return identicalImport && identicalImport.length > 0; }; exports.getNamedImportsFromModule = function (sourceFile, moduleName) { const allImports = sourceFile.getImportDeclarations(); return allImports.filter(imp => imp.getModuleSpecifierValue() === moduleName); };