UNPKG

@nestjs/graphql

Version:

Nest - modern, fast, powerful node.js web framework (@graphql)

181 lines (180 loc) 9.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ModelClassVisitor = void 0; const lodash_1 = require("lodash"); const ts = require("typescript"); const decorators_1 = require("../../decorators"); const plugin_constants_1 = require("../plugin-constants"); const ast_utils_1 = require("../utils/ast-utils"); const plugin_utils_1 = require("../utils/plugin-utils"); const metadataHostMap = new Map(); const importsToAddPerFile = new Map(); class ModelClassVisitor { visit(sourceFile, ctx, program, pluginOptions) { const typeChecker = program.getTypeChecker(); const visitNode = (node) => { if (ts.isClassDeclaration(node)) { this.clearMetadataOnRestart(node); node = ts.visitEachChild(node, visitNode, ctx); return this.addMetadataFactory(node); } else if (ts.isPropertyDeclaration(node)) { const decorators = node.decorators; const hideField = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)([decorators_1.HideField.name], decorators); if (hideField) { return node; } const isPropertyStatic = (node.modifiers || []).some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword); if (isPropertyStatic) { return node; } try { this.inspectPropertyDeclaration(node, typeChecker, sourceFile.fileName, sourceFile, pluginOptions); } catch (err) { return node; } return node; } else if (ts.isSourceFile(node)) { const visitedNode = ts.visitEachChild(node, visitNode, ctx); const importsToAdd = importsToAddPerFile.get(node.fileName); if (!importsToAdd) { return visitedNode; } return this.updateImports(visitedNode, Array.from(importsToAdd)); } return ts.visitEachChild(node, visitNode, ctx); }; return ts.visitNode(sourceFile, visitNode); } clearMetadataOnRestart(node) { const classMetadata = this.getClassMetadata(node); if (classMetadata) { metadataHostMap.delete(node.name.getText()); } } addMetadataFactory(node) { const classMutableNode = ts.getMutableClone(node); const classMetadata = this.getClassMetadata(node); const returnValue = classMetadata ? ts.createObjectLiteral(Object.keys(classMetadata).map((key) => ts.createPropertyAssignment(ts.createIdentifier(key), classMetadata[key]))) : ts.createObjectLiteral([], false); const method = ts.createMethod(undefined, [ts.createModifier(ts.SyntaxKind.StaticKeyword)], undefined, ts.createIdentifier(plugin_constants_1.METADATA_FACTORY_NAME), undefined, undefined, [], undefined, ts.createBlock([ts.createReturn(returnValue)], true)); classMutableNode.members = ts.createNodeArray([ ...classMutableNode.members, method, ]); return classMutableNode; } inspectPropertyDeclaration(compilerNode, typeChecker, hostFilename, sourceFile, pluginOptions) { const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(compilerNode, typeChecker, ts.createNodeArray(), hostFilename, sourceFile, pluginOptions); this.addClassMetadata(compilerNode, objectLiteralExpr, sourceFile); } createDecoratorObjectLiteralExpr(node, typeChecker, existingProperties = ts.createNodeArray(), hostFilename = '', sourceFile, pluginOptions) { const type = typeChecker.getTypeAtLocation(node); const isNullable = !!node.questionToken || (0, ast_utils_1.isNull)(type) || (0, ast_utils_1.isUndefined)(type); const properties = [ ...existingProperties, !(0, plugin_utils_1.hasPropertyKey)('nullable', existingProperties) && isNullable && ts.createPropertyAssignment('nullable', ts.createLiteral(isNullable)), this.createTypePropertyAssignment(node.type, typeChecker, existingProperties, hostFilename, sourceFile, pluginOptions), this.createDescriptionPropertyAssigment(node, existingProperties, pluginOptions, sourceFile), ]; const objectLiteral = ts.createObjectLiteral((0, lodash_1.compact)((0, lodash_1.flatten)(properties))); return objectLiteral; } createTypePropertyAssignment(node, typeChecker, existingProperties, hostFilename, sourceFile, pluginOptions) { const key = 'type'; if ((0, plugin_utils_1.hasPropertyKey)(key, existingProperties)) { return undefined; } if (node) { if (ts.isTypeLiteralNode(node)) { const propertyAssignments = Array.from(node.members || []).map((member) => { const literalExpr = this.createDecoratorObjectLiteralExpr(member, typeChecker, existingProperties, hostFilename, sourceFile, pluginOptions); return ts.createPropertyAssignment(ts.createIdentifier(member.name.getText()), literalExpr); }); return ts.createPropertyAssignment(key, ts.createArrowFunction(undefined, undefined, [], undefined, undefined, ts.createParen(ts.createObjectLiteral(propertyAssignments)))); } else if (ts.isUnionTypeNode(node)) { const nullableType = (0, ast_utils_1.findNullableTypeFromUnion)(node, typeChecker); const remainingTypes = node.types.filter((item) => item !== nullableType); if (remainingTypes.length === 1) { return this.createTypePropertyAssignment(remainingTypes[0], typeChecker, existingProperties, hostFilename); } } } const type = typeChecker.getTypeAtLocation(node); if (!type) { return undefined; } let typeReference = (0, plugin_utils_1.getTypeReferenceAsString)(type, typeChecker); if (!typeReference) { return undefined; } typeReference = (0, plugin_utils_1.replaceImportPath)(typeReference, hostFilename); if (typeReference && typeReference.includes('require')) { const importPath = /\(\"([^)]).+(\")/.exec(typeReference)[0]; if (importPath) { let importsToAdd = importsToAddPerFile.get(hostFilename); if (!importsToAdd) { importsToAdd = new Set(); importsToAddPerFile.set(hostFilename, importsToAdd); } importsToAdd.add(importPath.slice(2, importPath.length - 1)); } } return ts.createPropertyAssignment(key, ts.createArrowFunction(undefined, undefined, [], undefined, undefined, ts.createIdentifier(typeReference))); } addClassMetadata(node, objectLiteral, sourceFile) { const hostClass = node.parent; const className = hostClass.name && hostClass.name.getText(); if (!className) { return; } const existingMetadata = metadataHostMap.get(className) || {}; const propertyName = node.name && node.name.getText(sourceFile); if (!propertyName || (node.name && node.name.kind === ts.SyntaxKind.ComputedPropertyName)) { return; } metadataHostMap.set(className, { ...existingMetadata, [propertyName]: objectLiteral, }); } getClassMetadata(node) { if (!node.name) { return; } return metadataHostMap.get(node.name.getText()); } updateImports(sourceFile, pathsToImport) { var _a; const [major, minor] = (_a = ts.versionMajorMinor) === null || _a === void 0 ? void 0 : _a.split('.').map((x) => +x); const IMPORT_PREFIX = 'eager_import_'; const importDeclarations = pathsToImport.map((path, index) => { if (major == 4 && minor >= 2) { return ts.createImportEqualsDeclaration(undefined, undefined, false, IMPORT_PREFIX + index, ts.createExternalModuleReference(ts.createLiteral(path))); } return ts.createImportEqualsDeclaration(undefined, undefined, IMPORT_PREFIX + index, ts.createExternalModuleReference(ts.createLiteral(path))); }); return ts.updateSourceFileNode(sourceFile, [ ...importDeclarations, ...sourceFile.statements, ]); } createDescriptionPropertyAssigment(node, existingProperties = ts.createNodeArray(), options = {}, sourceFile) { if (!options.introspectComments || !sourceFile) { return; } const description = (0, ast_utils_1.getDescriptionOfNode)(node, sourceFile); const keyOfComment = 'description'; if (!(0, plugin_utils_1.hasPropertyKey)(keyOfComment, existingProperties) && description) { const descriptionPropertyAssignment = ts.createPropertyAssignment(keyOfComment, ts.createLiteral(description)); return descriptionPropertyAssignment; } } } exports.ModelClassVisitor = ModelClassVisitor;