UNPKG

@neo-one/smart-contract-compiler

Version:

NEO•ONE TypeScript smart contract compiler.

98 lines (96 loc) 4.04 kB
import { tsUtils } from '@neo-one/ts-utils'; import { utils } from '@neo-one/utils'; import ts from 'typescript'; import { createContextForDir } from './createContext'; import { CircularLinkedDependencyError, MultipleContractsInFileError } from './errors'; export const scanContext = (context) => { const smartContract = tsUtils.symbol.getDeclarations(context.builtins.getValueSymbol('SmartContract'))[0]; if (!ts.isClassDeclaration(smartContract)) { throw new Error('Something went wrong!'); } const { contracts, dependencies } = tsUtils.class_ .getExtendors(context.program, context.languageService, smartContract) .reduce((acc, derived) => { if (!tsUtils.modifier.isAbstract(derived) && !tsUtils.file.isDeclarationFile(tsUtils.node.getSourceFile(derived))) { const filePath = tsUtils.file.getFilePath(tsUtils.node.getSourceFile(derived)); const name = tsUtils.node.getNameOrThrow(derived); const existing = acc.contracts[filePath]; if (existing !== undefined) { throw new MultipleContractsInFileError(filePath); } const references = [ ...new Set(tsUtils.reference .findReferencesAsNodes(context.program, context.languageService, derived) .map((reference) => tsUtils.file.getFilePath(tsUtils.node.getSourceFile(reference)))), ]; const dependency = { filePath, name }; const dependenciesOut = references.reduce((innerAcc, reference) => { let filePathDependencies = innerAcc[reference]; if (filePathDependencies === undefined) { filePathDependencies = []; } return { ...innerAcc, [reference]: [...filePathDependencies, dependency], }; }, acc.dependencies); return { contracts: { ...acc.contracts, [filePath]: { filePath, name, dependencies: [], }, }, dependencies: dependenciesOut, }; } return acc; }, { contracts: {}, dependencies: {} }); const unsortedContracts = Object.values(contracts).map((contract) => { const filePathDependencies = dependencies[contract.filePath]; return { ...contract, dependencies: filePathDependencies === undefined ? [] : filePathDependencies, }; }); return topographicalSort(unsortedContracts); }; const topographicalSort = (contracts) => { const contractToDependencies = contracts.reduce((acc, contract) => ({ ...acc, [contract.filePath]: new Set(contract.dependencies.map((dep) => dep.filePath)), }), {}); const mutableOut = []; const satisfied = contracts.filter((contract) => contract.dependencies.length === 0); let remaining = contracts.filter((contract) => contract.dependencies.length !== 0); while (satisfied.length > 0) { const node = satisfied.shift(); if (node === undefined) { break; } mutableOut.push(node); remaining = remaining .map((contract) => { const deps = contractToDependencies[contract.filePath]; deps.delete(node.filePath); if (deps.size === 0) { satisfied.push(contract); return undefined; } return contract; }) .filter(utils.notNull); } if (mutableOut.length !== contracts.length) { throw new CircularLinkedDependencyError(contracts.map((contract) => contract.name)); } return mutableOut; }; export const scan = async (dir, host) => { const context = await createContextForDir(dir, host); return scanContext(context); }; //# sourceMappingURL=scan.js.map