Remove unused code files

pull/5370/head
ioedeveloper 3 years ago
parent 2c5deec864
commit 4fe15231aa
  1. 702
      apps/remix-ide/src/app/files/file-explorer.js
  2. 1
      apps/remix-ide/src/app/tabs/compile-tab.js
  3. 580
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  4. 51
      libs/remix-ui/solidity-compiler/src/lib/logic/compiler-abstract.ts
  5. 19
      libs/remix-ui/solidity-compiler/src/lib/logic/compiler-helpers.ts
  6. 46
      libs/remix-ui/solidity-compiler/src/lib/logic/compiler-utils.ts
  7. 3
      libs/remix-ui/solidity-compiler/src/lib/logic/index.ts

@ -1,702 +0,0 @@
/* global FileReader */
/* global fetch */
const async = require('async')
const Gists = require('gists')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const tooltip = require('../ui/tooltip')
const QueryParams = require('../../lib/query-params')
const helper = require('../../lib/helper')
const yo = require('yo-yo')
const Treeview = require('../ui/TreeView')
const modalDialog = require('../ui/modaldialog')
const EventManager = require('events')
const contextMenu = require('../ui/contextMenu')
const css = require('./styles/file-explorer-styles')
const globalRegistry = require('../../global/registry')
const queryParams = new QueryParams()
let MENU_HANDLE
function fileExplorer (localRegistry, files, menuItems, plugin) {
var self = this
this.events = new EventManager()
// file provider backend
this.files = files
// element currently focused on
this.focusElement = null
// path currently focused on
this.focusPath = null
const allItems =
[
{
action: 'createNewFile',
title: 'Create New File',
icon: 'fas fa-plus-circle'
},
{
action: 'publishToGist',
title: 'Publish all [browser] explorer files to a github gist',
icon: 'fab fa-github'
},
{
action: 'uploadFile',
title: 'Add Local file to the Browser Storage Explorer',
icon: 'far fa-folder-open'
},
{
action: 'updateGist',
title: 'Update the current [gist] explorer',
icon: 'fab fa-github'
}
]
// menu items
this.menuItems = allItems.filter(
(item) => {
if (menuItems) return menuItems.find((name) => { return name === item.action })
}
)
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._deps = {
config: self._components.registry.get('config').api,
editor: self._components.registry.get('editor').api,
fileManager: self._components.registry.get('filemanager').api
}
self.events.register('focus', function (path) {
self._deps.fileManager.open(path)
})
self._components.registry.put({ api: self, name: `fileexplorer/${self.files.type}` })
// warn if file changed outside of Remix
function remixdDialog () {
return yo`<div>This file has been changed outside of Remix IDE.</div>`
}
this.files.event.register('fileExternallyChanged', (path, file) => {
if (self._deps.config.get('currentFile') === path && self._deps.editor.currentContent() && self._deps.editor.currentContent() !== file.content) {
if (this.files.isReadOnly(path)) return self._deps.editor.setText(file.content)
modalDialog(path + ' changed', remixdDialog(),
{
label: 'Replace by the new content',
fn: () => {
self._deps.editor.setText(file.content)
}
},
{
label: 'Keep the content displayed in Remix',
fn: () => {}
}
)
}
})
// register to event of the file provider
files.event.on('fileRemoved', fileRemoved)
files.event.on('fileRenamed', fileRenamed)
files.event.on('fileRenamedError', fileRenamedError)
files.event.on('fileAdded', fileAdded)
files.event.on('folderAdded', folderAdded)
function fileRenamedError (error) {
modalDialogCustom.alert(error)
}
function fileAdded (filepath) {
self.ensureRoot(() => {
const folderpath = filepath.split('/').slice(0, -1).join('/')
const currentTree = self.treeView.nodeAt(folderpath)
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
if (currentTree) {
self.files.resolveDirectory(folderpath, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
fileTree = normalize(folderpath, fileTree)
self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
self.focusElement = self.treeView.labelAt(self.focusPath)
// TODO: here we update the selected file (it applicable)
// cause we are refreshing the interface of the whole directory when there's a new file.
if (self.focusElement && !self.focusElement.classList.contains('bg-secondary')) {
self.focusElement.classList.add('bg-secondary')
}
})
}
})
}
function extractNameFromKey (key) {
const keyPath = key.split('/')
return keyPath[keyPath.length - 1]
}
function folderAdded (folderpath) {
self.ensureRoot(() => {
folderpath = folderpath.split('/').slice(0, -1).join('/')
self.files.resolveDirectory(folderpath, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
fileTree = normalize(folderpath, fileTree)
self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
})
})
}
function fileRemoved (filepath) {
const label = self.treeView.labelAt(filepath)
filepath = filepath.split('/').slice(0, -1).join('/')
if (label && label.parentElement) {
label.parentElement.removeChild(label)
}
self.updatePath(filepath)
}
function fileRenamed (oldName, newName, isFolder) {
fileRemoved(oldName)
fileAdded(newName)
}
// make interface and register to nodeClick, leafClick
self.treeView = new Treeview({
extractData: function extractData (value, tree, key) {
var newValue = {}
// var isReadOnly = false
var isFile = false
Object.keys(value).filter(function keep (x) {
if (x === '/content') isFile = true
if (x[0] !== '/') return true
}).forEach(function (x) { newValue[x] = value[x] })
return {
path: (tree || {}).path ? tree.path + '/' + key : key,
children: isFile ? undefined
: value instanceof Array ? value.map((item, index) => ({
key: index, value: item
})) : value instanceof Object ? Object.keys(value).map(subkey => ({
key: subkey, value: value[subkey]
})) : undefined
}
},
formatSelf: function formatSelf (key, data, li) {
const isRoot = data.path === self.files.type
const isFolder = !!data.children
return yo`
<div class="${css.items}">
<span
title="${data.path}"
class="${css.label} ${!isRoot ? !isFolder ? css.leaf : css.folder : ''}"
data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}"
onkeydown=${editModeOff}
onblur=${editModeOff}
>
${key.split('/').pop()}
</span>
${isRoot ? self.renderMenuItems() : ''}
</div>
`
}
})
/**
* Extracts first two folders as a subpath from the path.
**/
function extractExternalFolder (path) {
const firstSlIndex = path.indexOf('/', 1)
if (firstSlIndex === -1) return ''
const secondSlIndex = path.indexOf('/', firstSlIndex + 1)
if (secondSlIndex === -1) return ''
return path.substring(0, secondSlIndex)
}
self.treeView.event.register('nodeRightClick', function (key, data, label, event) {
if (self.files.readonly) return
if (key === self.files.type) return
MENU_HANDLE && MENU_HANDLE.hide(null, true)
const actions = {}
const provider = self._deps.fileManager.fileProviderOf(key)
actions['Create File'] = () => self.createNewFile(key)
actions['Create Folder'] = () => self.createNewFolder(key)
// @todo(#2386) not fully implemented. Readd later when fixed
if (provider.isExternalFolder(key)) {
/* actions['Discard changes'] = () => {
modalDialogCustom.confirm(
'Discard changes',
'Are you sure you want to discard all your changes?',
() => { self.files.discardChanges(key) },
() => {}
)
} */
} else {
const folderPath = extractExternalFolder(key)
actions.Rename = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot rename folder. ' + self.files.type + ' is a read only explorer') }
var name = label.querySelector('span[data-path="' + key + '"]')
if (name) editModeOn(name)
}
actions.Delete = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') }
const currentFoldername = extractNameFromKey(key)
modalDialogCustom.confirm('Confirm to delete folder', `Are you sure you want to delete ${currentFoldername} folder?`,
async () => {
const fileManager = self._deps.fileManager
const removeFolder = await fileManager.remove(key)
if (!removeFolder) {
tooltip(`failed to remove ${key}. Make sure the directory is empty before removing it.`)
}
}, () => {})
}
if (folderPath === 'browser/gists') {
actions['Push changes to gist'] = () => {
const id = key.substr(key.lastIndexOf('/') + 1, key.length - 1)
modalDialogCustom.confirm(
'Push back to Gist',
'Are you sure you want to push all your changes back to Gist?',
() => { self.toGist(id) },
() => {}
)
}
}
}
MENU_HANDLE = contextMenu(event, actions)
})
self.treeView.event.register('leafRightClick', function (key, data, label, event) {
if (key === self.files.type) return
MENU_HANDLE && MENU_HANDLE.hide(null, true)
const actions = {}
const provider = self._deps.fileManager.fileProviderOf(key)
if (!provider.isExternalFolder(key)) {
actions['Create Folder'] = () => self.createNewFolder(self._deps.fileManager.extractPathOf(key))
actions.Rename = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot rename file. ' + self.files.type + ' is a read only explorer') }
var name = label.querySelector('span[data-path="' + key + '"]')
if (name) editModeOn(name)
}
actions.Delete = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') }
const currentFilename = extractNameFromKey(key)
modalDialogCustom.confirm(
'Delete file', `Are you sure you want to delete ${currentFilename} file?`,
async () => {
const fileManager = self._deps.fileManager
const removeFile = await fileManager.remove(key)
if (!removeFile) {
tooltip(`Failed to remove file ${key}.`)
}
},
() => {}
)
}
if (key.endsWith('.js')) {
actions.Run = async () => {
provider.get(key, (error, content) => {
if (error) return console.log(error)
plugin.call('scriptRunner', 'execute', content)
})
}
}
}
MENU_HANDLE = contextMenu(event, actions)
})
self.treeView.event.register('leafClick', function (key, data, label) {
self.events.trigger('focus', [key])
})
self.treeView.event.register('nodeClick', function (path, childrenContainer) {
if (!childrenContainer) return
if (childrenContainer.style.display === 'none') return
self.updatePath(path)
})
// register to main app, trigger when the current file in the editor changed
self._deps.fileManager.events.on('currentFileChanged', (newFile) => {
const provider = self._deps.fileManager.fileProviderOf(newFile)
if (self.focusElement && self.focusPath !== newFile) {
self.focusElement.classList.remove('bg-secondary')
self.focusElement = null
self.focusPath = null
}
if (provider && (provider.type === files.type)) {
self.focusElement = self.treeView.labelAt(newFile)
if (self.focusElement) {
self.focusElement.classList.add('bg-secondary')
self.focusPath = newFile
}
}
})
self._deps.fileManager.events.on('noFileSelected', () => {
if (self.focusElement) {
self.focusElement.classList.remove('bg-secondary')
self.focusElement = null
self.focusPath = null
}
})
var textUnderEdit = null
function selectElementContents (el) {
var range = document.createRange()
range.selectNodeContents(el)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
}
function editModeOn (label) {
textUnderEdit = label.innerText
label.setAttribute('contenteditable', true)
label.classList.add('bg-light')
label.focus()
selectElementContents(label)
}
function editModeOff (event) {
const label = this
const isFolder = label.className.indexOf('folder') !== -1
function rename () {
var newPath = label.dataset.path
newPath = newPath.split('/')
newPath[newPath.length - 1] = label.innerText
newPath = newPath.join('/')
if (label.innerText === '') {
modalDialogCustom.alert('File name cannot be empty')
label.innerText = textUnderEdit
} else if (helper.checkSpecialChars(label.innerText)) {
modalDialogCustom.alert('Special characters are not allowed')
label.innerText = textUnderEdit
} else {
files.exists(newPath, (error, exist) => {
if (error) return modalDialogCustom.alert('Unexpected error while renaming: ' + error)
if (!exist) {
files.rename(label.dataset.path, newPath, isFolder)
} else {
modalDialogCustom.alert('File already exists.')
label.innerText = textUnderEdit
}
})
}
}
if (event.which === 13) event.preventDefault()
if ((event.type === 'blur' || event.which === 13) && label.getAttribute('contenteditable')) {
var save = textUnderEdit !== label.innerText
if (save) {
modalDialogCustom.confirm(
'Confirm to rename a ' + (isFolder ? 'folder' : 'file'),
'Are you sure you want to rename ' + textUnderEdit + '?',
() => { rename() },
() => { label.innerText = textUnderEdit }
)
}
label.removeAttribute('contenteditable')
label.classList.remove('bg-light')
}
}
}
fileExplorer.prototype.updatePath = function (path) {
this.files.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
var newTree = normalize(path, fileTree)
this.treeView.updateNodeFromJSON(path, newTree, true)
})
}
fileExplorer.prototype.hide = function () {
if (this.container) this.container.style.display = 'none'
}
fileExplorer.prototype.show = function () {
if (this.container) this.container.style.display = 'block'
}
fileExplorer.prototype.init = function () {
this.container = yo`<div></div>`
return this.container
}
fileExplorer.prototype.publishToGist = function () {
modalDialogCustom.confirm(
'Create a public gist',
'Are you sure you want to publish all your files in browser directory anonymously as a public gist on github.com? Note: this will not include directories.',
() => { this.toGist() }
)
}
fileExplorer.prototype.uploadFile = function (event) {
// TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module.
const self = this
;[...event.target.files].forEach((file) => {
const files = this.files
function loadFile () {
var fileReader = new FileReader()
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
modalDialogCustom.alert('Special characters are not allowed')
return
}
var success = await files.set(name, event.target.result)
if (!success) {
modalDialogCustom.alert('Failed to create file ' + name)
} else {
self.events.trigger('focus', [name])
}
}
fileReader.readAsText(file)
}
var name = files.type + '/' + file.name
files.exists(name, (error, exist) => {
if (error) console.log(error)
if (!exist) {
loadFile()
} else {
modalDialogCustom.confirm('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
}
})
})
}
fileExplorer.prototype.toGist = function (id) {
const proccedResult = function (error, data) {
if (error) {
modalDialogCustom.alert('Failed to manage gist: ' + error)
console.log('Failed to manage gist: ' + error)
} else {
if (data.html_url) {
modalDialogCustom.confirm('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => {
window.open(data.html_url, '_blank')
})
} else {
modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
}
}
}
/**
* This function is to get the original content of given gist
* @params id is the gist id to fetch
*/
async function getOriginalFiles (id) {
if (!id) {
return []
}
const url = `https://api.github.com/gists/${id}`
const res = await fetch(url)
const data = await res.json()
return data.files || []
}
// If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
const folder = id ? '/gists/' + id : '/'
this.packageFiles(this.files, folder, (error, packaged) => {
if (error) {
console.log(error)
modalDialogCustom.alert('Failed to create gist: ' + error.message)
} else {
// check for token
var tokenAccess = this._deps.config.get('settings/gist-access-token')
if (!tokenAccess) {
modalDialogCustom.alert(
'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.'
)
} else {
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
const gists = new Gists({ token: tokenAccess })
if (id) {
const originalFileList = getOriginalFiles(id)
// Telling the GIST API to remove files
const updatedFileList = Object.keys(packaged)
const allItems = Object.keys(originalFileList)
.filter(fileName => updatedFileList.indexOf(fileName) === -1)
.reduce((acc, deleteFileName) => ({
...acc,
[deleteFileName]: null
}), originalFileList)
// adding new files
updatedFileList.forEach((file) => {
const _items = file.split('/')
const _fileName = _items[_items.length - 1]
allItems[_fileName] = packaged[file]
})
tooltip('Saving gist (' + id + ') ...')
gists.edit({
description: description,
public: true,
files: allItems,
id: id
}, (error, result) => {
proccedResult(error, result)
if (!error) {
for (const key in allItems) {
if (allItems[key] === null) delete allItems[key]
}
}
})
} else {
// id is not existing, need to create a new gist
tooltip('Creating a new gist ...')
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
proccedResult(error, result)
})
}
}
}
})
}
// return all the files, except the temporary/readonly ones..
fileExplorer.prototype.packageFiles = function (filesProvider, directory, callback) {
const ret = {}
filesProvider.resolveDirectory(directory, (error, files) => {
if (error) callback(error)
else {
async.eachSeries(Object.keys(files), (path, cb) => {
if (filesProvider.isDirectory(path)) {
cb()
} else {
filesProvider.get(path, (error, content) => {
if (error) return cb(error)
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
ret[path] = { content }
cb()
})
}
}, (error) => {
callback(error, ret)
})
}
})
}
fileExplorer.prototype.createNewFile = function (parentFolder = '/') {
const self = this
modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
if (!input) input = 'New file'
helper.createNonClashingName(parentFolder + '/' + input, self.files, async (error, newName) => {
if (error) return tooltip('Failed to create file ' + newName + ' ' + error)
const fileManager = self._deps.fileManager
const createFile = await fileManager.writeFile(newName, '')
if (!createFile) {
tooltip('Failed to create file ' + newName)
} else {
await fileManager.open(newName)
if (newName.includes('_test.sol')) {
self.events.trigger('newTestFileCreated', [newName])
}
}
})
}, null, true)
}
fileExplorer.prototype.createNewFolder = function (parentFolder) {
const self = this
modalDialogCustom.prompt('Create new folder', '', 'New folder', (input) => {
if (!input) {
return tooltip('Failed to create folder. The name can not be empty')
}
const currentPath = !parentFolder ? self._deps.fileManager.currentPath() : parentFolder
let newName = currentPath ? currentPath + '/' + input : self.files.type + '/' + input
newName = newName + '/'
self.files.exists(newName, (error, exist) => {
if (error) return tooltip('Unexpected error while creating folder: ' + error)
if (!exist) {
self.files.set(newName, '')
} else {
tooltip('Folder already exists.', () => {})
}
})
}, null, true)
}
fileExplorer.prototype.renderMenuItems = function () {
let items = ''
if (this.menuItems) {
items = this.menuItems.map(({ action, title, icon }) => {
if (action === 'uploadFile') {
return yo`
<label
id=${action}
data-id="fileExplorerUploadFile${action}"
class="${icon} mb-0 ${css.newFile}"
title="${title}"
>
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onchange=${(event) => {
event.stopPropagation()
this.uploadFile(event)
}} multiple />
</label>
`
} else {
return yo`
<span
id=${action}
data-id="fileExplorerNewFile${action}"
onclick=${(event) => { event.stopPropagation(); this[action]() }}
class="newFile ${icon} ${css.newFile}"
title=${title}
>
</span>
`
}
})
}
return yo`<span class=" ${css.menu}">${items}</span>`
}
fileExplorer.prototype.ensureRoot = function (cb) {
cb = cb || (() => {})
var self = this
if (self.element) return cb()
const root = {}
root[this.files.type] = {}
var element = self.treeView.render(root, false)
element.classList.add(css.fileexplorer)
element.events = self.events
element.api = self.api
self.container.appendChild(element)
self.element = element
if (cb) cb()
self.treeView.expand(self.files.type)
}
function normalize (path, filesList) {
var prefix = path.split('/')[0]
var newList = {}
Object.keys(filesList).forEach(key => {
newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true }
})
return newList
}
module.exports = fileExplorer

