@neo-one/smart-contract-compiler
Version:
NEO•ONE TypeScript smart contract compiler.
396 lines (394 loc) • 18.9 kB
JavaScript
"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