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