@ -24,7 +24,6 @@ const profile = {
documentation: 'https://remix-ide.readthedocs.io/en/latest/solidity_editor.html', documentation: 'https://remix-ide.readthedocs.io/en/latest/solidity_editor.html',
version: packageJson.version, version: packageJson.version,
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile'] methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile']
} }
// EditorApi: // EditorApi:

@ -1,580 +0,0 @@
import toaster from '../../ui/tooltip'
import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '@remix-project/remix-solidity'
const yo = require('yo-yo')
const helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip')
const semver = require('semver')
const modalDialogCustom = require('../../ui/modal-dialog-custom')
const css = require('../styles/compile-tab-styles')
const _paq = window._paq = window._paq || []
class CompilerContainer {
constructor (compileTabLogic, editor, config, queryParams) {
this._view = {}
this.compileTabLogic = compileTabLogic
this.editor = editor
this.config = config
this.queryParams = queryParams
this.hhCompilation = false
this.data = {
hideWarnings: config.get('hideWarnings') || false,
autoCompile: config.get('autoCompile'),
compileTimeout: null,
timeout: 300,
allversions: null,
selectedVersion: null,
defaultVersion: 'soljson-v0.8.4+commit.c7e474f2.js' // this default version is defined: in makeMockCompiler (for browser test)
}
}
/**
* Update the compilation button with the name of the current file
*/
set currentFile (name = '') {
if (name && name !== '') {
this._setCompilerVersionFromPragma(name)
}
if (!this._view.compilationButton) return
const button = this.compilationButton(name.split('/').pop())
this._disableCompileBtn(!name || (name && !this.isSolFileSelected(name)))
yo.update(this._view.compilationButton, button)
}
isSolFileSelected (currentFile = '') {
if (!currentFile) currentFile = this.config.get('currentFile')
if (!currentFile) return false
const extention = currentFile.substr(currentFile.length - 3, currentFile.length)
return extention.toLowerCase() === 'sol' || extention.toLowerCase() === 'yul'
}
deactivate () {
// deactivate editor listeners
this.editor.event.unregister('contentChanged')
this.editor.event.unregister('sessionSwitched')
}
activate () {
this.currentFile = this.config.get('currentFile')
this.listenToEvents()
}
listenToEvents () {
this.editor.event.register('sessionSwitched', () => {
if (!this._view.compileIcon) return
this.scheduleCompilation()
})
this.compileTabLogic.event.on('startingCompilation', () => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', 'compiling...')
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`)
this._view.compileIcon.classList.add(`${css.spinningIcon}`)
})
this.compileTabLogic.compiler.event.register('compilationDuration', (speed) => {
if (!this._view.warnCompilationSlow) return
if (speed > 1000) {
const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.`
this._view.warnCompilationSlow.setAttribute('title', msg)
this._view.warnCompilationSlow.style.visibility = 'visible'
} else {
this._view.warnCompilationSlow.style.visibility = 'hidden'
}
})
this.editor.event.register('contentChanged', () => {
if (!this._view.compileIcon) return
this.scheduleCompilation()
this._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab
})
this.compileTabLogic.compiler.event.register('loadingCompiler', () => {
if (!this._view.compileIcon) return
this._disableCompileBtn(true)
this._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.')
this._view.compileIcon.classList.add(`${css.spinningIcon}`)
this._view.warnCompilationSlow.style.visibility = 'hidden'
this._updateLanguageSelector()
})
this.compileTabLogic.compiler.event.register('compilerLoaded', () => {
if (!this._view.compileIcon) return
this._disableCompileBtn(false)
this._view.compileIcon.setAttribute('title', '')
this._view.compileIcon.classList.remove(`${css.spinningIcon}`)
if (this.data.autoCompile) this.compileIfAutoCompileOn()
})
this.compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', 'idle')
this._view.compileIcon.classList.remove(`${css.spinningIcon}`)
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`)
_paq.push(['trackEvent', 'compiler', 'compiled_with_version', this._retrieveVersion()])
})
}
/**************
* SUBCOMPONENT
*/
compilationButton (name = '') {
const displayed = name || '<no file selected>'
const disabled = name && this.isSolFileSelected() ? '' : 'disabled'
return yo`
<button id="compileBtn" data-id="compilerContainerCompileBtn" class="btn btn-primary btn-block ${disabled} mt-3" title="Compile" onclick="${this.compile.bind(this)}">
<span>${this._view.compileIcon} Compile ${displayed}</span>
</button>
`
}
_disableCompileBtn (shouldDisable) {
const btn = document.getElementById('compileBtn')
if (!btn) return
if (shouldDisable) {
btn.classList.add('disabled')
} else if (this.isSolFileSelected()) {
btn.classList.remove('disabled')
}
}
// Load solc compiler version according to pragma in contract file
_setCompilerVersionFromPragma (filename) {
if (!this.data.allversions) return
this.compileTabLogic.fileManager.readFile(filename).then(data => {
const pragmaArr = data.match(/(pragma solidity (.+?);)/g)
if (pragmaArr && pragmaArr.length === 1) {
const pragmaStr = pragmaArr[0].replace('pragma solidity', '').trim()
const pragma = pragmaStr.substring(0, pragmaStr.length - 1)
const releasedVersions = this.data.allversions.filter(obj => !obj.prerelease).map(obj => obj.version)
const allVersions = this.data.allversions.map(obj => this._retrieveVersion(obj.version))
const currentCompilerName = this._retrieveVersion(this._view.versionSelector.selectedOptions[0].label)
// contains only numbers part, for example '0.4.22'
const pureVersion = this._retrieveVersion()
// is nightly build newer than the last release
const isNewestNightly = currentCompilerName.includes('nightly') && semver.gt(pureVersion, releasedVersions[0])
// checking if the selected version is in the pragma range
const isInRange = semver.satisfies(pureVersion, pragma)
// checking if the selected version is from official compilers list(excluding custom versions) and in range or greater
const isOfficial = allVersions.includes(currentCompilerName)
if (isOfficial && (!isInRange && !isNewestNightly)) {
const compilerToLoad = semver.maxSatisfying(releasedVersions, pragma)
const compilerPath = this.data.allversions.filter(obj => !obj.prerelease && obj.version === compilerToLoad)[0].path
if (this.data.selectedVersion !== compilerPath) {
this.data.selectedVersion = compilerPath
this._updateVersionSelector()
}
}
}
})
}
_retrieveVersion (version) {
if (!version) version = this._view.versionSelector.value
if (version === 'builtin') version = this.data.defaultVersion
return semver.coerce(version) ? semver.coerce(version).version : ''
}
render () {
this.compileTabLogic.compiler.event.register('compilerLoaded', (version) => this.setVersionText(version))
this.fetchAllVersion((allversions, selectedVersion, isURL) => {
this.data.allversions = allversions
if (isURL) this._updateVersionSelector(selectedVersion)
else {
this.data.selectedVersion = selectedVersion
if (this._view.versionSelector) this._updateVersionSelector()
}
})
this.hardhatCompilation = yo`<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox" style="display:none">
<input class="${css.autocompile} custom-control-input" onchange=${(e) => this.updatehhCompilation(e)} id="enableHardhat" type="checkbox" title="Enable Hardhat Compilation">
<label class="form-check-label custom-control-label" for="enableHardhat">Enable Hardhat Compilation</label>
</div>`
this._view.warnCompilationSlow = yo`<i title="Compilation Slow" style="visibility:hidden" class="${css.warnCompilationSlow} fas fa-exclamation-triangle" aria-hidden="true"></i>`
this._view.compileIcon = yo`<i class="fas fa-sync ${css.icon}" aria-hidden="true"></i>`
this._view.autoCompile = yo`<input class="${css.autocompile} custom-control-input" onchange=${() => this.updateAutoCompile()} data-id="compilerContainerAutoCompile" id="autoCompile" type="checkbox" title="Auto compile">`
this._view.hideWarningsBox = yo`<input class="${css.autocompile} custom-control-input" onchange=${() => this.hideWarnings()} id="hideWarningsBox" type="checkbox" title="Hide warnings">`
if (this.data.autoCompile) this._view.autoCompile.setAttribute('checked', '')
if (this.data.hideWarnings) this._view.hideWarningsBox.setAttribute('checked', '')
this._view.optimize = yo`<input onchange=${() => this.onchangeOptimize()} class="custom-control-input" id="optimize" type="checkbox">`
if (this.compileTabLogic.optimize) this._view.optimize.setAttribute('checked', '')
this._view.runs = yo`<input
min="1"
class="custom-select ml-2 ${css.runs}"
id="runs"
placeholder="200"
value="200"
type="number"
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract."
onchange=${() => this.onchangeRuns()}
>`
if (this.compileTabLogic.optimize) {
this._view.runs.removeAttribute('disabled')
this._view.runs.value = this.compileTabLogic.runs
} else {
this._view.runs.setAttribute('disabled', '')
}
this._view.versionSelector = yo`
<select onchange="${() => this.onchangeLoadVersion()}" class="custom-select" id="versionSelector" disabled>
<option disabled selected>${this.data.defaultVersion}</option>
<option disabled>builtin</option>
</select>`
this._view.languageSelector = yo`
<select onchange="${() => this.onchangeLanguage()}" class="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7">
<option>Solidity</option>
<option>Yul</option>
</select>`
this._view.version = yo`<span id="version"></span>`
this._view.evmVersionSelector = yo`
<select onchange="${() => this.onchangeEvmVersion()}" class="custom-select" id="evmVersionSelector">
<option value="default" selected="selected">compiler default</option>
<option>berlin</option>
<option>muirGlacier</option>
<option>istanbul</option>
<option>petersburg</option>
<option>constantinople</option>
<option>byzantium</option>
<option>spuriousDragon</option>
<option>tangerineWhistle</option>
<option>homestead</option>
</select>`
if (this.compileTabLogic.evmVersion) {
const s = this._view.evmVersionSelector
let i
for (i = 0; i < s.options.length; i++) {
if (s.options[i].value === this.compileTabLogic.evmVersion) {
break
}
}
if (i === s.options.length) { // invalid evmVersion from queryParams
s.selectedIndex = 0 // compiler default
this.onchangeEvmVersion()
} else {
s.selectedIndex = i
this.onchangeEvmVersion()
}
}
this._view.compilationButton = this.compilationButton()
this._view.includeNightlies = yo`
<input class="mr-2 custom-control-input" id="nightlies" type="checkbox" onchange=${() => this._updateVersionSelector()}>
`
this._view.compileContainer = yo`
<section>
<!-- Select Compiler Version -->
<article>
<header class="${css.compilerSection} border-bottom">
<div class="mb-2">
<label class="${css.compilerLabel} form-check-label" for="versionSelector">
Compiler
<button class="far fa-plus-square border-0 p-0 mx-2 btn-sm" onclick="${(e) => this.promtCompiler(e)}" title="Add a custom compiler with URL"></button>
</label>
${this._view.versionSelector}
</div>
<div class="mb-2 ${css.nightlyBuilds} custom-control custom-checkbox">
${this._view.includeNightlies}
<label for="nightlies" class="form-check-label custom-control-label">Include nightly builds</label>
</div>
<div class="mb-2">
<label class="${css.compilerLabel} form-check-label" for="compilierLanguageSelector">Language</label>
${this._view.languageSelector}
</div>
<div class="mb-2">
<label class="${css.compilerLabel} form-check-label" for="evmVersionSelector">EVM Version</label>
${this._view.evmVersionSelector}
</div>
<div class="mt-3">
<p class="mt-2 ${css.compilerLabel}">Compiler Configuration</p>
<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox">
${this._view.autoCompile}
<label class="form-check-label custom-control-label" for="autoCompile">Auto compile</label>
</div>
<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox">
<div class="justify-content-between align-items-center d-flex">
${this._view.optimize}
<label class="form-check-label custom-control-label" for="optimize">Enable optimization</label>
${this._view.runs}
</div>
</div>
<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox">
${this._view.hideWarningsBox}
<label class="form-check-label custom-control-label" for="hideWarningsBox">Hide warnings</label>
</div>
</div>
${this.hardhatCompilation}
${this._view.compilationButton}
</header>
</article>
<!-- Config -->
</section>`
return this._view.compileContainer
}
promtCompiler () {
modalDialogCustom.prompt(
'Add a custom compiler',
'URL',
'',
(url) => this.addCustomCompiler(url)
)
}
addCustomCompiler (url) {
this.data.selectedVersion = this._view.versionSelector.value
this._updateVersionSelector(url)
}
updateAutoCompile (event) {
this.config.set('autoCompile', this._view.autoCompile.checked)
}
updatehhCompilation (event) {
this.hhCompilation = event.target.checked
}
compile (event) {
const currentFile = this.config.get('currentFile')
if (!this.isSolFileSelected()) return
this._setCompilerVersionFromPragma(currentFile)
this.compileTabLogic.runCompiler(this.hhCompilation)
}
compileIfAutoCompileOn () {
if (this.config.get('autoCompile')) {
this.compile()
}
}
hideWarnings (event) {
this.config.set('hideWarnings', this._view.hideWarningsBox.checked)
this.compileIfAutoCompileOn()
}
/*
The following functions are handlers for internal events.
*/
onchangeOptimize () {
this.compileTabLogic.setOptimize(!!this._view.optimize.checked)
if (this.compileTabLogic.optimize) {
this._view.runs.removeAttribute('disabled')
this.compileTabLogic.setRuns(parseInt(this._view.runs.value))
} else {
this.compileTabLogic.setRuns(200)
this._view.runs.setAttribute('disabled', '')
}
this.compileIfAutoCompileOn()
}
onchangeRuns () {
this.compileTabLogic.setRuns(parseInt(this._view.runs.value))
this.compileIfAutoCompileOn()
}
onchangeLanguage () {
this.compileTabLogic.setLanguage(this._view.languageSelector.value)
this.compileIfAutoCompileOn()
}
onchangeEvmVersion () {
const s = this._view.evmVersionSelector
let v = s.value
if (v === 'default') {
v = null
}
this.compileTabLogic.setEvmVersion(v)
for (let i = 0; i < s.options.length; i++) {
if (i === s.selectedIndex) {
s.options[s.selectedIndex].setAttribute('selected', 'selected')
} else {
s.options[i].removeAttribute('selected')
}
}
this.compileIfAutoCompileOn()
}
onchangeLoadVersion () {
this.data.selectedVersion = this._view.versionSelector.value
this._updateVersionSelector()
this._updateLanguageSelector()
}
/*
The following functions map with the above event handlers.
They are an external API for modifying the compiler configuration.
*/
setConfiguration (settings) {
this.setLanguage(settings.language)
this.setEvmVersion(settings.evmVersion)
this.setOptimize(settings.optimize)
this.setRuns(settings.runs)
this.setVersion(settings.version)
}
setOptimize (enabled) {
this._view.optimize.checked = enabled
this.onchangeOptimize()
}
setRuns (value) {
if (value) {
this._view.runs.value = value
this.onchangeRuns()
}
}
setLanguage (lang) {
this._view.languageSelector.value = lang
this.onchangeLanguage()
}
setEvmVersion (version) {
this._view.evmVersionSelector.value = version || 'default'
this.onchangeEvmVersion()
}
setVersion (version) {
this._view.versionSelector.value = `soljson-v${version}.js`
this.onchangeLoadVersion()
}
_shouldBeAdded (version) {
return !version.includes('nightly') ||
(version.includes('nightly') && this._view.includeNightlies.checked)
}
_updateVersionSelector (customUrl = '') {
// update selectedversion if previous one got filtered out
if (!this.data.selectedVersion || !this._shouldBeAdded(this.data.selectedVersion)) {
this.data.selectedVersion = this.data.defaultVersion
}
this._view.versionSelector.innerHTML = ''
this._view.versionSelector.removeAttribute('disabled')
this.queryParams.update({ version: this.data.selectedVersion })
let url
if (customUrl !== '') {
this.data.selectedVersion = customUrl
this._view.versionSelector.appendChild(yo`<option value="${customUrl}" selected>custom</option>`)
url = customUrl
this.queryParams.update({ version: this.data.selectedVersion })
} else if (this.data.selectedVersion === 'builtin') {
let location = window.document.location
let path = location.pathname
if (!path.startsWith('/')) path = '/' + path
location = `${location.protocol}//${location.host}${path}assets/js`
if (location.endsWith('index.html')) location = location.substring(0, location.length - 10)
if (!location.endsWith('/')) location += '/'
url = location + 'soljson.js'
} else {
if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) {
return console.log('loading ' + this.data.selectedVersion + ' not allowed')
}
url = `${urlFromVersion(this.data.selectedVersion)}`
}
this.data.allversions.forEach(build => {
const option = build.path === this.data.selectedVersion
? yo`<option value="${build.path}" selected>${build.longVersion}</option>`
: yo`<option value="${build.path}">${build.longVersion}</option>`
if (this._shouldBeAdded(option.innerText)) {
this._view.versionSelector.appendChild(option)
}
})
if (this.data.selectedVersion !== 'builtin' && semver.lt(this._retrieveVersion(), 'v0.4.12+commit.194ff033.js')) {
toaster(yo`
<div>
<b>Old compiler usage detected.</b>
<p>You are using a compiler older than v0.4.12.</p>
<p>Some functionality may not work.</p>
</div>`
)
}
// Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case.
if (canUseWorker(this._retrieveVersion())) {
this.compileTabLogic.compiler.loadVersion(true, url)
this.setVersionText('(loading using worker)')
} else {
this.compileTabLogic.compiler.loadVersion(false, url)
this.setVersionText('(loading)')
}
}
_updateLanguageSelector () {
// This is the first version when Yul is available
if (!semver.valid(this._retrieveVersion()) || semver.lt(this._retrieveVersion(), 'v0.5.7+commit.6da8b019.js')) {
this._view.languageSelector.setAttribute('disabled', '')
this._view.languageSelector.value = 'Solidity'
this.compileTabLogic.setLanguage('Solidity')
} else {
this._view.languageSelector.removeAttribute('disabled')
}
}
setVersionText (text) {
if (this._view.version) this._view.version.innerText = text
}
// fetching both normal and wasm builds and creating a [version, baseUrl] map
async fetchAllVersion (callback) {
let selectedVersion, allVersionsWasm, isURL
let allVersions = [{ path: 'builtin', longVersion: 'Stable local version - 0.8.4' }]
// fetch normal builds
const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`)
// fetch wasm builds
const wasmRes = await promisedMiniXhr(`${baseURLWasm}/list.json`)
if (binRes.event.type === 'error' && wasmRes.event.type === 'error') {
selectedVersion = 'builtin'
return callback(allVersions, selectedVersion)
}
try {
const versions = JSON.parse(binRes.json).builds.slice().reverse()
allVersions = [...allVersions, ...versions]
selectedVersion = this.data.defaultVersion
if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version
// Check if version is a URL and corresponding filename starts with 'soljson'
if (selectedVersion.startsWith('https://')) {
const urlArr = selectedVersion.split('/')
if (urlArr[urlArr.length - 1].startsWith('soljson')) isURL = true
}
if (wasmRes.event.type !== 'error') {
allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse()
}
} catch (e) {
addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload. Error: ' + e)
}
// replace in allVersions those compiler builds which exist in allVersionsWasm with new once
if (allVersionsWasm && allVersions) {
allVersions.forEach((compiler, index) => {
const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion })
if (wasmIndex !== -1) {
allVersions[index] = allVersionsWasm[wasmIndex]
pathToURL[compiler.path] = baseURLWasm
} else {
pathToURL[compiler.path] = baseURLBin
}
})
}
callback(allVersions, selectedVersion, isURL)
}
scheduleCompilation () {
if (!this.config.get('autoCompile')) return
if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout)
this.data.compileTimeout = window.setTimeout(() => this.compileIfAutoCompileOn(), this.data.timeout)
}
}
module.exports = CompilerContainer

@ -1,51 +0,0 @@
'use strict'
import * as remixLib from '@remix-project/remix-lib'
const txHelper = remixLib.execution.txHelper
export class CompilerAbstract {
public languageversion: string
public data: Record<string, any>
public source: Record<string, any>
constructor (languageversion, data, source) {
this.languageversion = languageversion
this.data = data
this.source = source // source code
}
getContracts () {
return this.data.contracts
}
getContract (name) {
return txHelper.getContract(name, this.data.contracts)
}
visitContracts (calllback) {
return txHelper.visitContracts(this.data.contracts, calllback)
}
getData () {
return this.data
}
getAsts () {
return this.data.sources // ast
}
getSourceName (fileIndex) {
if (this.data && this.data.sources) {
return Object.keys(this.data.sources)[fileIndex]
} else if (Object.keys(this.source.sources).length === 1) {
// if we don't have ast, we return the only one filename present.
const sourcesArray = Object.keys(this.source.sources)
return sourcesArray[0]
}
return null
}
getSourceCode () {
return this.source
}
}

@ -1,19 +0,0 @@
'use strict'
import { canUseWorker, urlFromVersion } from './compiler-utils'
import { Compiler } from '@remix-project/remix-solidity'
import { CompilerAbstract } from './compiler-abstract'
export const compile = async (compilationTargets, settings, contentResolverCallback) => {
return new Promise((resolve) => {
const compiler = new Compiler(contentResolverCallback)
compiler.set('evmVersion', settings.evmVersion)
compiler.set('optimize', settings.optimize)
compiler.set('language', settings.language)
compiler.set('runs', settings.runs)
compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version))
compiler.event.register('compilationFinished', (success, compilationData, source) => {
resolve(new CompilerAbstract(settings.version, compilationData, source))
})
compiler.event.register('compilerLoaded', () => compiler.compile(compilationTargets, ''))
})
}

@ -1,46 +0,0 @@
const semver = require('semver')
const minixhr = require('minixhr')
/* global Worker */
export const baseURLBin = 'https://binaries.soliditylang.org/bin'
export const baseURLWasm = 'https://binaries.soliditylang.org/wasm'
export const pathToURL = {}
/**
* Retrieves the URL of the given compiler version
* @param version is the version of compiler with or without 'soljson-v' prefix and .js postfix
*/
export function urlFromVersion (version) {
if (!version.startsWith('soljson-v')) version = 'soljson-v' + version
if (!version.endsWith('.js')) version = version + '.js'
return `${pathToURL[version]}/${version}`
}
/**
* Checks if the worker can be used to load a compiler.
* checks a compiler whitelist, browser support and OS.
*/
export function canUseWorker (selectedVersion) {
const version = semver.coerce(selectedVersion)
const isNightly = selectedVersion.includes('nightly')
return browserSupportWorker() && (
// All compiler versions (including nightlies) after 0.6.3 are wasm compiled
semver.gt(version, '0.6.3') ||
// Only releases are wasm compiled starting with 0.3.6
(semver.gte(version, '0.3.6') && !isNightly)
)
}
function browserSupportWorker () {
return document.location.protocol !== 'file:' && Worker !== undefined
}
// returns a promise for minixhr
export function promisedMiniXhr (url) {
return new Promise((resolve) => {
minixhr(url, (json, event) => {
resolve({ json, event })
})
})
}

@ -1,5 +1,2 @@
export * from './compileTabLogic' export * from './compileTabLogic'
export * from './compiler-abstract'
export * from './compiler-helpers'
export * from './compiler-utils'
export * from './contract-parser' export * from './contract-parser'

Loading…
Cancel
Save