Merge branch 'master' into fsfallback

pull/5370/head
bunsenstraat 3 years ago committed by GitHub
commit 260aa6c94e
  1. 15
      apps/remix-ide-e2e/src/tests/url.spec.ts
  2. 43
      apps/remix-ide/src/app/editor/editor.js
  3. 2
      apps/remix-ide/src/app/files/fileManager.ts
  4. 23
      apps/remix-ide/src/app/files/remixDProvider.js
  5. 4
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  6. 4
      apps/remix-ide/src/app/tabs/test-tab.js
  7. 4
      apps/solidity-compiler/src/app/compiler-api.ts
  8. 128
      libs/remix-core-plugin/src/lib/compiler-artefacts.ts
  9. 28
      libs/remix-core-plugin/src/lib/compiler-metadata.ts
  10. 2
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  11. 2
      libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts
  12. 2
      libs/remix-debug/src/source/offsetToLineColumnConverter.ts
  13. 8
      libs/remix-solidity/src/compiler/compiler-abstract.ts
  14. 4
      libs/remix-solidity/src/compiler/compiler-helpers.ts
  15. 1
      libs/remix-solidity/src/compiler/compiler-worker.ts
  16. 22
      libs/remix-solidity/src/compiler/compiler.ts
  17. 1
      libs/remix-solidity/src/compiler/types.ts
  18. 6
      libs/remix-tests/src/compiler.ts
  19. 160
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  20. 6
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  21. 4
      libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts
  22. 4
      libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
  23. 4
      libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts
  24. 2
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx

@ -104,6 +104,21 @@ module.exports = {
.verify.elementPresent('#runs:disabled') .verify.elementPresent('#runs:disabled')
.click('[for="optimize"') .click('[for="optimize"')
.verify.attributeEquals('#runs', 'value', '200') .verify.attributeEquals('#runs', 'value', '200')
},
'Should load json files from link passed in remix URL': function (browser: NightwatchBrowser) {
browser
.url('http://localhost:8080/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.6.12+commit.27d51765.js&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json')
.refresh()
.pause(5000)
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="code-sample"]')
.openFile('@openzeppelin')
.openFile('@openzeppelin/contracts')
.openFile('@openzeppelin/contracts/access')
.openFile('@openzeppelin/contracts/access/AccessControl.sol')
.openFile('contracts')
.openFile('contracts/governance')
.openFile('contracts/governance/UnionGovernor.sol')
.end() .end()
} }
} }

