UNPKG

@neo-one/smart-contract-compiler

Version:

NEO•ONE TypeScript smart contract compiler.

396 lines (394 loc) 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnalysisService = exports.DEFAULT_DIAGNOSTIC_OPTIONS = void 0; const tslib_1 = require("tslib"); const client_common_1 = require("@neo-one/client-common"); const ts_utils_1 = require("@neo-one/ts-utils"); const utils_1 = require("@neo-one/utils"); const typescript_1 = tslib_1.__importDefault(require("typescript")); const types_1 = require("../compile/helper/types"); const DiagnosticCode_1 = require("../DiagnosticCode"); const DiagnosticMessage_1 = require("../DiagnosticMessage"); const utils_2 = require("../utils"); exports.DEFAULT_DIAGNOSTIC_OPTIONS = { error: true, warning: false, }; class AnalysisService { constructor(context) { this.context = context; this.memoized = utils_2.createMemoized(); } getFunctionReturnType(node, options = exports.DEFAULT_DIAGNOSTIC_OPTIONS) { if (typescript_1.default.isAccessor(node)) { return this.getType(node); } const typeNode = ts_utils_1.tsUtils.type_.getTypeNode(node); if (typeNode !== undefined) { return this.getNotAnyType(typeNode, ts_utils_1.tsUtils.type_.getTypeFromTypeNode(this.context.typeChecker, typeNode)); } const signatureTypes = this.extractSignature(node, options); return signatureTypes === undefined ? undefined : signatureTypes.returnType; } extractAllSignatures(node, options = exports.DEFAULT_DIAGNOSTIC_OPTIONS) { return this.extractAllSignaturesForType(node, this.getType(node), options); } extractSignature(node, options = exports.DEFAULT_DIAGNOSTIC_OPTIONS) { return this.extractSignatureForType(node, this.getType(node), options); } getSignatures(node) { const signature = this.context.typeChecker.getResolvedSignature(node); if (signature !== undefined && !ts_utils_1.tsUtils.signature.isFailure(signature)) { return [signature]; } const expr = ts_utils_1.tsUtils.expression.getExpressionForCall(node); const type = this.getType(expr); if (type === undefined) { return undefined; } return ts_utils_1.tsUtils.type_.getCallSignatures(type); } extractAllSignaturesForType(node, type, options = exports.DEFAULT_DIAGNOSTIC_OPTIONS) { const signatures = type === undefined ? undefined : ts_utils_1.tsUtils.type_.getCallSignatures(type); if (signatures === undefined) { return []; } return signatures.map((signature) => this.extractSignatureTypes(node, signature, options)).filter(utils_1.utils.notNull); } extractSignatureForType(node, type, options = exports.DEFAULT_DIAGNOSTIC_OPTIONS) { const signatureTypes = this.extractAllSignaturesForType(node, type, options); if (signatureTypes.length === 0) { return undefined; } if (signatureTypes.length !== 1) { this.report(options, node, DiagnosticCode_1.DiagnosticCode.MultipleSignatures, DiagnosticMessage_1.DiagnosticMessage.MultipleSignatures); return undefined; } return signatureTypes[0]; } extractSignaturesForCall(node, options = exports.DEFAULT_DIAGNOSTIC_OPTIONS) { const signatures = this.getSignatures(node); if (signatures === undefined) { return undefined; } return signatures.map((signature) => this.extractSignatureTypes(node, signature, options)).filter(utils_1.utils.notNull); } extractSignatureTypes(node, signature, options = exports.DEFAULT_DIAGNOSTIC_OPTIONS) { const params = ts_utils_1.tsUtils.signature.getParameters(signature); const paramTypes = params.map((param) => this.getTypeOfSymbol(param, node)); const paramDeclsNullable = params.map((param) => ts_utils_1.tsUtils.symbol.getValueDeclaration(param)); const nullParamIndex = paramDeclsNullable.indexOf(undefined); if (nullParamIndex !== -1) { const nullParam = params[nullParamIndex]; this.report(options, node, DiagnosticCode_1.DiagnosticCode.Invariant, DiagnosticMessage_1.DiagnosticMessage.MissingParameterDeclaration, ts_utils_1.tsUtils.symbol.getName(nullParam)); return undefined; } const paramDecls = paramDeclsNullable.filter(utils_1.utils.notNull).filter(typescript_1.default.isParameter); const declToType = new Map(); for (const [paramDecl, paramType] of utils_1.utils.zip(paramDecls, paramTypes)) { declToType.set(paramDecl, paramType); } return { paramDecls, paramTypes: declToType, returnType: this.getNotAnyType(node, ts_utils_1.tsUtils.signature.getReturnType(signature)), }; } extractLiteralAddress(original) { return this.memoized('extract-literal-address', utils_2.nodeKey(original), () => this.extractLiteral(original, 'AddressConstructor', (value) => { try { return client_common_1.common.stringToUInt160(client_common_1.addressToScriptHash(value)); } catch (_a) { return client_common_1.common.stringToUInt160(value); } }, client_common_1.common.bufferToUInt160)); } extractLiteralHash256(original) { return this.extractLiteral(original, 'Hash256Constructor', client_common_1.common.stringToUInt256, client_common_1.common.bufferToUInt256); } extractLiteralPublicKey(original) { return this.extractLiteral(original, 'PublicKeyConstructor', client_common_1.common.stringToECPoint, client_common_1.common.bufferToECPoint); } getType(node, options = {}) { return this.memoized('get-type', utils_2.nodeKey(node), () => this.getNotAnyType(node, ts_utils_1.tsUtils.type_.getType(this.context.typeChecker, node), options)); } getTypeOfSymbol(symbol, node) { if (symbol === undefined) { return undefined; } return this.memoized('get-type-of-symbol', `${ts_utils_1.symbolKey(symbol)}:${utils_2.nodeKey(node)}`, () => this.getNotAnyType(node, ts_utils_1.tsUtils.type_.getTypeAtLocation(this.context.typeChecker, symbol, node))); } getSymbol(node) { return this.memoized('symbol', utils_2.nodeKey(node), () => { const symbol = ts_utils_1.tsUtils.node.getSymbol(this.context.typeChecker, node); if (symbol === undefined) { return undefined; } const aliased = ts_utils_1.tsUtils.symbol.getAliasedSymbol(this.context.typeChecker, symbol); if (aliased !== undefined) { return aliased; } return symbol; }); } getTypeSymbol(node) { return this.memoized('get-type-symbol', utils_2.nodeKey(node), () => { const type = this.getType(node); return this.getSymbolForType(node, type); }); } getSymbolForType(_node, type) { if (type === undefined) { return undefined; } return this.memoized('get-symbol-for-type', utils_2.typeKey(type), () => { let symbol = ts_utils_1.tsUtils.type_.getAliasSymbol(type); if (symbol === undefined) { symbol = ts_utils_1.tsUtils.type_.getSymbol(type); } if (symbol === undefined) { return undefined; } const aliased = ts_utils_1.tsUtils.symbol.getAliasedSymbol(this.context.typeChecker, symbol); if (aliased !== undefined) { return aliased; } return symbol; }); } getNotAnyType(node, type, { error = true } = {}) { if (type !== undefined && ts_utils_1.tsUtils.type_.isAny(type)) { if (error && !ts_utils_1.tsUtils.type_.isErrorType(type)) { this.context.reportTypeError(node); } return undefined; } if (type !== undefined) { const constraintType = ts_utils_1.tsUtils.type_.getConstraint(type); if (constraintType !== undefined) { return constraintType; } } return type; } extractStorageKey(node) { return this.memoized('extract-storage-key', utils_2.nodeKey(node), () => { const smartContract = ts_utils_1.tsUtils.node.getFirstAncestorByTest(node, typescript_1.default.isClassDeclaration); if (smartContract === undefined || !this.isSmartContract(smartContract)) { return undefined; } const decl = ts_utils_1.tsUtils.node.getFirstAncestorByTest(node, typescript_1.default.isPropertyDeclaration); if (decl === undefined) { return undefined; } return ts_utils_1.tsUtils.node.getName(decl); }); } isSmartContract(node) { return this.memoized('is-smart-contract', utils_2.nodeKey(node), () => { const extendsExpr = ts_utils_1.tsUtils.class_.getExtends(node); const isSmartContract = extendsExpr !== undefined && this.context.builtins.isValue(ts_utils_1.tsUtils.expression.getExpression(extendsExpr), 'SmartContract'); if (isSmartContract) { return true; } const baseClasses = ts_utils_1.tsUtils.class_.getBaseClasses(this.context.typeChecker, node); if (baseClasses.some((value) => this.context.builtins.isValue(value, 'SmartContract'))) { return true; } const baseClass = ts_utils_1.tsUtils.class_.getBaseClass(this.context.typeChecker, node); return baseClass !== undefined && this.isSmartContract(baseClass); }); } isSmartContractNode(node) { return this.memoized('is-smart-contract-node', utils_2.nodeKey(node), () => { const symbol = this.getSymbol(node); if (symbol === undefined) { return false; } const decls = ts_utils_1.tsUtils.symbol.getDeclarations(symbol); if (decls.length === 0) { return false; } const decl = decls[0]; return typescript_1.default.isClassDeclaration(decl) && this.isSmartContract(decl); }); } getSymbolAndAllInheritedSymbols(node) { return this.memoized('get-symbol-and-all-inherited-symbols', utils_2.nodeKey(node), () => { const symbol = this.getSymbol(node); const symbols = [symbol].filter(utils_1.utils.notNull); if (typescript_1.default.isClassDeclaration(node) || typescript_1.default.isClassExpression(node) || typescript_1.default.isInterfaceDeclaration(node)) { const baseTypes = ts_utils_1.tsUtils.class_.getBaseTypesFlattened(this.context.typeChecker, node); return symbols.concat(baseTypes.map((baseType) => this.getSymbolForType(node, baseType)).filter(utils_1.utils.notNull)); } return symbols; }); } isValidStorageType(node, type) { return !ts_utils_1.tsUtils.type_.hasType(type, (tpe) => !ts_utils_1.tsUtils.type_.isOnlyType(tpe, (tp) => types_1.isOnlyUndefined(this.context, node, tp) || types_1.isOnlyNull(this.context, node, tp) || types_1.isOnlyBoolean(this.context, node, tp) || types_1.isOnlyNumber(this.context, node, tp) || types_1.isOnlyString(this.context, node, tp) || types_1.isOnlySymbol(this.context, node, tp) || types_1.isOnlyBuffer(this.context, node, tp) || this.isValidStorageArray(node, tp) || this.isValidStorageMap(node, tp) || this.isValidStorageSet(node, tp))); } findReferencesAsNodes(node) { return this.memoized('find-references-as-nodes', utils_2.nodeKey(node), () => ts_utils_1.tsUtils.reference .findReferencesAsNodes(this.context.program, this.context.languageService, node) .filter((found) => this.context.sourceFiles.has(ts_utils_1.tsUtils.node.getSourceFile(found)))); } isSmartContractMixinFunction(node) { const parameters = ts_utils_1.tsUtils.parametered.getParameters(node); if (parameters.length !== 1) { return false; } const signatureTypess = this.extractAllSignatures(node); if (signatureTypess.length !== 1) { return false; } const signatureTypes = signatureTypess[0]; const firstParam = signatureTypes.paramDecls[0]; const firstParamType = signatureTypes.paramTypes.get(firstParam); if (firstParamType === undefined || ts_utils_1.tsUtils.type_.getConstructSignatures(firstParamType).length !== 1) { return false; } const constructSignatureTypes = this.extractSignatureTypes(firstParam, ts_utils_1.tsUtils.type_.getConstructSignatures(firstParamType)[0]); if (constructSignatureTypes === undefined) { return false; } const returnTypeSymbol = this.getSymbolForType(firstParam, constructSignatureTypes.returnType); return returnTypeSymbol !== undefined && returnTypeSymbol === this.context.builtins.getValueSymbol('SmartContract'); } isValidStorageArray(node, type) { if (!types_1.isOnlyArray(this.context, node, type)) { return false; } const typeArguments = ts_utils_1.tsUtils.type_.getTypeArgumentsArray(type); if (typeArguments.length !== 1) { return true; } return this.isValidStorageType(node, typeArguments[0]); } isValidStorageMap(node, type) { if (!types_1.isOnlyMap(this.context, node, type)) { return false; } const typeArguments = ts_utils_1.tsUtils.type_.getTypeArgumentsArray(type); if (typeArguments.length !== 2) { return true; } return this.isValidStorageType(node, typeArguments[0]) && this.isValidStorageType(node, typeArguments[1]); } isValidStorageSet(node, type) { if (!types_1.isOnlySet(this.context, node, type)) { return false; } const typeArguments = ts_utils_1.tsUtils.type_.getTypeArgumentsArray(type); if (typeArguments.length !== 1) { return true; } return this.isValidStorageType(node, typeArguments[0]); } extractLiteral(original, name, processText, processBuffer) { return this.traceIdentifier(original, (node) => { if (!typescript_1.default.isCallExpression(node) && !typescript_1.default.isTaggedTemplateExpression(node)) { return undefined; } const expr = typescript_1.default.isCallExpression(node) ? ts_utils_1.tsUtils.expression.getExpression(node) : ts_utils_1.tsUtils.template.getTag(node); const symbol = this.getSymbol(expr); const hash256From = this.context.builtins.getOnlyMemberSymbol(name, 'from'); const bufferFrom = this.context.builtins.getOnlyMemberSymbol('BufferConstructor', 'from'); if (symbol === hash256From) { const arg = typescript_1.default.isCallExpression(node) ? ts_utils_1.tsUtils.argumented.getArguments(node)[0] : ts_utils_1.tsUtils.template.getTemplate(node); if (typescript_1.default.isTaggedTemplateExpression(node) && !typescript_1.default.isNoSubstitutionTemplateLiteral(ts_utils_1.tsUtils.template.getTemplate(node))) { return undefined; } if (arg === undefined) { return undefined; } return this.traceIdentifier(arg, (value) => { if (typescript_1.default.isStringLiteral(value) || typescript_1.default.isNoSubstitutionTemplateLiteral(value)) { try { return processText(ts_utils_1.tsUtils.literal.getLiteralValue(value)); } catch (_a) { } } return undefined; }); } if (symbol === bufferFrom && typescript_1.default.isCallExpression(node)) { const arg = ts_utils_1.tsUtils.argumented.getArguments(node)[0]; if (arg === undefined) { return undefined; } return this.traceIdentifier(arg, (value) => { if (!typescript_1.default.isStringLiteral(value)) { return undefined; } try { return processBuffer(Buffer.from(ts_utils_1.tsUtils.literal.getLiteralValue(value), 'hex')); } catch (_a) { return undefined; } }); } return undefined; }); } traceIdentifier(nodeIn, extractValue) { const node = this.unwrapExpression(nodeIn); if (typescript_1.default.isIdentifier(node)) { const symbol = this.getSymbol(node); if (symbol === undefined) { return undefined; } const decl = ts_utils_1.tsUtils.symbol.getValueDeclaration(symbol); if (decl === undefined) { return undefined; } if (!typescript_1.default.isVariableDeclaration(decl)) { return undefined; } const parent = ts_utils_1.tsUtils.node.getParent(decl); if (!typescript_1.default.isVariableDeclarationList(parent) || !ts_utils_1.tsUtils.modifier.isConst(parent)) { return undefined; } const initializer = ts_utils_1.tsUtils.initializer.getInitializer(parent); if (initializer === undefined) { return undefined; } return this.traceIdentifier(initializer, extractValue); } return extractValue(node); } unwrapExpression(node) { if (typescript_1.default.isParenthesizedExpression(node)) { return ts_utils_1.tsUtils.expression.getExpression(node); } if (typescript_1.default.isAsExpression(node)) { return ts_utils_1.tsUtils.expression.getExpression(node); } return node; } report(options, node, code, message, ...args) { if (options.error) { this.context.reportError(node, code, message, ...args); } else if (options.warning) { this.context.reportWarning(node, code, message, ...args); } } } exports.AnalysisService = AnalysisService; //# sourceMappingURL=AnalysisService.js.map