1 | import fs from './fileSystem'
|
2 | import async from 'async'
|
3 | import path from 'path'
|
4 | let RemixCompiler = require('remix-solidity').Compiler
|
5 | import { SrcIfc, CompilerConfiguration } from './types'
|
6 |
|
7 | function 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 |
|
12 | function 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 |
|
26 |
|
27 |
|
28 |
|
29 | function isRemixTestFile(path: string) {
|
30 | return ['tests.sol', 'remix_tests.sol', 'remix_accounts.sol'].some(name => path.includes(name))
|
31 | }
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | function 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 |
|
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 |
|
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('');
|
63 |
|
64 |
|
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 |
|
72 | const userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'
|
73 | const isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' electron/') > -1)
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | export 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 |
|
103 | fs.walkSync(filepath, (foundpath: string) => {
|
104 |
|
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 |
|
119 | next()
|
120 |
|
121 | },
|
122 | function doCompilation(next: Function) {
|
123 |
|
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 | */
|
150 | export 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 |
|
177 | compiler.event.register('compilerLoaded', this, (version) => {
|
178 | next()
|
179 | })
|
180 | },
|
181 | function doCompilation (next: Function) {
|
182 |
|
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)
|
197 | })
|
198 | }
|
199 |
|
\ | No newline at end of file |