@ -28,8 +28,6 @@ class Editor extends Plugin {
// Init // Init
this.event = new EventManager() this.event = new EventManager()
this.sessions = {} this.sessions = {}
this.sourceAnnotationsPerFile = {}
this.markerPerFile = {}
this.readOnlySessions = {} this.readOnlySessions = {}
this.previousInput = '' this.previousInput = ''
this.saveTimeout = null this.saveTimeout = null
@ -74,8 +72,6 @@ class Editor extends Plugin {
editorAPI={state.api} editorAPI={state.api}
themeType={state.currentThemeType} themeType={state.currentThemeType}
currentFile={state.currentFile} currentFile={state.currentFile}
sourceAnnotationsPerFile={state.sourceAnnotationsPerFile}
markerPerFile={state.markerPerFile}
events={state.events} events={state.events}
plugin={state.plugin} plugin={state.plugin}
/> />
@ -108,6 +104,10 @@ class Editor extends Plugin {
} }
this.ref.gotoLine = (line, column) => this.gotoLine(line, column || 0) this.ref.gotoLine = (line, column) => this.gotoLine(line, column || 0)
this.ref.getCursorPosition = () => this.getCursorPosition() this.ref.getCursorPosition = () => this.getCursorPosition()
this.ref.addMarkerPerFile = (marker, filePath) => this.addMarkerPerFile(marker, filePath)
this.ref.addSourceAnnotationsPerFile = (annotation, filePath) => this.addSourceAnnotationsPerFile(annotation, filePath)
this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration)
this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration)
}} id='editorView'> }} id='editorView'>
<PluginViewWrapper plugin={this} /> <PluginViewWrapper plugin={this} />
</div> </div>
@ -118,8 +118,6 @@ class Editor extends Plugin {
api: this.api, api: this.api,
currentThemeType: this.currentThemeType, currentThemeType: this.currentThemeType,
currentFile: this.currentFile, currentFile: this.currentFile,
sourceAnnotationsPerFile: this.sourceAnnotationsPerFile,
markerPerFile: this.markerPerFile,
events: this.events, events: this.events,
plugin: this plugin: this
}) })
@ -410,27 +408,12 @@ class Editor extends Plugin {
if (filePath && !this.sessions[filePath]) throw new Error('file not found' + filePath) if (filePath && !this.sessions[filePath]) throw new Error('file not found' + filePath)
const path = filePath || this.currentFile const path = filePath || this.currentFile
const currentAnnotations = this[typeOfDecoration][path] this.api.clearDecorationsByPlugin(path, plugin, typeOfDecoration)
if (!currentAnnotations) return
const newAnnotations = []
for (const annotation of currentAnnotations) {
if (annotation.from !== plugin) newAnnotations.push(annotation)
}
this[typeOfDecoration][path] = newAnnotations
this.renderComponent()
} }
keepDecorationsFor (name, typeOfDecoration) { keepDecorationsFor (plugin, typeOfDecoration) {
if (!this.currentFile) return if (!this.currentFile) return
if (!this[typeOfDecoration][this.currentFile]) return this.api.keepDecorationsFor(this.currentFile, plugin, typeOfDecoration)
const annotations = this[typeOfDecoration][this.currentFile]
for (const annotation of annotations) {
annotation.hide = annotation.from !== name
}
this.renderComponent()
} }
/** /**
@ -473,10 +456,16 @@ class Editor extends Plugin {
const path = filePath || this.currentFile const path = filePath || this.currentFile
const { from } = this.currentRequest const { from } = this.currentRequest
if (!this[typeOfDecoration][path]) this[typeOfDecoration][path] = []
decoration.from = from decoration.from = from
this[typeOfDecoration][path].push(decoration)
this.renderComponent() if (typeOfDecoration === 'markerPerFile') {
this.api.addMarkerPerFile(decoration, path)
return
}
if (typeOfDecoration === 'sourceAnnotationsPerFile') {
this.api.addSourceAnnotationsPerFile(decoration, path)
return
}
} }
/** /**

@ -752,7 +752,7 @@ class FileManager extends Plugin {
if (provider) { if (provider) {
try{ try{
const content = await provider.get(currentFile) const content = await provider.get(currentFile)
this.editor.setText(content) if(content) this.editor.setText(content)
}catch(error){ }catch(error){
console.log(error) console.log(error)
} }

@ -98,20 +98,19 @@ module.exports = class RemixDProvider extends FileProvider {
}) })
} }
get (path, cb) { async get (path, cb) {
if (!this._isReady) return cb && cb('provider not ready') if (!this._isReady) return cb && cb('provider not ready')
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
this._appManager.call('remixd', 'get', { path: unprefixedpath }) try{
.then((file) => { const file = await this._appManager.call('remixd', 'get', { path: unprefixedpath })
this.filesContent[path] = file.content this.filesContent[path] = file.content
if (file.readonly) { this._readOnlyFiles[path] = 1 } if (file.readonly) { this._readOnlyFiles[path] = 1 }
cb(null, file.content) if(cb) cb(null, file.content)
}).catch((error) => { return file.content
if (error) console.log(error) } catch(error) {
// display the last known content. if (error) console.log(error)
// TODO should perhaps better warn the user that the file is not synced. if(cb) return cb(null, this.filesContent[path])
return cb(null, this.filesContent[path]) }
})
} }
async set (path, content, cb) { async set (path, content, cb) {

@ -47,11 +47,9 @@ module.exports = class SettingsTab extends ViewPlugin {
} }
render() { render() {
return ( return <div id='settingsTab'>
<div id='settingsTab'>
<PluginViewWrapper plugin={this} /> <PluginViewWrapper plugin={this} />
</div> </div>
);
} }
updateComponent(state: any){ updateComponent(state: any){

@ -89,12 +89,12 @@ module.exports = class TestTab extends ViewPlugin {
this.createTestLibs() this.createTestLibs()
}) })
this.testRunner.event.on('compilationFinished', (success, data, source) => { this.testRunner.event.on('compilationFinished', (success, data, source, input, version) => {
if (success) { if (success) {
this.allFilesInvolved.push(...Object.keys(data.sources)) this.allFilesInvolved.push(...Object.keys(data.sources))
// forwarding the event to the appManager infra // forwarding the event to the appManager infra
// This is listened by compilerArtefacts to show data while debugging // This is listened by compilerArtefacts to show data while debugging
this.emit('compilationFinished', source.target, source, 'soljson', data) this.emit('compilationFinished', source.target, source, 'soljson', data, input, version)
} }
}) })
} }

@ -261,11 +261,11 @@ export const CompilerApiMixin = (Base) => class extends Base {
this.on('fileManager', 'fileClosed', this.data.eventHandlers.onFileClosed) this.on('fileManager', 'fileClosed', this.data.eventHandlers.onFileClosed)
this.data.eventHandlers.onCompilationFinished = (success, data, source) => { this.data.eventHandlers.onCompilationFinished = (success, data, source, input, version) => {
this.compileErrors = data this.compileErrors = data
if (success) { if (success) {
// forwarding the event to the appManager infra // forwarding the event to the appManager infra
this.emit('compilationFinished', source.target, source, 'soljson', data) this.emit('compilationFinished', source.target, source, 'soljson', data, input, version)
if (data.errors && data.errors.length > 0) { if (data.errors && data.errors.length > 0) {
this.statusChanged({ this.statusChanged({
key: data.errors.length, key: data.errors.length,

@ -24,12 +24,12 @@ export class CompilerArtefacts extends Plugin {
} }
onActivation () { onActivation () {
const saveCompilationPerFileResult = (file, source, languageVersion, data) => { const saveCompilationPerFileResult = (file, source, languageVersion, data, input?) => {
this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source) this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input)
} }
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => { this.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => {
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input)
saveCompilationPerFileResult(file, source, languageVersion, data) saveCompilationPerFileResult(file, source, languageVersion, data)
}) })
@ -48,16 +48,24 @@ export class CompilerArtefacts extends Plugin {
saveCompilationPerFileResult(file, source, languageVersion, data) saveCompilationPerFileResult(file, source, languageVersion, data)
}) })
this.on('solidityUnitTesting', 'compilationFinished', (file, source, languageVersion, data) => { this.on('solidityUnitTesting', 'compilationFinished', (file, source, languageVersion, data, input, version) => {
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input)
saveCompilationPerFileResult(file, source, languageVersion, data) saveCompilationPerFileResult(file, source, languageVersion, data, input)
}) })
} }
/**
* Get artefacts for last compiled contract
* * @returns last compiled contract compiler abstract
*/
getLastCompilationResult () { getLastCompilationResult () {
return this.compilersArtefacts.__last return this.compilersArtefacts.__last
} }
/**
* Get compilation output for contracts compiled during a session of Remix IDE
* @returns compilatin output
*/
getAllContractDatas () { getAllContractDatas () {
const contractsData = {} const contractsData = {}
Object.keys(this.compilersArtefactsPerFile).map((targetFile) => { Object.keys(this.compilersArtefactsPerFile).map((targetFile) => {
@ -72,51 +80,85 @@ export class CompilerArtefacts extends Plugin {
return contractsData return contractsData
} }
async getArtefactsFromFE (path, contractName) { /**
* Get a particular contract output/artefacts from a compiler output of a Solidity file compilation
* @param compilerOutput compiler output
* @param contractName contract name
* @returns arefacts object, with fully qualified name (e.g; contracts/1_Storage.sol:Storage) as key
*/
_getAllContractArtefactsfromOutput (compilerOutput, contractName) {
const contractArtefacts = {}
for (const filename in compilerOutput) {
if(Object.keys(compilerOutput[filename]).includes(contractName)) contractArtefacts[filename + ':' + contractName] = compilerOutput[filename][contractName]
}
return contractArtefacts
}
/**
* Populate resultant object with a particular contract output/artefacts by processing all the artifacts stored in file explorer
* @param path path to start looking from
* @param contractName contract to be looked for
* @param contractArtefacts populated resultant artefacts object, with fully qualified name (e.g: contracts/1_Storage.sol:Storage) as key
* Once method execution completes, contractArtefacts object will hold all possible artefacts for contract
*/
async _populateAllContractArtefactsFromFE (path, contractName, contractArtefacts) {
const dirList = await this.call('fileManager', 'dirList', path) const dirList = await this.call('fileManager', 'dirList', path)
if(dirList && dirList.length) { if(dirList && dirList.length) {
if(dirList.includes(path + '/artifacts')) { for (const dirPath of dirList) {
const fileList = await this.call('fileManager', 'fileList', path + '/artifacts') // check if directory contains an 'artifacts' folder and a 'build-info' folder inside 'artifacts'
const artefactsFilePaths = fileList.filter(filePath => { if(dirPath === path + '/artifacts' && await this.call('fileManager', 'exists', dirPath + '/build-info')) {
const filenameArr = filePath.split('/') const buildFileList = await this.call('fileManager', 'fileList', dirPath + '/build-info')
const filename = filenameArr[filenameArr.length - 1] // process each build-info file to populate the artefacts for contractName
if (filename === `${contractName}.json` || filename === `${contractName}_metadata.json`) return true for (const buildFile of buildFileList) {
}) let content = await this.call('fileManager', 'readFile', buildFile)
if (artefactsFilePaths && artefactsFilePaths.length) { if (content) content = JSON.parse(content)
const content = await this.call('fileManager', 'readFile', artefactsFilePaths[1]) const compilerOutput = content.output.contracts
const artifacts = JSON.parse(content) const artefacts = this._getAllContractArtefactsfromOutput(compilerOutput, contractName)
return { abi: artifacts.abi, bytecode: artifacts.data.bytecode.object } // populate the resultant object with artefacts
} else { Object.assign(contractArtefacts, artefacts)
for (const dirPath of dirList) {
const result = await this.getArtefactsFromFE (dirPath, contractName)
if (result) return result
} }
} } else await this._populateAllContractArtefactsFromFE (dirPath, contractName, contractArtefacts)
} else { }
for (const dirPath of dirList) {
const result = await this.getArtefactsFromFE (dirPath, contractName)
if (result) return result
}
}
} else return } else return
} }
async getArtefactsByContractName (contractName) { /**
* Get artefacts for a contract (called by script-runner)
* @param name contract name or fully qualified name i.e. <filename>:<contractname> e.g: contracts/1_Storage.sol:Storage
* @returns artefacts for the contract
*/
async getArtefactsByContractName (name) {
const contractsDataByFilename = this.getAllContractDatas() const contractsDataByFilename = this.getAllContractDatas()
const contractsData = Object.values(contractsDataByFilename) // check if name is a fully qualified name
if (contractsData && contractsData.length) { if (name.includes(':')) {
const index = contractsData.findIndex((contractsObj) => Object.keys(contractsObj).includes(contractName)) const fullyQualifiedName = name
if (index !== -1) return { abi: contractsData[index][contractName].abi, bytecode: contractsData[index][contractName].evm.bytecode.object } const nameArr = fullyQualifiedName.split(':')
const filename = nameArr[0]
const contract = nameArr[1]
if(Object.keys(contractsDataByFilename).includes(filename) && contractsDataByFilename[filename][contract])
return contractsDataByFilename[filename][contract]
else { else {
const result = await this.getArtefactsFromFE ('contracts', contractName) const allContractsData = {}
if (result) return result await this._populateAllContractArtefactsFromFE ('contracts', contract, allContractsData)
else throw new Error(`Could not find artifacts for ${contractName}. Compile contract to generate artifacts.`) if(allContractsData[fullyQualifiedName]) return allContractsData[fullyQualifiedName]
else throw new Error(`Could not find artifacts for ${fullyQualifiedName}. Compile contract to generate artifacts.`)
} }
} else { } else {
const result = await this.getArtefactsFromFE ('contracts', contractName) const contractName = name
if (result) return result const contractArtefacts = this._getAllContractArtefactsfromOutput(contractsDataByFilename, contractName)
else throw new Error(`Could not find artifacts for ${contractName}. Compile contract to generate artifacts.`) let keys = Object.keys(contractArtefacts)
} if (!keys.length) {
await this._populateAllContractArtefactsFromFE ('contracts', contractName, contractArtefacts)
keys = Object.keys(contractArtefacts)
}
if (keys.length === 1) return contractArtefacts[keys[0]]
else if (keys.length > 1) {
throw new Error(`There are multiple artifacts for contract "${contractName}", please use a fully qualified name.\n
Please replace ${contractName} for one of these options wherever you are trying to read its artifact: \n
${keys.join()}\n
OR just compile the required contract again`)
} else throw new Error(`Could not find artifacts for ${contractName}. Compile contract to generate artifacts.`)
}
} }
getCompilerAbstract (file) { getCompilerAbstract (file) {

@ -1,6 +1,7 @@
'use strict' 'use strict'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import { createHash } from 'crypto'
const profile = { const profile = {
name: 'compilerMetadata', name: 'compilerMetadata',
@ -28,10 +29,11 @@ export class CompilerMetadata extends Plugin {
onActivation () { onActivation () {
const self = this const self = this
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data) => { this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
if (!await this.call('settings', 'get', 'settings/generate-contract-metadata')) return if (!await this.call('settings', 'get', 'settings/generate-contract-metadata')) return
const compiler = new CompilerAbstract(languageVersion, data, source) const compiler = new CompilerAbstract(languageVersion, data, source, input)
const path = self._extractPathOf(source.target) const path = self._extractPathOf(source.target)
await this.setBuildInfo(version, input, data, path)
compiler.visitContracts((contract) => { compiler.visitContracts((contract) => {
if (contract.file !== source.target) return if (contract.file !== source.target) return
(async () => { (async () => {
@ -43,6 +45,23 @@ export class CompilerMetadata extends Plugin {
}) })
} }
async setBuildInfo (version, input, output, path) {
input = JSON.parse(input)
const solcLongVersion = version.replace('.Emscripten.clang', '')
const solcVersion = solcLongVersion.substring(0, solcLongVersion.indexOf('+commit'))
const format = 'hh-sol-build-info-1'
const json = JSON.stringify({
_format: format,
solcVersion,
solcLongVersion,
input
})
const id = createHash('md5').update(Buffer.from(json)).digest().toString('hex')
const buildFilename = this.joinPath(path, this.innerPath, 'build-info/' + id + '.json')
const buildData = {id, _format: format, solcVersion, solcLongVersion, input, output}
await this.call('fileManager', 'writeFile', buildFilename, JSON.stringify(buildData, null, '\t'))
}
_extractPathOf (file) { _extractPathOf (file) {
const reg = /(.*)(\/).*/ const reg = /(.*)(\/).*/
const path = reg.exec(file) const path = reg.exec(file)
@ -51,14 +70,15 @@ export class CompilerMetadata extends Plugin {
async _setArtefacts (content, contract, path) { async _setArtefacts (content, contract, path) {
content = content || '{}' content = content || '{}'
const fileName = this._JSONFileName(path, contract.name)
const metadataFileName = this._MetadataFileName(path, contract.name)
let metadata let metadata
try { try {
metadata = JSON.parse(content) metadata = JSON.parse(content)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
const fileName = this._JSONFileName(path, contract.name)
const metadataFileName = this._MetadataFileName(path, contract.name)
const deploy = metadata.deploy || {} const deploy = metadata.deploy || {}
this.networks.forEach((network) => { this.networks.forEach((network) => {

@ -42,7 +42,7 @@ export class EditorContextListener extends Plugin {
onActivation () { onActivation () {
this.on('editor', 'contentChanged', () => { this._stopHighlighting() }) this.on('editor', 'contentChanged', () => { this._stopHighlighting() })
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => { this.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => {
if (languageVersion.indexOf('soljson') !== 0) return if (languageVersion.indexOf('soljson') !== 0) return
this._stopHighlighting() this._stopHighlighting()
this._index = { this._index = {

@ -70,7 +70,7 @@ export class OffsetToLineColumnConverter extends Plugin {
* called by plugin API * called by plugin API
*/ */
activate () { activate () {
this.on('solidity', 'compilationFinished', () => { this.on('solidity', 'compilationFinished', (success, data, source, input, version) => {
this.clear() this.clear()
}) })
} }

@ -8,7 +8,7 @@ export class OffsetToColumnConverter {
constructor (compilerEvent) { constructor (compilerEvent) {
this.lineBreakPositionsByContent = {} this.lineBreakPositionsByContent = {}
if (compilerEvent) { if (compilerEvent) {
compilerEvent.register('compilationFinished', (success, data, source) => { compilerEvent.register('compilationFinished', (success, data, source, input, version) => {
this.clear() this.clear()
}) })
} }

@ -5,10 +5,12 @@ export class CompilerAbstract {
languageversion: any languageversion: any
data: any data: any
source: any source: any
constructor (languageversion, data, source) { input: any
constructor (languageversion, data, source, input?) {
this.languageversion = languageversion this.languageversion = languageversion
this.data = data this.data = data
this.source = source // source code this.source = source // source code
this.input = input // source code
} }
getContracts () { getContracts () {
@ -27,6 +29,10 @@ export class CompilerAbstract {
return this.data return this.data
} }
getInput () {
return this.input
}
getAsts () { getAsts () {
return this.data.sources // ast return this.data.sources // ast
} }

@ -12,8 +12,8 @@ export const compile = async (compilationTargets, settings, contentResolverCallb
compiler.set('language', settings.language) compiler.set('language', settings.language)
compiler.set('runs', settings.runs) compiler.set('runs', settings.runs)
compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version)) compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version))
compiler.event.register('compilationFinished', (success, compilationData, source) => { compiler.event.register('compilationFinished', (success, compilationData, source, input, version) => {
resolve(new CompilerAbstract(settings.version, compilationData, source)) resolve(new CompilerAbstract(settings.version, compilationData, source, input))
}) })
compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, '')) compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, ''))
}) })

@ -45,6 +45,7 @@ export default function (self) { // eslint-disable-line @typescript-eslint/expli
cmd: 'compiled', cmd: 'compiled',
job: data.job, job: data.job,
data: compileJSON(data.input), data: compileJSON(data.input),
input: data.input,
missingInputs: missingInputs missingInputs: missingInputs
}) })
} }

@ -37,7 +37,7 @@ export class Compiler {
} }
} }
this.event.register('compilationFinished', (success: boolean, data: CompilationResult, source: SourceWithTarget) => { this.event.register('compilationFinished', (success: boolean, data: CompilationResult, source: SourceWithTarget, input: string, version: string) => {
if (success && this.state.compilationStartTime) { if (success && this.state.compilationStartTime) {
this.event.trigger('compilationDuration', [(new Date().getTime()) - this.state.compilationStartTime]) this.event.trigger('compilationDuration', [(new Date().getTime()) - this.state.compilationStartTime])
} }
@ -70,7 +70,7 @@ export class Compiler {
this.gatherImports(files, missingInputs, (error, input) => { this.gatherImports(files, missingInputs, (error, input) => {
if (error) { if (error) {
this.state.lastCompilationResult = null this.state.lastCompilationResult = null
this.event.trigger('compilationFinished', [false, { error: { formattedMessage: error, severity: 'error' } }, files]) this.event.trigger('compilationFinished', [false, { error: { formattedMessage: error, severity: 'error' } }, files, input, this.state.currentVersion])
} else if (this.state.compileJSON && input) { this.state.compileJSON(input) } } else if (this.state.compileJSON && input) { this.state.compileJSON(input) }
}) })
} }
@ -111,16 +111,17 @@ export class Compiler {
return { error: 'Deferred import' } return { error: 'Deferred import' }
} }
let result: CompilationResult = {} let result: CompilationResult = {}
let input
try { try {
if (source && source.sources) { if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state const { optimize, runs, evmVersion, language } = this.state
const input = compilerInput(source.sources, { optimize, runs, evmVersion, language }) input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
result = JSON.parse(compiler.compile(input, { import: missingInputsCallback })) result = JSON.parse(compiler.compile(input, { import: missingInputsCallback }))
} }
} catch (exception) { } catch (exception) {
result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } } result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } }
} }
this.onCompilationFinished(result, missingInputs, source) this.onCompilationFinished(result, missingInputs, source, input, this.state.currentVersion)
} }
this.onCompilerLoaded(compiler.version()) this.onCompilerLoaded(compiler.version())
} }
@ -133,7 +134,7 @@ export class Compiler {
* @param source Source * @param source Source
*/ */
onCompilationFinished (data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget): void { onCompilationFinished (data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget, input?: string, version?: string): void {
let noFatalErrors = true // ie warnings are ok let noFatalErrors = true // ie warnings are ok
const checkIfFatalError = (error: CompilationError) => { const checkIfFatalError = (error: CompilationError) => {
@ -146,7 +147,7 @@ export class Compiler {
if (!noFatalErrors) { if (!noFatalErrors) {
// There are fatal errors, abort here // There are fatal errors, abort here
this.state.lastCompilationResult = null this.state.lastCompilationResult = null
this.event.trigger('compilationFinished', [false, data, source]) this.event.trigger('compilationFinished', [false, data, source, input, version])
} else if (missingInputs !== undefined && missingInputs.length > 0 && source && source.sources) { } else if (missingInputs !== undefined && missingInputs.length > 0 && source && source.sources) {
// try compiling again with the new set of inputs // try compiling again with the new set of inputs
this.internalCompile(source.sources, missingInputs) this.internalCompile(source.sources, missingInputs)
@ -159,7 +160,7 @@ export class Compiler {
source: source source: source
} }
} }
this.event.trigger('compilationFinished', [true, data, source]) this.event.trigger('compilationFinished', [true, data, source, input, version])
} }
} }
@ -182,16 +183,17 @@ export class Compiler {
return { error: 'Deferred import' } return { error: 'Deferred import' }
} }
let result: CompilationResult = {} let result: CompilationResult = {}
let input: string
try { try {
if (source && source.sources) { if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state const { optimize, runs, evmVersion, language } = this.state
const input = compilerInput(source.sources, { optimize, runs, evmVersion, language }) input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback })) result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback }))
} }
} catch (exception) { } catch (exception) {
result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } } result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } }
} }
this.onCompilationFinished(result, missingInputs, source) this.onCompilationFinished(result, missingInputs, source, input, version)
} }
this.onCompilerLoaded(version) this.onCompilerLoaded(version)
} }
@ -273,7 +275,7 @@ export class Compiler {
sources = jobs[data.job].sources sources = jobs[data.job].sources
delete jobs[data.job] delete jobs[data.job]
} }
this.onCompilationFinished(result, data.missingInputs, sources) this.onCompilationFinished(result, data.missingInputs, sources, data.input, this.state.currentVersion)
} }
break break
} }

