UNPKG

8.34 kBPlain TextView Raw
1import fs from './fileSystem'
2import async from 'async'
3import path from 'path'
4let RemixCompiler = require('remix-solidity').Compiler
5import { SrcIfc, CompilerConfiguration } from './types'
6
7function regexIndexOf (inputString: string, regex: RegExp, startpos: number = 0) {
8 const indexOf = inputString.substring(startpos).search(regex)
9 return (indexOf >= 0) ? (indexOf + (startpos)) : indexOf
10}
11
12function writeTestAccountsContract (accounts: string[]) {
13 const testAccountContract = require('../sol/tests_accounts.sol.js')
14 let body = `address[${accounts.length}] memory accounts;`
15 if (!accounts.length) body += ';'
16 else {
17 accounts.map((address, index) => {
18 body += `\naccounts[${index}] = ${address};\n`
19 })
20 }
21 return testAccountContract.replace('>accounts<', body)
22}
23
24/**
25 * @dev Check if path includes name of a remix test file
26 * @param path file path to check
27 */
28
29function isRemixTestFile(path: string) {
30 return ['tests.sol', 'remix_tests.sol', 'remix_accounts.sol'].some(name => path.includes(name))
31}
32
33/**
34 * @dev Process file to prepare sources object to be passed in solc compiler input
35 *
36 * See: https://solidity.readthedocs.io/en/latest/using-the-compiler.html#input-description
37 *
38 * @param filePath path of file to process
39 * @param sources existing 'sources' object in which keys are the "global" names of the source files and
40 * value is object containing content of corresponding file with under key 'content'
41 * @param isRoot True, If file is a root test contract file which is getting processed, not an imported file
42 */
43
44function processFile(filePath: string, sources: SrcIfc, isRoot: boolean = false) {
45 const importRegEx: RegExp = /import ['"](.+?)['"];/g;
46 let group: RegExpExecArray| null = null;
47 const isFileAlreadyInSources: boolean = Object.keys(sources).includes(filePath)
48
49 // Return if file is a remix test file or already processed
50 if(isRemixTestFile(filePath) || isFileAlreadyInSources)
51 return
52
53 let content: string = fs.readFileSync(filePath, { encoding: 'utf-8' });
54 const testFileImportRegEx: RegExp = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
55
56 // import 'remix_tests.sol', if file is a root test contract file and doesn't already have it
57 if (isRoot && filePath.endsWith('_test.sol') && regexIndexOf(content, testFileImportRegEx) < 0) {
58 const includeTestLibs: string = '\nimport \'remix_tests.sol\';\n'
59 content = includeTestLibs.concat(content)
60 }
61 sources[filePath] = {content};
62 importRegEx.exec(''); // Resetting state of RegEx
63
64 // Process each 'import' in file content
65 while (group = importRegEx.exec(content)) {
66 const importedFile: string = group[1];
67 const importedFilePath: string = path.join(path.dirname(filePath), importedFile);
68 processFile(importedFilePath, sources)
69 }
70}
71
72const userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'
73const isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' electron/') > -1)
74
75/**
76 * @dev Compile file or files before running tests (used for CLI execution)
77 * @param filename Name of file
78 * @param isDirectory True, if path is a directory
79 * @param opts Options
80 * @param cb Callback
81 *
82 * TODO: replace this with remix's own compiler code
83 */
84
85export function compileFileOrFiles(filename: string, isDirectory: boolean, opts: any, cb: Function) {
86 let compiler: any
87 const accounts: string[] = opts.accounts || []
88 const sources: SrcIfc = {
89 'tests.sol': { content: require('../sol/tests.sol.js') },
90 'remix_tests.sol': { content: require('../sol/tests.sol.js') },
91 'remix_accounts.sol': { content: writeTestAccountsContract(accounts) }
92 }
93 const filepath: string = (isDirectory ? filename : path.dirname(filename))
94 try {
95 if(!isDirectory && fs.existsSync(filename)) {
96 if (filename.split('.').pop() === 'sol') {
97 processFile(filename, sources, true)
98 } else {
99 throw new Error('Not a solidity file')
100 }
101 } else {
102 // walkSync only if it is a directory
103 fs.walkSync(filepath, (foundpath: string) => {
104 // only process .sol files
105 if (foundpath.split('.').pop() === 'sol') {
106 processFile(foundpath, sources, true)
107 }
108 })
109 }
110
111 } catch (e) {
112 throw e
113 } finally {
114 async.waterfall([
115 function loadCompiler(next: Function) {
116 compiler = new RemixCompiler()
117 compiler.onInternalCompilerLoaded()
118 // compiler.event.register('compilerLoaded', this, function (version) {
119 next()
120 // });
121 },
122 function doCompilation(next: Function) {
123 // @ts-ignore
124 compiler.event.register('compilationFinished', this, (success, data, source) => {
125 next(null, data)
126 })
127 compiler.compile(sources, filepath)
128 }
129 ], function (err: Error | null | undefined, result: any) {
130 let error: Error[] = []
131 if (result.error) error.push(result.error)
132 let errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error')
133 if (errors.length > 0) {
134 if (!isBrowser) require('signale').fatal(errors)
135 return cb(errors)
136 }
137 cb(err, result.contracts, result.sources) //return callback with contract details & ASTs
138 })
139 }
140}
141
142/**
143 * @dev Compile contract source before running tests (used for IDE tests execution)
144 * @param sources sources
145 * @param compilerConfig current compiler configuration
146 * @param importFileCb Import file callback
147 * @param opts Options
148 * @param cb Callback
149 */
150export function compileContractSources(sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb: Function) {
151 let compiler, filepath: string
152 const accounts: string[] = opts.accounts || []
153 // Iterate over sources keys. Inject test libraries. Inject test library import statements.
154 if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) {
155 sources['tests.sol'] = { content: require('../sol/tests.sol.js') }
156 sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') }
157 sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) }
158 }
159 const testFileImportRegEx: RegExp = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
160
161 let includeTestLibs: string = '\nimport \'remix_tests.sol\';\n'
162 for (let file in sources) {
163 const c: string = sources[file].content
164 if (file.endsWith('_test.sol') && c && regexIndexOf(c, testFileImportRegEx) < 0) {
165 sources[file].content = includeTestLibs.concat(c)
166 }
167 }
168
169 async.waterfall([
170 function loadCompiler (next: Function) {
171 const {currentCompilerUrl, evmVersion, optimize, usingWorker} = compilerConfig
172 compiler = new RemixCompiler(importFileCb)
173 compiler.set('evmVersion', evmVersion)
174 compiler.set('optimize', optimize)
175 compiler.loadVersion(usingWorker, currentCompilerUrl)
176 // @ts-ignore
177 compiler.event.register('compilerLoaded', this, (version) => {
178 next()
179 })
180 },
181 function doCompilation (next: Function) {
182 // @ts-ignore
183 compiler.event.register('compilationFinished', this, (success, data, source) => {
184 next(null, data)
185 })
186 compiler.compile(sources, filepath)
187 }
188 ], function (err: Error | null | undefined , result: any) {
189 let error: Error[] = []
190 if (result.error) error.push(result.error)
191 let errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error')
192 if (errors.length > 0) {
193 if (!isBrowser) require('signale').fatal(errors)
194 return cb(errors)
195 }
196 cb(err, result.contracts, result.sources) // return callback with contract details & ASTs
197 })
198}
199
\No newline at end of file