@ -186,6 +186,7 @@ export interface MessageFromWorker {
cmd: string, cmd: string,
job?: number, job?: number,
missingInputs?: string[], missingInputs?: string[],
input?: any,
data?: string data?: string
} }

@ -142,7 +142,7 @@ export function compileFileOrFiles (filename: string, isDirectory: boolean, opts
}, },
function doCompilation (next) { function doCompilation (next) {
// @ts-ignore // @ts-ignore
compiler.event.register('compilationFinished', this, (success, data, source) => { compiler.event.register('compilationFinished', this, (success, data, source, input, version) => {
next(null, data) next(null, data)
}) })
compiler.compile(sources, filepath) compiler.compile(sources, filepath)
@ -201,10 +201,10 @@ export function compileContractSources (sources: SrcIfc, newCompConfig: any, imp
} }
}, },
(next) => { (next) => {
const compilationFinishedCb = (success, data, source) => { const compilationFinishedCb = (success, data, source, input, version) => {
// data.error usually exists for exceptions like worker error etc. // data.error usually exists for exceptions like worker error etc.
if (!data.error) UTRunner.compiler = compiler if (!data.error) UTRunner.compiler = compiler
if (opts && opts.event) opts.event.emit('compilationFinished', success, data, source) if (opts && opts.event) opts.event.emit('compilationFinished', success, data, source, input, version)
next(null, data) next(null, data)
} }
compiler.event.unregister('compilationFinished', compilationFinishedCb) compiler.event.unregister('compilationFinished', compilationFinishedCb)

@ -38,14 +38,6 @@ type sourceMarker = {
hide: boolean hide: boolean
} }
type sourceAnnotationMap = {
[key: string]: [sourceAnnotation];
}
type sourceMarkerMap = {
[key: string]: [sourceMarker];
}
loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } }) loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } })
/* eslint-disable-next-line */ /* eslint-disable-next-line */
@ -54,8 +46,6 @@ export interface EditorUIProps {
activated: boolean activated: boolean
themeType: string themeType: string
currentFile: string currentFile: string
sourceAnnotationsPerFile: sourceAnnotationMap
markerPerFile: sourceMarkerMap
events: { events: {
onBreakPointAdded: (file: string, line: number) => void onBreakPointAdded: (file: string, line: number) => void
onBreakPointCleared: (file: string, line: number) => void onBreakPointCleared: (file: string, line: number) => void
@ -71,17 +61,21 @@ export interface EditorUIProps {
getFontSize: () => number, getFontSize: () => number,
getValue: (uri: string) => string getValue: (uri: string) => string
getCursorPosition: () => cursorPosition getCursorPosition: () => cursorPosition
addMarkerPerFile: (marker: sourceMarker, filePath: string) => void
addSourceAnnotationsPerFile: (annotations: sourceAnnotation, filePath: string) => void
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string) => void
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string) => void
} }
} }
export const EditorUI = (props: EditorUIProps) => { export const EditorUI = (props: EditorUIProps) => {
const [, setCurrentBreakpoints] = useState({}) const [, setCurrentBreakpoints] = useState({})
const [currentAnnotations, setCurrentAnnotations] = useState({})
const [currentMarkers, setCurrentMarkers] = useState({})
const editorRef = useRef(null) const editorRef = useRef(null)
const monacoRef = useRef(null) const monacoRef = useRef(null)
const currentFileRef = useRef('') const currentFileRef = useRef('')
const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
const registeredDecorations = useRef({}) // registered decorations
const [editorModelsState, dispatch] = useReducer(reducerActions, initialState) const [editorModelsState, dispatch] = useReducer(reducerActions, initialState)
const formatColor = (name) => { const formatColor = (name) => {
@ -226,55 +220,6 @@ export const EditorUI = (props: EditorUIProps) => {
defineAndSetTheme(monacoRef.current) defineAndSetTheme(monacoRef.current)
}) })
const setAnnotationsbyFile = (uri) => {
if (props.sourceAnnotationsPerFile[uri]) {
const model = editorModelsState[uri]?.model
const newAnnotations = []
for (const annotation of props.sourceAnnotationsPerFile[uri]) {
if (!annotation.hide) {
newAnnotations.push({
range: new monacoRef.current.Range(annotation.row + 1, 1, annotation.row + 1, 1),
options: {
isWholeLine: false,
glyphMarginHoverMessage: { value: (annotation.from ? `from ${annotation.from}:\n` : '') + annotation.text },
glyphMarginClassName: `fal fa-exclamation-square text-${annotation.type === 'error' ? 'danger' : (annotation.type === 'warning' ? 'warning' : 'info')}`
}
})
}
}
setCurrentAnnotations(prevState => {
prevState[uri] = model.deltaDecorations(currentAnnotations[uri] || [], newAnnotations)
return prevState
})
}
}
const setMarkerbyFile = (uri) => {
if (props.markerPerFile[uri]) {
const model = editorModelsState[uri]?.model
const newMarkers = []
for (const marker of props.markerPerFile[uri]) {
if (!marker.hide) {
let isWholeLine = false
if (marker.position.start.line === marker.position.end.line && marker.position.end.column - marker.position.start.column < 3) {
// in this case we force highlighting the whole line (doesn't make sense to highlight 2 chars)
isWholeLine = true
}
newMarkers.push({
range: new monacoRef.current.Range(marker.position.start.line + 1, marker.position.start.column + 1, marker.position.end.line + 1, marker.position.end.column + 1),
options: {
isWholeLine,
inlineClassName: `alert-info border-0 highlightLine${marker.position.start.line + 1}`
}
})
}
}
setCurrentMarkers(prevState => {
prevState[uri] = model.deltaDecorations(currentMarkers[uri] || [], newMarkers)
return prevState
})
}
}
useEffect(() => { useEffect(() => {
if (!editorRef.current) return if (!editorRef.current) return
@ -286,18 +231,91 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity') monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
} else if (file.language === 'cairo') { } else if (file.language === 'cairo') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-cairo') monacoRef.current.editor.setModelLanguage(file.model, 'remix-cairo')
} }
setAnnotationsbyFile(props.currentFile)
setMarkerbyFile(props.currentFile)
}, [props.currentFile]) }, [props.currentFile])
useEffect(() => { const convertToMonacoDecoration = (decoration: sourceAnnotation | sourceMarker, typeOfDecoration: string) => {
setAnnotationsbyFile(props.currentFile) if (typeOfDecoration === 'sourceAnnotationsPerFile') {
}, [JSON.stringify(props.sourceAnnotationsPerFile)]) decoration = decoration as sourceAnnotation
return {
type: typeOfDecoration,
range: new monacoRef.current.Range(decoration.row + 1, 1, decoration.row + 1, 1),
options: {
isWholeLine: false,
glyphMarginHoverMessage: { value: (decoration.from ? `from ${decoration.from}:\n` : '') + decoration.text },
glyphMarginClassName: `fal fa-exclamation-square text-${decoration.type === 'error' ? 'danger' : (decoration.type === 'warning' ? 'warning' : 'info')}`
}
}
}
if (typeOfDecoration === 'markerPerFile') {
decoration = decoration as sourceMarker
let isWholeLine = false
if (decoration.position.start.line === decoration.position.end.line && decoration.position.end.column - decoration.position.start.column < 3) {
// in this case we force highlighting the whole line (doesn't make sense to highlight 2 chars)
isWholeLine = true
}
return {
type: typeOfDecoration,
range: new monacoRef.current.Range(decoration.position.start.line + 1, decoration.position.start.column + 1, decoration.position.end.line + 1, decoration.position.end.column + 1),
options: {
isWholeLine,
inlineClassName: `alert-info border-0 highlightLine${decoration.position.start.line + 1}`
}
}
}
}
props.editorAPI.clearDecorationsByPlugin = (filePath: string, plugin: string, typeOfDecoration: string) => {
const model = editorModelsState[filePath]?.model
if (!model) return
const decorations = []
const newRegisteredDecorations = []
if (registeredDecorations.current[filePath]) {
for (const decoration of registeredDecorations.current[filePath]) {
if (decoration.type === typeOfDecoration && decoration.value.from !== plugin) {
decorations.push(convertToMonacoDecoration(decoration.value, typeOfDecoration))
newRegisteredDecorations.push(decoration)
}
}
}
currentDecorations.current[typeOfDecoration][filePath] = model.deltaDecorations(currentDecorations.current[typeOfDecoration][filePath], decorations)
registeredDecorations.current[filePath] = newRegisteredDecorations
}
props.editorAPI.keepDecorationsFor = (filePath: string, plugin: string, typeOfDecoration: string) => {
const model = editorModelsState[filePath]?.model
if (!model) return
const decorations = []
if (registeredDecorations.current[filePath]) {
for (const decoration of registeredDecorations.current[filePath]) {
if (decoration.type === typeOfDecoration && decoration.value.from === plugin) {
decorations.push(convertToMonacoDecoration(decoration.value, typeOfDecoration))
}
}
}
currentDecorations.current[typeOfDecoration][filePath] = model.deltaDecorations(currentDecorations.current[typeOfDecoration][filePath], decorations)
}
const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => {
const model = editorModelsState[filePath]?.model
if (!model) return
const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration)
if (!registeredDecorations.current[filePath]) registeredDecorations.current[filePath] = []
registeredDecorations.current[filePath].push({ value: decoration, type: typeOfDecoration })
if (!currentDecorations.current[typeOfDecoration][filePath]) currentDecorations.current[typeOfDecoration][filePath] = []
currentDecorations.current[typeOfDecoration][filePath].push(...model.deltaDecorations([], [monacoDecoration]))
}
props.editorAPI.addSourceAnnotationsPerFile = (annotation: sourceAnnotation, filePath: string) => {
addDecoration(annotation, filePath, 'sourceAnnotationsPerFile')
}
props.editorAPI.addMarkerPerFile = (marker: sourceMarker, filePath: string) => {
addDecoration(marker, filePath, 'markerPerFile')
}
useEffect(() => {
setMarkerbyFile(props.currentFile)
}, [JSON.stringify(props.markerPerFile)])
props.editorAPI.findMatches = (uri: string, value: string) => { props.editorAPI.findMatches = (uri: string, value: string) => {
if (!editorRef.current) return if (!editorRef.current) return

@ -66,7 +66,7 @@ const setupEvents = () => {
plugin.on('manager', 'pluginDeactivated', removePluginProvider.bind(plugin)) plugin.on('manager', 'pluginDeactivated', removePluginProvider.bind(plugin))
plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult(file, source, languageVersion, data, input))
plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data))
@ -301,9 +301,9 @@ export const signMessageWithAddress = (account: string, message: string, modalCo
}) })
} }
const broadcastCompilationResult = (file, source, languageVersion, data) => { const broadcastCompilationResult = (file, source, languageVersion, data, input?) => {
// TODO check whether the tab is configured // TODO check whether the tab is configured
const compiler = new CompilerAbstract(languageVersion, data, source) const compiler = new CompilerAbstract(languageVersion, data, source, input)
plugin.compilersArtefacts[languageVersion] = compiler plugin.compilersArtefacts[languageVersion] = compiler
plugin.compilersArtefacts.__last = compiler plugin.compilersArtefacts.__last = compiler

@ -50,7 +50,7 @@ export const listenToEvents = (compileTabLogic: CompileTabLogic, api) => (dispat
dispatch(setCompilerMode('compilerLoaded')) dispatch(setCompilerMode('compilerLoaded'))
}) })
compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => { compileTabLogic.compiler.event.register('compilationFinished', (success, data, source, input, version) => {
dispatch(setCompilerMode('compilationFinished', success, data, source)) dispatch(setCompilerMode('compilationFinished', success, data, source, input, version))
}) })
} }

@ -5,9 +5,9 @@ export const compilation = (analysisModule, dispatch) => {
analysisModule.on( analysisModule.on(
'solidity', 'solidity',
'compilationFinished', 'compilationFinished',
(file, source, languageVersion, data) => { (file, source, languageVersion, data, input, version) => {
if (languageVersion.indexOf('soljson') !== 0) return if (languageVersion.indexOf('soljson') !== 0) return
dispatch({ type: 'compilationFinished', payload: { file, source, languageVersion, data } }) dispatch({ type: 'compilationFinished', payload: { file, source, languageVersion, data, input, version } })
} }
) )
} }

@ -13,7 +13,9 @@ export const analysisReducer = (state, action) => {
file: action.payload.file, file: action.payload.file,
source: action.payload.source, source: action.payload.source,
languageVersion: action.payload.languageVersion, languageVersion: action.payload.languageVersion,
data: action.payload.data data: action.payload.data,
input: action.payload.input,
version: action.payload.version
} }
default: default:
return initialState return initialState

@ -544,7 +544,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
) )
} else { } else {
return ( return (
<div className={classNameBlock} data-id="block" key={i}><span className={x.style}>{ msg ? msg.toString().replace(/,/g, '') : msg }</span></div> <div className={classNameBlock} data-id="block" key={i}><span className={x.style}>{msg? msg.toString() : null}</span></div>
) )
} }
}) })

Loading…
Cancel
Save