lint ide fix

1spacelint
bunsenstraat 1 year ago
parent ff91f929d2
commit a400388344
  1. 94
      apps/remix-ide/ci/download_e2e_assets.js
  2. 12
      apps/remix-ide/ci/lint-targets.js
  3. 32
      apps/remix-ide/ci/splice_tests.js
  4. 86
      apps/remix-ide/src/app/components/main-panel.tsx
  5. 206
      apps/remix-ide/src/app/components/preload.tsx
  6. 4
      apps/remix-ide/src/app/components/vertical-icons.tsx
  7. 6
      apps/remix-ide/src/app/editor/editor.js
  8. 4
      apps/remix-ide/src/app/files/dgitProvider.js
  9. 22
      apps/remix-ide/src/app/files/fileManager.ts
  10. 104
      apps/remix-ide/src/app/files/fileSystem.ts
  11. 308
      apps/remix-ide/src/app/files/filesystems/fileSystemUtility.ts
  12. 156
      apps/remix-ide/src/app/files/filesystems/indexedDB.ts
  13. 98
      apps/remix-ide/src/app/files/filesystems/localStorage.ts
  14. 6
      apps/remix-ide/src/app/panels/layout.ts
  15. 6
      apps/remix-ide/src/app/panels/terminal.js
  16. 482
      apps/remix-ide/src/app/plugins/code-format.ts
  17. 64
      apps/remix-ide/src/app/plugins/code-format/index.ts
  18. 210
      apps/remix-ide/src/app/plugins/code-format/parser.ts
  19. 4
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  20. 118
      apps/remix-ide/src/app/plugins/file-decorator.ts
  21. 976
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  22. 72
      apps/remix-ide/src/app/plugins/parser/services/antlr-worker.ts
  23. 408
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  24. 430
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  25. 112
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  26. 130
      apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts
  27. 232
      apps/remix-ide/src/app/plugins/parser/types/antlr-types.ts
  28. 8
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  29. 28
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  30. 262
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  31. 32
      apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx
  32. 48
      apps/remix-ide/src/app/state/registry.ts
  33. 8
      apps/remix-ide/src/app/tabs/analysis-tab.js
  34. 44
      apps/remix-ide/src/app/tabs/search.tsx
  35. 2
      apps/remix-ide/src/app/tabs/theme-module.js
  36. 4
      apps/remix-ide/src/app/tabs/web3-provider.js
  37. 4
      apps/remix-ide/src/app/udapp/run-tab.js
  38. 6
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  39. 122
      apps/remix-ide/src/blockchain/blockchain.tsx
  40. 22
      apps/remix-ide/src/blockchain/helper.ts
  41. 2
      apps/remix-ide/src/blockchain/providers/injected.ts
  42. 2
      apps/remix-ide/src/blockchain/providers/node.ts
  43. 10
      apps/remix-ide/src/blockchain/providers/vm.ts
  44. 116
      apps/remix-ide/src/blockchain/providers/worker-vm.ts

@ -5,8 +5,8 @@ const { exit } = require('process');
const child = child_process.spawnSync('grep -r --include="*.json" --include="*.ts" --include="*.tsx" "+commit" apps/**/* libs/**/*', [], { encoding: 'utf8', cwd: process.cwd(), shell: true }); const child = child_process.spawnSync('grep -r --include="*.json" --include="*.ts" --include="*.tsx" "+commit" apps/**/* libs/**/*', [], { encoding: 'utf8', cwd: process.cwd(), shell: true });
if (child.error) { if (child.error) {
console.log("ERROR: ", child); console.log("ERROR: ", child);
exit(1); exit(1);
} }
@ -15,63 +15,63 @@ let soljson =[];
const quotedVersionsRegex = /['"v]\d*\.\d*\.\d*\+commit\.[\d\w]*/g; const quotedVersionsRegex = /['"v]\d*\.\d*\.\d*\+commit\.[\d\w]*/g;
let quotedVersionsRegexMatch = child.stdout.match(quotedVersionsRegex) let quotedVersionsRegexMatch = child.stdout.match(quotedVersionsRegex)
if(quotedVersionsRegexMatch){ if(quotedVersionsRegexMatch){
let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v')) let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v'))
console.log('non nightly soljson versions found: ', soljson2); console.log('non nightly soljson versions found: ', soljson2);
if(soljson2) soljson = soljson.concat(soljson2); if(soljson2) soljson = soljson.concat(soljson2);
} }
const nightlyVersionsRegex = /\d*\.\d*\.\d-nightly.*\+commit\.[\d\w]*/g const nightlyVersionsRegex = /\d*\.\d*\.\d-nightly.*\+commit\.[\d\w]*/g
const nightlyVersionsRegexMatch = child.stdout.match(nightlyVersionsRegex) const nightlyVersionsRegexMatch = child.stdout.match(nightlyVersionsRegex)
if(nightlyVersionsRegexMatch){ if(nightlyVersionsRegexMatch){
let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item); let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item);
console.log('nightly soljson versions found: ', soljson3); console.log('nightly soljson versions found: ', soljson3);
if(soljson3) soljson = soljson.concat(soljson3); if(soljson3) soljson = soljson.concat(soljson3);
} }
if (soljson) { if (soljson) {
// filter out duplicates // filter out duplicates
soljson = soljson.filter((item, index) => soljson.indexOf(item) === index); soljson = soljson.filter((item, index) => soljson.indexOf(item) === index);
// manually add some versions // manually add some versions
soljson.push('v0.7.6+commit.7338295f'); soljson.push('v0.7.6+commit.7338295f');
console.log('soljson versions found: ', soljson, soljson.length); console.log('soljson versions found: ', soljson, soljson.length);
for (let i = 0; i < soljson.length; i++) { for (let i = 0; i < soljson.length; i++) {
const version = soljson[i]; const version = soljson[i];
if (version) { if (version) {
let url = '' let url = ''
// if nightly // if nightly
if (version.includes('nightly')) { if (version.includes('nightly')) {
url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`; url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`;
}else{ }else{
url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`; url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`;
} }
const dir = './dist/apps/remix-ide/assets/js/soljson'; const dir = './dist/apps/remix-ide/assets/js/soljson';
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir); fs.mkdirSync(dir);
} }
const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`; const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`;
// check if the file exists // check if the file exists
const exists = fs.existsSync(path); const exists = fs.existsSync(path);
if (!exists) { if (!exists) {
console.log('URL:', url) console.log('URL:', url)
try { try {
// use curl to download the file // use curl to download the file
child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true }) child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true })
} catch (e) { } catch (e) {
console.log('Failed to download soljson' + version + ' from ' + url) console.log('Failed to download soljson' + version + ' from ' + url)
}
}
} }
}
} }
}
} }

@ -5,15 +5,15 @@ const file = fs.readFileSync('projects.json')
const projects = JSON.parse(file) const projects = JSON.parse(file)
console.log(Object.keys(projects.graph.nodes)) console.log(Object.keys(projects.graph.nodes))
for(let node of Object.keys(projects.graph.nodes)){ for(let node of Object.keys(projects.graph.nodes)){
if(projects.graph.nodes[node].data.targets.lint){ if(projects.graph.nodes[node].data.targets.lint){
console.log(projects.graph.nodes[node].data.name) console.log(projects.graph.nodes[node].data.name)
const result = spawnSync('yarn', ['lint', projects.graph.nodes[node].data.name]) const result = spawnSync('yarn', ['lint', projects.graph.nodes[node].data.name])
if(result.status == 0){ if(result.status == 0){
console.log('success') console.log('success')
}else{ }else{
console.log(result.stdout.toString()) console.log(result.stdout.toString())
console.log(result.stderr.toString()) console.log(result.stderr.toString())
exit(1) exit(1)
} }
} }
} }

@ -7,21 +7,21 @@ let args = process.argv.slice(2)
const jobsize = args[0] || 10; const jobsize = args[0] || 10;
const job = args[1] || 0; const job = args[1] || 0;
exec(cmd, (error, stdout, stderr) => { exec(cmd, (error, stdout, stderr) => {
if (error) { if (error) {
console.error(`error: ${error.message}`); console.error(`error: ${error.message}`);
return; return;
} }
if (stderr) { if (stderr) {
console.error(`stderr: ${stderr}`); console.error(`stderr: ${stderr}`);
return; return;
} }
let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', '')) let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', ''))
let splitIndex = Math.ceil(files.length / jobsize); let splitIndex = Math.ceil(files.length / jobsize);
const parts = [] const parts = []
for (let i = 0; i < jobsize; i++) { for (let i = 0; i < jobsize; i++) {
parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex)) parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex))
} }
console.log(parts[job].join('\n')) console.log(parts[job].join('\n'))
}); });

@ -13,56 +13,56 @@ const profile = {
} }
export class MainPanel extends AbstractPanel { export class MainPanel extends AbstractPanel {
element: HTMLDivElement element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor (config) { constructor (config) {
super(profile) super(profile)
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer') this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;') this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config // this.config = config
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
onActivation () { onActivation () {
this.renderComponent() this.renderComponent()
} }
focus (name) { focus (name) {
this.emit('focusChanged', name) this.emit('focusChanged', name)
super.focus(name) super.focus(name)
this.renderComponent() this.renderComponent()
} }
addView (profile, view) { addView (profile, view) {
super.addView(profile, view) super.addView(profile, view)
this.renderComponent() this.renderComponent()
} }
removeView (profile) { removeView (profile) {
super.removeView(profile) super.removeView(profile)
this.renderComponent() this.renderComponent()
} }
async showContent (name) { async showContent (name) {
super.showContent(name) super.showContent(name)
this.renderComponent() this.renderComponent()
} }
renderComponent () { renderComponent () {
this.dispatch({ this.dispatch({
plugins: this.plugins plugins: this.plugins
}) })
} }
render() { render() {
return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div> return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div>
} }
updateComponent (state: any) { updateComponent (state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/> return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
} }
} }

@ -11,134 +11,134 @@ const _paq = window._paq = window._paq || []
export const Preload = () => { export const Preload = () => {
const [supported, setSupported] = useState<boolean>(true) const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false) const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false) const [showDownloader, setShowDownloader] = useState<boolean>(false)
const remixFileSystems = useRef<fileSystems>(new fileSystems()) const remixFileSystems = useRef<fileSystems>(new fileSystems())
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem()) const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS()) const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings // url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
function loadAppComponent() { function loadAppComponent() {
import('../../app').then((AppComponent) => { import('../../app').then((AppComponent) => {
const appComponent = new AppComponent.default() const appComponent = new AppComponent.default()
appComponent.run().then(() => { appComponent.run().then(() => {
render( render(
<> <>
<RemixApp app={appComponent} /> <RemixApp app={appComponent} />
</>, </>,
document.getElementById('root') document.getElementById('root')
) )
}) })
}).catch(err => { }).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message]) _paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err) console.error('Error loading Remix:', err)
setError(true) setError(true)
}) })
} }
const downloadBackup = async () => { const downloadBackup = async () => {
setShowDownloader(false) setShowDownloader(false)
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage']) await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage'])
await migrateAndLoad() await migrateAndLoad()
} }
const migrateAndLoad = async () => { const migrateAndLoad = async () => {
setShowDownloader(false) setShowDownloader(false)
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current) const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail']) _paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail'])
await setFileSystems() await setFileSystems()
} }
const setFileSystems = async() => { const setFileSystems = async() => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current]) const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current])
if (fsLoaded) { if (fsLoaded) {
console.log(fsLoaded.name + ' activated') console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name]) _paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
loadAppComponent() loadAppComponent()
} else { } else {
_paq.push(['trackEvent', 'Storage', 'error', 'no supported storage']) _paq.push(['trackEvent', 'Storage', 'error', 'no supported storage'])
setSupported(false) setSupported(false)
}
} }
}
const testmigration = async() => { const testmigration = async() => {
if (testmigrationResult.current) { if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs) fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
}
} }
}
useEffect(() => { useEffect(() => {
async function loadStorage() { async function loadStorage() {
await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported']) await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported']) await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])
await testmigration() await testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces() remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces()
localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces() localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces()
remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true)) remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true))
!remixIndexedDB.current.loaded && await setFileSystems() !remixIndexedDB.current.loaded && await setFileSystems()
} }
loadStorage() loadStorage()
}, []) }, [])
return <> return <>
<div className='preload-container'> <div className='preload-container'>
<div className='preload-logo pb-4'> <div className='preload-logo pb-4'>
{logo} {logo}
<div className="info-secondary splash"> <div className="info-secondary splash">
REMIX IDE REMIX IDE
<br /> <br />
<span className='version'> v{packageJson.version}</span> <span className='version'> v{packageJson.version}</span>
</div> </div>
</div> </div>
{!supported ? {!supported ?
<div className='preload-info-container alert alert-warning'> <div className='preload-info-container alert alert-warning'>
Your browser does not support any of the filesystems required by Remix. Your browser does not support any of the filesystems required by Remix.
Either change the settings in your browser or use a supported browser. Either change the settings in your browser or use a supported browser.
</div> : null} </div> : null}
{error ? {error ?
<div className='preload-info-container alert alert-danger text-left'> <div className='preload-info-container alert alert-danger text-left'>
An unknown error has occurred while loading the application.<br></br> An unknown error has occurred while loading the application.<br></br>
Doing a hard refresh might fix this issue:<br></br> Doing a hard refresh might fix this issue:<br></br>
<div className='pt-2'> <div className='pt-2'>
Windows:<br></br> Windows:<br></br>
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br> - Chrome: CTRL + F5 or CTRL + Reload Button<br></br>
- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br> - Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div> </div>
<div className='pt-2'> <div className='pt-2'>
MacOS:<br></br> MacOS:<br></br>
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br> - Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div> </div>
<div className='pt-2'> <div className='pt-2'>
Linux:<br></br> Linux:<br></br>
- Chrome & FireFox: CTRL + SHIFT + R<br></br> - Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div> </div>
</div> : null} </div> : null}
{showDownloader ? {showDownloader ?
<div className='preload-info-container alert alert-info'> <div className='preload-info-container alert alert-info'>
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work. This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br> <br></br>
You don't need to do anything else, your files will be available when the app loads. You don't need to do anything else, your files will be available when the app loads.
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div> <div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div>
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div> <div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div>
</div> : null} </div> : null}
{(supported && !error && !showDownloader) ? {(supported && !error && !showDownloader) ?
<div> <div>
<i className="fas fa-spinner fa-spin fa-2x"></i> <i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null} </div> : null}
</div> </div>
</> </>
} }
const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100"> const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" /> <path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" /> <path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" /> <path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
</svg> </svg>

@ -113,8 +113,8 @@ export class VerticalIcons extends Plugin {
updateComponent(state: any){ updateComponent(state: any){
return <RemixUiVerticalIconsPanel return <RemixUiVerticalIconsPanel
verticalIconsPlugin={state.verticalIconsPlugin} verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons} icons={state.icons}
/> />
} }

@ -99,8 +99,8 @@ class Editor extends Plugin {
this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration) this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration)
this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration) this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration)
}} id='editorView'> }} id='editorView'>
<PluginViewWrapper plugin={this} /> <PluginViewWrapper plugin={this} />
</div> </div>
} }
renderComponent () { renderComponent () {
@ -293,7 +293,7 @@ class Editor extends Plugin {
* Get the text in the current session, if any. * Get the text in the current session, if any.
* @param {string} url Address of the content to retrieve. * @param {string} url Address of the content to retrieve.
*/ */
getText (url) { getText (url) {
if (this.sessions[url]) { if (this.sessions[url]) {
return this.sessions[url].getValue() return this.sessions[url].getValue()
} }

@ -110,7 +110,7 @@ class DGitProvider extends Plugin {
...cmd ...cmd
}) })
if (refresh) { if (refresh) {
setTimeout(async () => { setTimeout(async () => {
await this.call('fileManager', 'refresh') await this.call('fileManager', 'refresh')
}, 1000) }, 1000)
} }
@ -141,7 +141,7 @@ class DGitProvider extends Plugin {
...cmd ...cmd
}) })
if (refresh) { if (refresh) {
setTimeout(async () => { setTimeout(async () => {
await this.call('fileManager', 'refresh') await this.call('fileManager', 'refresh')
}, 1000) }, 1000)
} }

@ -361,15 +361,15 @@ class FileManager extends Plugin {
try { try {
const downloadFileName = helper.extractNameFromKey(path) const downloadFileName = helper.extractNameFromKey(path)
if (await this.isDirectory(path)) { if (await this.isDirectory(path)) {
const zip = new JSZip() const zip = new JSZip()
await this.zipDir(path, zip) await this.zipDir(path, zip)
const content = await zip.generateAsync({type: 'blob'}) const content = await zip.generateAsync({type: 'blob'})
saveAs(content, `${downloadFileName}.zip`) saveAs(content, `${downloadFileName}.zip`)
} else { } else {
path = this.normalize(path) path = this.normalize(path)
const content: any = await this.readFile(path) const content: any = await this.readFile(path)
saveAs(new Blob([content]), downloadFileName) saveAs(new Blob([content]), downloadFileName)
} }
} catch (e) { } catch (e) {
throw new Error(e) throw new Error(e)
} }
@ -777,7 +777,7 @@ class FileManager extends Plugin {
provider.set(currentFile, oldContent) provider.set(currentFile, oldContent)
return console.error(error) return console.error(error)
} else { } else {
this.emit('fileSaved', currentFile) this.emit('fileSaved', currentFile)
} }
}) })
}) })
@ -868,7 +868,7 @@ class FileManager extends Plugin {
* @returns {void} * @returns {void}
*/ */
async moveFile(src: string, dest: string) { async moveFile(src: string, dest: string) {
try { try {
src = this.normalize(src) src = this.normalize(src)
dest = this.normalize(dest) dest = this.normalize(dest)

@ -1,71 +1,71 @@
export class fileSystem { export class fileSystem {
name: string name: string
enabled: boolean enabled: boolean
available: boolean available: boolean
fs: any fs: any
fsCallBack: any; fsCallBack: any;
hasWorkSpaces: boolean hasWorkSpaces: boolean
loaded: boolean loaded: boolean
load: () => Promise<unknown> load: () => Promise<unknown>
test: () => Promise<unknown> test: () => Promise<unknown>
constructor() { constructor() {
this.available = false this.available = false
this.enabled = false this.enabled = false
this.hasWorkSpaces = false this.hasWorkSpaces = false
this.loaded = false this.loaded = false
} }
checkWorkspaces = async () => { checkWorkspaces = async () => {
try { try {
await this.fs.stat('.workspaces') await this.fs.stat('.workspaces')
this.hasWorkSpaces = true this.hasWorkSpaces = true
} catch (e) { } catch (e) {
}
} }
}
set = async () => { set = async () => {
const w = (window as any) const w = (window as any)
if (!this.loaded) return false if (!this.loaded) return false
w.remixFileSystem = this.fs w.remixFileSystem = this.fs
w.remixFileSystem.name = this.name w.remixFileSystem.name = this.name
w.remixFileSystemCallback = this.fsCallBack w.remixFileSystemCallback = this.fsCallBack
return true return true
} }
} }
export class fileSystems { export class fileSystems {
fileSystems: Record<string, fileSystem> fileSystems: Record<string, fileSystem>
constructor() { constructor() {
this.fileSystems = {} this.fileSystems = {}
} }
addFileSystem = async (fs: fileSystem): Promise<boolean> => { addFileSystem = async (fs: fileSystem): Promise<boolean> => {
try { try {
this.fileSystems[fs.name] = fs this.fileSystems[fs.name] = fs
await fs.test() && await fs.load() await fs.test() && await fs.load()
console.log(fs.name + ' is loaded...') console.log(fs.name + ' is loaded...')
return true return true
} catch (e) { } catch (e) {
console.log(fs.name + ' not available...') console.log(fs.name + ' not available...')
return false return false
}
} }
/** }
/**
* sets filesystem using list as fallback * sets filesystem using list as fallback
* @param {string[]} names * @param {string[]} names
* @returns {Promise} * @returns {Promise}
*/ */
setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => { setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => {
for (const fs of filesystems) { for (const fs of filesystems) {
if (fs && this.fileSystems[fs.name]) { if (fs && this.fileSystems[fs.name]) {
const result = await this.fileSystems[fs.name].set() const result = await this.fileSystems[fs.name].set()
if (result) return this.fileSystems[fs.name] if (result) return this.fileSystems[fs.name]
} }
}
return null
} }
return null
}
} }

@ -3,189 +3,189 @@ import JSZip from "jszip"
import { fileSystem } from "../fileSystem" import { fileSystem } from "../fileSystem"
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
export class fileSystemUtility { export class fileSystemUtility {
migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => { migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => {
try { try {
await fsFrom.checkWorkspaces() await fsFrom.checkWorkspaces()
await fsTo.checkWorkspaces() await fsTo.checkWorkspaces()
if (fsTo.hasWorkSpaces) { if (fsTo.hasWorkSpaces) {
console.log(`${fsTo.name} already has files`) console.log(`${fsTo.name} already has files`)
return true return true
} }
if (!fsFrom.hasWorkSpaces) { if (!fsFrom.hasWorkSpaces) {
console.log('no files to migrate') console.log('no files to migrate')
return true return true
} }
const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs) const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs)
await this.populateWorkspace(fromFiles, fsTo.fs) await this.populateWorkspace(fromFiles, fsTo.fs)
const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs) const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs)
if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) { if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) {
console.log('file migration successful') console.log('file migration successful')
return true return true
} else { } else {
_paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch']) _paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch'])
console.log('file migration failed falling back to ' + fsFrom.name) console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false fsTo.loaded = false
return false return false
} }
} catch (err) { } catch (err) {
console.log(err) console.log(err)
_paq.push(['trackEvent', 'Migrate', 'error', err && err.message]) _paq.push(['trackEvent', 'Migrate', 'error', err && err.message])
console.log('file migration failed falling back to ' + fsFrom.name) console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false fsTo.loaded = false
return false return false
}
} }
}
downloadBackup = async (fs: fileSystem) => {
try { downloadBackup = async (fs: fileSystem) => {
const zip = new JSZip() try {
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.") const zip = new JSZip()
await fs.checkWorkspaces() zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => { await fs.checkWorkspaces()
zip.file(path, content) await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => {
}) zip.file(path, content)
const blob = await zip.generateAsync({ type: 'blob' }) })
const today = new Date() const blob = await zip.generateAsync({ type: 'blob' })
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate() const today = new Date()
const time = today.getHours() + 'h' + today.getMinutes() + 'min' const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`) const time = today.getHours() + 'h' + today.getMinutes() + 'min'
_paq.push(['trackEvent','Backup','download','preload']) this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
} catch (err) { _paq.push(['trackEvent','Backup','download','preload'])
_paq.push(['trackEvent','Backup','error',err && err.message]) } catch (err) {
console.log(err) _paq.push(['trackEvent','Backup','error',err && err.message])
} console.log(err)
} }
}
populateWorkspace = async (json, fs) => {
for (const item in json) { populateWorkspace = async (json, fs) => {
const isFolder = json[item].content === undefined for (const item in json) {
if (isFolder) { const isFolder = json[item].content === undefined
await this.createDir(item, fs) if (isFolder) {
await this.populateWorkspace(json[item].children, fs) await this.createDir(item, fs)
} else { await this.populateWorkspace(json[item].children, fs)
await fs.writeFile(item, json[item].content, 'utf8') } else {
} await fs.writeFile(item, json[item].content, 'utf8')
} }
} }
}
/** /**
* copy the folder recursively * copy the folder recursively
* @param {string} path is the folder to be copied over * @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files * @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders * @param {Function} visitFolder is a function called for each visited folders
*/ */
copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => { copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => {
visitFile = visitFile || (() => { }) visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { }) visitFolder = visitFolder || (() => { })
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb)
} }
/** /**
* copy the folder recursively (internal use) * copy the folder recursively (internal use)
* @param {string} path is the folder to be copied over * @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files * @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders * @param {Function} visitFolder is a function called for each visited folders
*/ */
async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) { async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) {
visitFile = visitFile || function () { /* do nothing. */ } visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ } visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {} const json = {}
// path = this.removePrefix(path) // path = this.removePrefix(path)
if (await fs.exists(path)) { if (await fs.exists(path)) {
const items = await fs.readdir(path) const items = await fs.readdir(path)
visitFolder({ path }) visitFolder({ path })
if (items.length !== 0) { if (items.length !== 0) {
for (const item of items) { for (const item of items) {
const file: any = {} const file: any = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}` const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await fs.stat(curPath)).isDirectory()) { if ((await fs.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb) file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb)
} else { } else {
file.content = await fs.readFile(curPath, 'utf8') file.content = await fs.readFile(curPath, 'utf8')
if (cb) cb({ path: curPath, content: file.content }) if (cb) cb({ path: curPath, content: file.content })
visitFile({ path: curPath, content: file.content }) visitFile({ path: curPath, content: file.content })
} }
json[curPath] = file json[curPath] = file
}
}
}
return json
}
createDir = async (path, fs) => {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + (currentCheck ? '/' : '') + value
if (!await fs.exists(currentCheck)) {
await fs.mkdir(currentCheck)
}
} }
}
} }
return json
saveAs = (blob, name) => { }
const node = document.createElement('a')
node.download = name createDir = async (path, fs) => {
node.rel = 'noopener' const paths = path.split('/')
node.href = URL.createObjectURL(blob) if (paths.length && paths[0] === '') paths.shift()
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s let currentCheck = ''
setTimeout(function () { for (const value of paths) {
try { currentCheck = currentCheck + (currentCheck ? '/' : '') + value
node.dispatchEvent(new MouseEvent('click')) if (!await fs.exists(currentCheck)) {
} catch (e) { await fs.mkdir(currentCheck)
const evt = document.createEvent('MouseEvents') }
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
} }
}
saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
}
} }
/* eslint-disable no-template-curly-in-string */ /* eslint-disable no-template-curly-in-string */
export const migrationTestData = { export const migrationTestData = {
'.workspaces': { '.workspaces': {
children: {
'.workspaces/default_workspace': {
children: { children: {
'.workspaces/default_workspace': { '.workspaces/default_workspace/README.txt': {
children: { content: 'TEST README'
'.workspaces/default_workspace/README.txt': { }
content: 'TEST README' }
} },
} '.workspaces/emptyspace': {
},
'.workspaces/emptyspace': {
}, },
'.workspaces/workspace_test': { '.workspaces/workspace_test': {
children: {
'.workspaces/workspace_test/TEST_README.txt': {
content: 'TEST README'
},
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: { children: {
'.workspaces/workspace_test/TEST_README.txt': { '.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: 'TEST README' content: '{ "test": "data" }'
}, }
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: {
'.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: '{ "test": "data" }'
}
}
}
}
}
} }
}
} }
}
} }
}
} }
}
} }

@ -2,90 +2,90 @@ import LightningFS from "@isomorphic-git/lightning-fs"
import { fileSystem } from "../fileSystem" import { fileSystem } from "../fileSystem"
export class IndexedDBStorage extends LightningFS { export class IndexedDBStorage extends LightningFS {
base: LightningFS.PromisifedFS base: LightningFS.PromisifedFS
addSlash: (file: string) => string addSlash: (file: string) => string
extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> } extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> }
constructor(name: string) { constructor(name: string) {
super(name) super(name)
this.addSlash = (file) => { this.addSlash = (file) => {
if (!file.startsWith('/')) file = '/' + file if (!file.startsWith('/')) file = '/' + file
return file return file
}
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
} }
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
}
} }
export class indexedDBFileSystem extends fileSystem { export class indexedDBFileSystem extends fileSystem {
constructor() { constructor() {
super() super()
this.name = 'indexedDB' this.name = 'indexedDB'
} }
load = async () => { load = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const fs = new IndexedDBStorage('RemixFileSystem') const fs = new IndexedDBStorage('RemixFileSystem')
fs.init('RemixFileSystem') fs.init('RemixFileSystem')
this.fs = fs.extended this.fs = fs.extended
this.fsCallBack = fs this.fsCallBack = fs
this.loaded = true this.loaded = true
resolve(true) resolve(true)
} catch (e) { } catch (e) {
reject(e) reject(e)
} }
}) })
} }
test = async () => { test = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!window.indexedDB) { if (!window.indexedDB) {
this.available = false this.available = false
reject('No indexedDB on window') reject('No indexedDB on window')
} }
const request = window.indexedDB.open("RemixTestDataBase"); const request = window.indexedDB.open("RemixTestDataBase");
request.onerror = () => { request.onerror = () => {
this.available = false this.available = false
reject('Error creating test database') reject('Error creating test database')
}; };
request.onsuccess = () => { request.onsuccess = () => {
window.indexedDB.deleteDatabase("RemixTestDataBase"); window.indexedDB.deleteDatabase("RemixTestDataBase");
this.available = true this.available = true
resolve(true) resolve(true)
}; };
}) })
} }
} }

@ -2,56 +2,56 @@ import { fileSystem } from "../fileSystem";
export class localStorageFS extends fileSystem { export class localStorageFS extends fileSystem {
constructor() { constructor() {
super() super()
this.name = 'localstorage' this.name = 'localstorage'
} }
load = async () => { load = async () => {
const me = this const me = this
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const w = window as any const w = window as any
w.BrowserFS.install(window) w.BrowserFS.install(window)
w.BrowserFS.configure({ w.BrowserFS.configure({
fs: 'LocalStorage' fs: 'LocalStorage'
}, async function (e) { }, async function (e) {
if (e) { if (e) {
console.log('BrowserFS Error: ' + e) console.log('BrowserFS Error: ' + e)
reject(e) reject(e)
} else { } else {
me.fs = { ...window.require('fs') } me.fs = { ...window.require('fs') }
me.fsCallBack = window.require('fs') me.fsCallBack = window.require('fs')
me.fs.readdir = me.fs.readdirSync me.fs.readdir = me.fs.readdirSync
me.fs.readFile = me.fs.readFileSync me.fs.readFile = me.fs.readFileSync
me.fs.writeFile = me.fs.writeFileSync me.fs.writeFile = me.fs.writeFileSync
me.fs.stat = me.fs.statSync me.fs.stat = me.fs.statSync
me.fs.unlink = me.fs.unlinkSync me.fs.unlink = me.fs.unlinkSync
me.fs.rmdir = me.fs.rmdirSync me.fs.rmdir = me.fs.rmdirSync
me.fs.mkdir = me.fs.mkdirSync me.fs.mkdir = me.fs.mkdirSync
me.fs.rename = me.fs.renameSync me.fs.rename = me.fs.renameSync
me.fs.exists = me.fs.existsSync me.fs.exists = me.fs.existsSync
me.loaded = true me.loaded = true
resolve(true) resolve(true)
} }
})
} catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
}) })
} } catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
})
}
test = async () => { test = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const test = 'test'; const test = 'test';
try { try {
localStorage.setItem(test, test); localStorage.setItem(test, test);
localStorage.removeItem(test); localStorage.removeItem(test);
resolve(true) resolve(true)
} catch(e) { } catch(e) {
reject(e) reject(e)
} }
}) })
} }
} }

@ -61,9 +61,9 @@ export class Layout extends Plugin {
}) })
this.on('manager', 'activate', (profile: Profile) => { this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) { switch (profile.name) {
case 'filePanel': case 'filePanel':
this.call('menuicons', 'select', 'filePanel') this.call('menuicons', 'select', 'filePanel')
break break
} }
}) })
this.on('sidePanel', 'focusChanged', async (name) => { this.on('sidePanel', 'focusChanged', async (name) => {

@ -114,9 +114,9 @@ class Terminal extends Plugin {
updateComponent(state) { updateComponent(state) {
return <RemixUiTerminal return <RemixUiTerminal
plugin={state.plugin} plugin={state.plugin}
onReady={state.onReady} onReady={state.onReady}
/> />
} }
renderComponent () { renderComponent () {

@ -8,279 +8,279 @@ import toml from 'toml'
import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter' import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter'
const profile = { const profile = {
name: 'codeFormatter', name: 'codeFormatter',
desciption: 'prettier plugin for Remix', desciption: 'prettier plugin for Remix',
methods: ['format'], methods: ['format'],
events: [''], events: [''],
version: '0.0.1' version: '0.0.1'
} }
const defaultOptions = { const defaultOptions = {
"overrides": [ "overrides": [
{ {
"files": "*.sol", "files": "*.sol",
"options": { "options": {
"printWidth": 80, "printWidth": 80,
"tabWidth": 4, "tabWidth": 4,
"useTabs": false, "useTabs": false,
"singleQuote": false, "singleQuote": false,
"bracketSpacing": false, "bracketSpacing": false,
} }
}, },
{ {
"files": "*.yml", "files": "*.yml",
"options": { "options": {
} }
}, },
{ {
"files": "*.yaml", "files": "*.yaml",
"options": { "options": {
} }
}, },
{ {
"files": "*.toml", "files": "*.toml",
"options": { "options": {
} }
}, },
{ {
"files": "*.json", "files": "*.json",
"options": { "options": {
} }
}, },
{ {
"files": "*.js", "files": "*.js",
"options": { "options": {
} }
}, },
{ {
"files": "*.ts", "files": "*.ts",
"options": { "options": {
} }
} }
] ]
} }
export class CodeFormat extends Plugin { export class CodeFormat extends Plugin {
prettier: any prettier: any
ts: any ts: any
babel: any babel: any
espree: any espree: any
yml: any yml: any
sol: any sol: any
constructor() { constructor() {
super(profile) super(profile)
} }
async format(file: string) { async format(file: string) {
// lazy load // lazy load
if (!this.prettier) { if (!this.prettier) {
this.prettier = await import('prettier/standalone') this.prettier = await import('prettier/standalone')
this.ts = await import('prettier/parser-typescript') this.ts = await import('prettier/parser-typescript')
this.babel = await import('prettier/parser-babel') this.babel = await import('prettier/parser-babel')
this.espree = await import('prettier/parser-espree') this.espree = await import('prettier/parser-espree')
this.yml = await import('prettier/parser-yaml') this.yml = await import('prettier/parser-yaml')
} }
try { try {
const content = await this.call('fileManager', 'readFile', file) const content = await this.call('fileManager', 'readFile', file)
if (!content) return if (!content) return
let parserName = '' let parserName = ''
let options: Options = { let options: Options = {
} }
switch (path.extname(file)) { switch (path.extname(file)) {
case '.sol': case '.sol':
parserName = 'solidity-parse' parserName = 'solidity-parse'
break break
case '.ts': case '.ts':
parserName = 'typescript' parserName = 'typescript'
options = { options = {
...options, ...options,
trailingComma: 'all', trailingComma: 'all',
semi: false, semi: false,
singleQuote: true, singleQuote: true,
quoteProps: 'as-needed', quoteProps: 'as-needed',
bracketSpacing: true, bracketSpacing: true,
arrowParens: 'always', arrowParens: 'always',
} }
break break
case '.js': case '.js':
parserName = "espree" parserName = "espree"
options = { options = {
...options, ...options,
semi: false, semi: false,
singleQuote: true, singleQuote: true,
} }
break break
case '.json': case '.json':
parserName = 'json' parserName = 'json'
break break
case '.yml': case '.yml':
parserName = 'yaml' parserName = 'yaml'
break break
case '.yaml': case '.yaml':
parserName = 'yaml' parserName = 'yaml'
break break
} }
if (file === '.prettierrc') { if (file === '.prettierrc') {
parserName = 'json' parserName = 'json'
} }
const possibleFileNames = [ const possibleFileNames = [
'.prettierrc', '.prettierrc',
'.prettierrc.json', '.prettierrc.json',
'.prettierrc.yaml', '.prettierrc.yaml',
'.prettierrc.yml', '.prettierrc.yml',
'.prettierrc.toml', '.prettierrc.toml',
'.prettierrc.js', '.prettierrc.js',
'.prettierrc.cjs', '.prettierrc.cjs',
'prettier.config.js', 'prettier.config.js',
'prettier.config.cjs', 'prettier.config.cjs',
'.prettierrc.json5', '.prettierrc.json5',
] ]
const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => { const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => {
const exists = await this.call('fileManager', 'exists', fileName) const exists = await this.call('fileManager', 'exists', fileName)
return exists return exists
}) })
let parsed = null let parsed = null
if (prettierConfigFile) { if (prettierConfigFile) {
let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile) let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile)
if (prettierConfig) { if (prettierConfig) {
if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) { if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) {
try { try {
parsed = yaml.load(prettierConfig) parsed = yaml.load(prettierConfig)
} catch (e) { } catch (e) {
// do nothing // do nothing
}
} else if (prettierConfigFile.endsWith('.toml')) {
try {
parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
}
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
} }
} else if (prettierConfigFile.endsWith('.toml')) {
if (!parsed && prettierConfigFile) { try {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`) parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
} }
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
}
if (!parsed && prettierConfigFile) {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`)
}
// merge options
if (parsed) {
options = {
...options,
...parsed,
}
}
// search for overrides // merge options
if (parsed && parsed.overrides) { if (parsed) {
const override = parsed.overrides.find((override) => { options = {
if (override.files) { ...options,
const pathFilter: AnyFilter = {} ...parsed,
pathFilter.include = setGlobalExpression(override.files) }
const filteredFiles = [file] }
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
}
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
if (override) { // search for overrides
options = { if (parsed && parsed.overrides) {
...options, const override = parsed.overrides.find((override) => {
...override.options, if (override.files) {
} const pathFilter: AnyFilter = {}
} pathFilter.include = setGlobalExpression(override.files)
const filteredFiles = [file]
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
} }
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
if (override) {
const result = this.prettier.format(content, { options = {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml], ...options,
parser: parserName, ...override.options,
...options }
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
} }
}
const result = this.prettier.format(content, {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml],
parser: parserName,
...options
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
} }
}
} }
//*.sol, **/*.txt, contracts/* //*.sol, **/*.txt, contracts/*
const setGlobalExpression = (paths: string) => { const setGlobalExpression = (paths: string) => {
const results = [] const results = []
paths.split(',').forEach(path => { paths.split(',').forEach(path => {
path = path.trim() path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.') if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*')) if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*') path = path.replace(/(\*)/g, '**/*.*')
results.push(path) results.push(path)
}) })
return results return results
} }
async function findAsync(arr, asyncCallback) { async function findAsync(arr, asyncCallback) {
const promises = arr.map(asyncCallback); const promises = arr.map(asyncCallback);
const results = await Promise.all(promises); const results = await Promise.all(promises);
const index = results.findIndex(result => result); const index = results.findIndex(result => result);
return arr[index]; return arr[index];
} }

@ -8,54 +8,54 @@ import { parse } from './parser'
// https://prettier.io/docs/en/plugins.html#languages // https://prettier.io/docs/en/plugins.html#languages
// https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json // https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json
const languages = [ const languages = [
{ {
linguistLanguageId: 237469032, linguistLanguageId: 237469032,
name: 'Solidity', name: 'Solidity',
type: 'programming', type: 'programming',
color: '#AA6746', color: '#AA6746',
aceMode: 'text', aceMode: 'text',
tmScope: 'source.solidity', tmScope: 'source.solidity',
extensions: ['.sol'], extensions: ['.sol'],
parsers: ['solidity-parse'], parsers: ['solidity-parse'],
vscodeLanguageIds: ['solidity'] vscodeLanguageIds: ['solidity']
} }
]; ];
// https://prettier.io/docs/en/plugins.html#parsers // https://prettier.io/docs/en/plugins.html#parsers
const parser = { astFormat: 'solidity-ast', parse, ...loc }; const parser = { astFormat: 'solidity-ast', parse, ...loc };
const parsers = { const parsers = {
'solidity-parse': parser 'solidity-parse': parser
}; };
const canAttachComment = (node) => const canAttachComment = (node) =>
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment'; node.type && node.type !== 'BlockComment' && node.type !== 'LineComment';
// https://prettier.io/docs/en/plugins.html#printers // https://prettier.io/docs/en/plugins.html#printers
const printers = { const printers = {
'solidity-ast': { 'solidity-ast': {
canAttachComment, canAttachComment,
handleComments: { handleComments: {
ownLine: handleComments.handleOwnLineComment, ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment, endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment remaining: handleComments.handleRemainingComment
}, },
isBlockComment: handleComments.isBlockComment, isBlockComment: handleComments.isBlockComment,
massageAstNode, massageAstNode,
print, print,
printComment printComment
} }
}; };
// https://prettier.io/docs/en/plugins.html#defaultoptions // https://prettier.io/docs/en/plugins.html#defaultoptions
const defaultOptions = { const defaultOptions = {
bracketSpacing: false, bracketSpacing: false,
tabWidth: 4 tabWidth: 4
}; };
export default { export default {
languages, languages,
parsers, parsers,
printers, printers,
options, options,
defaultOptions defaultOptions
}; };

@ -80,113 +80,113 @@ export function parse(text, _parsers, options) {
}, },
BinaryOperation(ctx) { BinaryOperation(ctx) {
switch (ctx.operator) { switch (ctx.operator) {
case '+': case '+':
case '-': case '-':
ctx.left = tryHug(ctx.left, ['%']); ctx.left = tryHug(ctx.left, ['%']);
ctx.right = tryHug(ctx.right, ['%']); ctx.right = tryHug(ctx.right, ['%']);
break; break;
case '*': case '*':
ctx.left = tryHug(ctx.left, ['/', '%']); ctx.left = tryHug(ctx.left, ['/', '%']);
break; break;
case '/': case '/':
ctx.left = tryHug(ctx.left, ['*', '%']); ctx.left = tryHug(ctx.left, ['*', '%']);
break; break;
case '%': case '%':
ctx.left = tryHug(ctx.left, ['*', '/', '%']); ctx.left = tryHug(ctx.left, ['*', '/', '%']);
break; break;
case '**': case '**':
// If the compiler has not been given as an option using we leave a**b**c. // If the compiler has not been given as an option using we leave a**b**c.
if (!compiler) break; if (!compiler) break;
if (semver.satisfies(compiler, '<0.8.0')) { if (semver.satisfies(compiler, '<0.8.0')) {
// If the compiler is less than 0.8.0 then a**b**c is formatted as // If the compiler is less than 0.8.0 then a**b**c is formatted as
// (a**b)**c. // (a**b)**c.
ctx.left = tryHug(ctx.left, ['**']); ctx.left = tryHug(ctx.left, ['**']);
break;
}
if (
ctx.left.type === 'BinaryOperation' &&
ctx.left.operator === '**'
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
break; break;
}
if (
ctx.left.type === 'BinaryOperation' &&
ctx.left.operator === '**'
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
break;
} }
} }
}); });

@ -61,8 +61,8 @@ export class ContractFlattener extends Plugin {
try{ try{
dependencyGraph = getDependencyGraph(ast, filePath) dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty() sorted = dependencyGraph.isEmpty()
? [filePath] ? [filePath]
: dependencyGraph.sort().reverse() : dependencyGraph.sort().reverse()
sources = source.sources sources = source.sources
result = concatSourceFiles(sorted, sources) result = concatSourceFiles(sorted, sources)
}catch(err){ }catch(err){

@ -6,79 +6,79 @@ import { Plugin } from '@remixproject/engine'
import { fileDecoration } from '@remix-ui/file-decorators' import { fileDecoration } from '@remix-ui/file-decorators'
const profile = { const profile = {
name: 'fileDecorator', name: 'fileDecorator',
desciption: 'Keeps decorators of the files', desciption: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'], methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'], events: ['fileDecoratorsChanged'],
version: '0.0.1' version: '0.0.1'
} }
export class FileDecorator extends Plugin { export class FileDecorator extends Plugin {
private _fileStates: fileDecoration[] = [] private _fileStates: fileDecoration[] = []
constructor() { constructor() {
super(profile) super(profile)
} }
onActivation(): void { onActivation(): void {
this.on('filePanel', 'setWorkspace', async () => { this.on('filePanel', 'setWorkspace', async () => {
await this.clearAllFileDecorators() await this.clearAllFileDecorators()
}) })
} }
/** /**
* @param fileStates Array of file states * @param fileStates Array of file states
*/ */
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {
const { from } = this.currentRequest const { from } = this.currentRequest
const workspace = await this.call('filePanel', 'getCurrentWorkspace') const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates] const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates]
// clear all file states in the previous state of this owner on the files called // clear all file states in the previous state of this owner on the files called
fileStatesPayload.forEach((state) => { fileStatesPayload.forEach((state) => {
state.workspace = workspace state.workspace = workspace
state.owner = from state.owner = from
}) })
const filteredState = this._fileStates.filter((state) => { const filteredState = this._fileStates.filter((state) => {
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => { const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => {
return from == state.owner && payloadFileState.path == state.path return from == state.owner && payloadFileState.path == state.path
}) })
return index == -1 return index == -1
}) })
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath) const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) { if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates) this.emit('fileDecoratorsChanged', this._fileStates)
}
} }
}
async clearFileDecorators(path?: string) { async clearFileDecorators(path?: string) {
const { from } = this.currentRequest const { from } = this.currentRequest
if (!from) return if (!from) return
const filteredState = this._fileStates.filter((state) => { const filteredState = this._fileStates.filter((state) => {
if(state.owner != from) return true if(state.owner != from) return true
if(path && state.path != path) return true if(path && state.path != path) return true
}) })
const newState = [...filteredState].sort(sortByPath) const newState = [...filteredState].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
} }
async clearAllFileDecorators() { }
this._fileStates = []
this.emit('fileDecoratorsChanged', []) async clearAllFileDecorators() {
} this._fileStates = []
this.emit('fileDecoratorsChanged', [])
}
} }
const sortByPath = (a: fileDecoration, b: fileDecoration) => { const sortByPath = (a: fileDecoration, b: fileDecoration) => {
if (a.path < b.path) { if (a.path < b.path) {
return -1; return -1;
} }
if (a.path > b.path) { if (a.path > b.path) {
return 1; return 1;
} }
return 0; return 0;
} }

File diff suppressed because it is too large Load Diff

@ -1,45 +1,45 @@
let parser: any let parser: any
self.onmessage = (e: MessageEvent) => { self.onmessage = (e: MessageEvent) => {
const data: any = e.data const data: any = e.data
switch (data.cmd) { switch (data.cmd) {
case 'load': case 'load':
{ {
(self as any).importScripts(e.data.url) (self as any).importScripts(e.data.url)
// @ts-ignore // @ts-ignore
parser = SolidityParser as any; parser = SolidityParser as any;
self.postMessage({ self.postMessage({
cmd: 'loaded', cmd: 'loaded',
}) })
break break
} }
case 'parse': case 'parse':
if (data.text && parser) { if (data.text && parser) {
try { try {
let startTime = performance.now() let startTime = performance.now()
const blocks = parser.parseBlock(data.text, { loc: true, range: true, tolerant: true }) const blocks = parser.parseBlock(data.text, { loc: true, range: true, tolerant: true })
const blockDuration = performance.now() - startTime const blockDuration = performance.now() - startTime
startTime = performance.now() startTime = performance.now()
const ast = parser.parse(data.text, { loc: true, range: true, tolerant: true }) const ast = parser.parse(data.text, { loc: true, range: true, tolerant: true })
const endTime = performance.now() const endTime = performance.now()
self.postMessage({ self.postMessage({
cmd: 'parsed', cmd: 'parsed',
timestamp: data.timestamp, timestamp: data.timestamp,
ast, ast,
text: data.text, text: data.text,
file: data.file, file: data.file,
duration: endTime - startTime, duration: endTime - startTime,
blockDuration, blockDuration,
blocks blocks
}) })
} catch (e) { } catch (e) {
// do nothing // do nothing
} }
}
break
} }
break
}
} }

@ -20,13 +20,13 @@ interface BlockDefinition {
} }
export default class CodeParserAntlrService { export default class CodeParserAntlrService {
plugin: CodeParser plugin: CodeParser
worker: Worker worker: Worker
parserStartTime: number = 0 parserStartTime: number = 0
workerTimer: NodeJS.Timer workerTimer: NodeJS.Timer
parserThreshold: number = 10 parserThreshold: number = 10
parserThresholdSampleAmount = 3 parserThresholdSampleAmount = 3
cache: { cache: {
[name: string]: { [name: string]: {
text: string, text: string,
ast: antlr.ParseResult | null, ast: antlr.ParseResult | null,
@ -36,253 +36,253 @@ export default class CodeParserAntlrService {
blockDurations?: number[] blockDurations?: number[]
} }
} = {}; } = {};
constructor(plugin: CodeParser) { constructor(plugin: CodeParser) {
this.plugin = plugin this.plugin = plugin
this.createWorker() this.createWorker()
} }
createWorker() { createWorker() {
this.worker = new Worker(new URL('./antlr-worker', import.meta.url)) this.worker = new Worker(new URL('./antlr-worker', import.meta.url))
this.worker.postMessage({ this.worker.postMessage({
cmd: 'load', cmd: 'load',
url: document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js', url: document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js',
}); });
const self = this const self = this
this.worker.addEventListener('message', function (ev) { this.worker.addEventListener('message', function (ev) {
switch (ev.data.cmd) { switch (ev.data.cmd) {
case 'parsed': case 'parsed':
if (ev.data.ast && self.parserStartTime === ev.data.timestamp) { if (ev.data.ast && self.parserStartTime === ev.data.timestamp) {
self.cache[ev.data.file] = { self.cache[ev.data.file] = {
...self.cache[ev.data.file], ...self.cache[ev.data.file],
text: ev.data.text, text: ev.data.text,
ast: ev.data.ast, ast: ev.data.ast,
duration: ev.data.duration, duration: ev.data.duration,
blocks: ev.data.blocks, blocks: ev.data.blocks,
blockDurations: self.cache[ev.data.file].blockDurations? [...self.cache[ev.data.file].blockDurations.slice(-self.parserThresholdSampleAmount), ev.data.blockDuration]: [ev.data.blockDuration] blockDurations: self.cache[ev.data.file].blockDurations? [...self.cache[ev.data.file].blockDurations.slice(-self.parserThresholdSampleAmount), ev.data.blockDuration]: [ev.data.blockDuration]
} }
self.setFileParsingState(ev.data.file) self.setFileParsingState(ev.data.file)
} }
break; break;
} }
}); });
} }
setFileParsingState(file: string) { setFileParsingState(file: string) {
if (this.cache[file]) { if (this.cache[file]) {
if (this.cache[file].blockDurations && this.cache[file].blockDurations.length > this.parserThresholdSampleAmount) { if (this.cache[file].blockDurations && this.cache[file].blockDurations.length > this.parserThresholdSampleAmount) {
// calculate average of durations to determine if the parsing should be disabled // calculate average of durations to determine if the parsing should be disabled
const values = [...this.cache[file].blockDurations] const values = [...this.cache[file].blockDurations]
const average = values.reduce((a, b) => a + b, 0) / values.length const average = values.reduce((a, b) => a + b, 0) / values.length
if (average > this.parserThreshold) { if (average > this.parserThreshold) {
this.cache[file].parsingEnabled = false this.cache[file].parsingEnabled = false
} else { } else {
this.cache[file].parsingEnabled = true this.cache[file].parsingEnabled = true
}
}
} }
}
} }
}
enableWorker() { enableWorker() {
if (!this.workerTimer) { if (!this.workerTimer) {
this.workerTimer = setInterval(() => { this.workerTimer = setInterval(() => {
this.setCurrentFileAST() this.setCurrentFileAST()
}, 5000) }, 5000)
}
} }
}
disableWorker() { disableWorker() {
clearInterval(this.workerTimer) clearInterval(this.workerTimer)
} }
async parseWithWorker(text: string, file: string) { async parseWithWorker(text: string, file: string) {
this.parserStartTime = Date.now() this.parserStartTime = Date.now()
this.worker.postMessage({ this.worker.postMessage({
cmd: 'parse', cmd: 'parse',
text, text,
timestamp: this.parserStartTime, timestamp: this.parserStartTime,
file, file,
parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true
}); });
} }
async parseSolidity(text: string) { async parseSolidity(text: string) {
const ast: antlr.ParseResult = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true }) const ast: antlr.ParseResult = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true })
return ast return ast
} }
/** /**
* Tries to parse the current file or the given text and returns the AST * Tries to parse the current file or the given text and returns the AST
* If the parsing fails it returns the last successful AST for this file * If the parsing fails it returns the last successful AST for this file
* @param text * @param text
* @returns * @returns
*/ */
async setCurrentFileAST(text: string | null = null) { async setCurrentFileAST(text: string | null = null) {
try { try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
if (!this.cache[this.plugin.currentFile]) { if (!this.cache[this.plugin.currentFile]) {
this.cache[this.plugin.currentFile] = { this.cache[this.plugin.currentFile] = {
text: '', text: '',
ast: null, ast: null,
parsingEnabled: true, parsingEnabled: true,
blockDurations: [] blockDurations: []
} }
} }
if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].text !== fileContent) { if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].text !== fileContent) {
try { try {
await this.parseWithWorker(fileContent, this.plugin.currentFile) await this.parseWithWorker(fileContent, this.plugin.currentFile)
} catch (e) { } catch (e) {
// do nothing
}
}
}
} catch (e) {
// do nothing // do nothing
}
} }
}
} catch (e) {
// do nothing
} }
}
/** /**
* Lists the AST nodes from the current file parser * Lists the AST nodes from the current file parser
* These nodes need to be changed to match the node types returned by the compiler * These nodes need to be changed to match the node types returned by the compiler
* @returns * @returns
*/ */
async listAstNodes() { async listAstNodes() {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.cache[this.plugin.currentFile]) return if (!this.cache[this.plugin.currentFile]) return
const nodes: AstNode[] = []; const nodes: AstNode[] = [];
(SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, { (SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, {
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => { StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => {
if (node.variables) { if (node.variables) {
for (const variable of node.variables) { for (const variable of node.variables) {
nodes.push({ ...variable, nodeType: 'VariableDeclaration', id: null, src: null }) nodes.push({ ...variable, nodeType: 'VariableDeclaration', id: null, src: null })
} }
} }
}, },
VariableDeclaration: (node: antlr.VariableDeclaration) => { VariableDeclaration: (node: antlr.VariableDeclaration) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
UserDefinedTypeName: (node: antlr.UserDefinedTypeName) => { UserDefinedTypeName: (node: antlr.UserDefinedTypeName) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
FunctionDefinition: (node: antlr.FunctionDefinition) => { FunctionDefinition: (node: antlr.FunctionDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
ContractDefinition: (node: antlr.ContractDefinition) => { ContractDefinition: (node: antlr.ContractDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
MemberAccess: function (node: antlr.MemberAccess) { MemberAccess: function (node: antlr.MemberAccess) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
Identifier: function (node: antlr.Identifier) { Identifier: function (node: antlr.Identifier) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
EventDefinition: function (node: antlr.EventDefinition) { EventDefinition: function (node: antlr.EventDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
ModifierDefinition: function (node: antlr.ModifierDefinition) { ModifierDefinition: function (node: antlr.ModifierDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
InvalidNode: function (node: antlr.InvalidNode) { InvalidNode: function (node: antlr.InvalidNode) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
EnumDefinition: function (node: antlr.EnumDefinition) { EnumDefinition: function (node: antlr.EnumDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}, },
StructDefinition: function (node: antlr.StructDefinition) { StructDefinition: function (node: antlr.StructDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
} }
}) })
return nodes return nodes
} }
/** /**
* *
* @param ast * @param ast
* @returns * @returns
*/ */
async getLastNodeInLine(ast: string) { async getLastNodeInLine(ast: string) {
let lastNode: any let lastNode: any
const checkLastNode = (node: antlr.MemberAccess | antlr.Identifier) => { const checkLastNode = (node: antlr.MemberAccess | antlr.Identifier) => {
if (lastNode && lastNode.range && lastNode.range[1]) { if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) { if (node.range[1] > lastNode.range[1]) {
lastNode = node lastNode = node
}
} else {
lastNode = node
}
} }
} else {
lastNode = node
}
}
(SolidityParser as any).visit(ast, { (SolidityParser as any).visit(ast, {
MemberAccess: function (node: antlr.MemberAccess) { MemberAccess: function (node: antlr.MemberAccess) {
checkLastNode(node) checkLastNode(node)
}, },
Identifier: function (node: antlr.Identifier) { Identifier: function (node: antlr.Identifier) {
checkLastNode(node) checkLastNode(node)
} }
}) })
if (lastNode && lastNode.expression) { if (lastNode && lastNode.expression) {
return lastNode.expression return lastNode.expression
}
return lastNode
} }
/* return lastNode
}
/*
* get the code blocks of the current file * get the code blocks of the current file
*/ */
async getCurrentFileBlocks(text: string | null = null) { async getCurrentFileBlocks(text: string | null = null) {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.cache[this.plugin.currentFile]) { if (this.cache[this.plugin.currentFile]) {
if (!this.cache[this.plugin.currentFile].parsingEnabled) { if (!this.cache[this.plugin.currentFile].parsingEnabled) {
return return
} }
} }
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
try { try {
const startTime = Date.now() const startTime = Date.now()
const blocks = (SolidityParser as any).parseBlock(fileContent, { loc: true, range: true, tolerant: true }) const blocks = (SolidityParser as any).parseBlock(fileContent, { loc: true, range: true, tolerant: true })
if(this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].blockDurations){ if(this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].blockDurations){
this.cache[this.plugin.currentFile].blockDurations = [...this.cache[this.plugin.currentFile].blockDurations.slice(-this.parserThresholdSampleAmount), Date.now() - startTime] this.cache[this.plugin.currentFile].blockDurations = [...this.cache[this.plugin.currentFile].blockDurations.slice(-this.parserThresholdSampleAmount), Date.now() - startTime]
this.setFileParsingState(this.plugin.currentFile) this.setFileParsingState(this.plugin.currentFile)
}
if (blocks) this.cache[this.plugin.currentFile].blocks = blocks
return blocks
} catch (e) {
// do nothing
}
} }
if (blocks) this.cache[this.plugin.currentFile].blocks = blocks
return blocks
} catch (e) {
// do nothing
}
} }
}
/** /**
* Returns the block surrounding the given position * Returns the block surrounding the given position
* For example if the position is in the middle of a function, it will return the function * For example if the position is in the middle of a function, it will return the function
* @param {position} position * @param {position} position
* @param {string} text // optional * @param {string} text // optional
* @return {any} * @return {any}
* */ * */
async getANTLRBlockAtPosition(position: any, text: string = null) { async getANTLRBlockAtPosition(position: any, text: string = null) {
const blocks: any[] = await this.getCurrentFileBlocks(text) const blocks: any[] = await this.getCurrentFileBlocks(text)
const walkAst = (blocks) => { const walkAst = (blocks) => {
let nodeFound = null let nodeFound = null
for (const object of blocks) { for (const object of blocks) {
if (object.start <= position) { if (object.start <= position) {
nodeFound = object nodeFound = object
break break
}
}
return nodeFound
} }
if (!blocks) return }
blocks.reverse() return nodeFound
const block = walkAst(blocks)
return block
} }
if (!blocks) return
blocks.reverse()
const block = walkAst(blocks)
return block
}
} }

@ -34,253 +34,253 @@ type errorMarker = {
file: string file: string
} }
export default class CodeParserCompiler { export default class CodeParserCompiler {
plugin: CodeParser
compiler: any // used to compile the current file seperately from the main compiler
onAstFinished: (success: any, data: CompilationResult, source: CompilationSourceCode, input: any, version: any) => Promise<void>;
errorState: boolean;
gastEstimateTimeOut: any
constructor(
plugin: CodeParser plugin: CodeParser
compiler: any // used to compile the current file seperately from the main compiler ) {
onAstFinished: (success: any, data: CompilationResult, source: CompilationSourceCode, input: any, version: any) => Promise<void>; this.plugin = plugin
errorState: boolean; }
gastEstimateTimeOut: any
constructor(
plugin: CodeParser
) {
this.plugin = plugin
}
init() {
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSourceCode, input: any, version) => { init() {
this.plugin.call('editor', 'clearAnnotations')
this.errorState = true
const result = new CompilerAbstract('soljson', data, source, input)
let allErrors: errorMarker[] = []
if (data.errors || data.error) {
const file = await this.plugin.call('fileManager', 'getCurrentFile')
const currentFileContent = await this.plugin.call('fileManager', 'readFile', file)
const sources = result.getSourceCode().sources || []
if (data.error) {
if (data.error.formattedMessage) {
// mark this file as error
const errorMarker = await this.createErrorMarker(data.error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
}
} else {
for (const error of data.errors) {
if (!error.sourceLocation) {
// mark this file as error
const errorMarker = await this.createErrorMarker(error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
} else {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({
start: error.sourceLocation.start,
length: error.sourceLocation.end - error.sourceLocation.start
}, lineBreaks)
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSourceCode, input: any, version) => {
this.plugin.call('editor', 'clearAnnotations')
this.errorState = true
const result = new CompilerAbstract('soljson', data, source, input)
let allErrors: errorMarker[] = []
if (data.errors || data.error) {
const file = await this.plugin.call('fileManager', 'getCurrentFile')
const currentFileContent = await this.plugin.call('fileManager', 'readFile', file)
const sources = result.getSourceCode().sources || []
if (data.error) {
if (data.error.formattedMessage) {
// mark this file as error
const errorMarker = await this.createErrorMarker(data.error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
}
} else {
for (const error of data.errors) {
if (!error.sourceLocation) {
// mark this file as error
const errorMarker = await this.createErrorMarker(error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
} else {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({
start: error.sourceLocation.start,
length: error.sourceLocation.end - error.sourceLocation.start
}, lineBreaks)
const filePath = error.sourceLocation.file
const fileTarget = await this.plugin.call('fileManager', 'getUrlFromPath', filePath)
const importFilePositions = await this.getPositionForImportErrors(fileTarget.file, currentFileContent) const filePath = error.sourceLocation.file
for (const importFilePosition of importFilePositions) { const fileTarget = await this.plugin.call('fileManager', 'getUrlFromPath', filePath)
for (const line of importFilePosition.lines) {
allErrors = [...allErrors, await this.createErrorMarker(error, file, line.position)]
}
}
allErrors = [...allErrors, await this.createErrorMarker(error, filePath, lineColumn)] const importFilePositions = await this.getPositionForImportErrors(fileTarget.file, currentFileContent)
} for (const importFilePosition of importFilePositions) {
} for (const line of importFilePosition.lines) {
allErrors = [...allErrors, await this.createErrorMarker(error, file, line.position)]
} }
}
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors') allErrors = [...allErrors, await this.createErrorMarker(error, filePath, lineColumn)]
if (displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors)
this.addDecorators(allErrors, sources)
} else {
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
await this.clearDecorators(result.getSourceCode().sources)
} }
}
}
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if (displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors)
this.addDecorators(allErrors, sources)
} else {
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
await this.clearDecorators(result.getSourceCode().sources)
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false
this.plugin.nodeIndex = {
declarations: {},
flatReferences: {},
nodesPerFile: {},
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false
this.plugin.nodeIndex = {
declarations: {},
flatReferences: {},
nodesPerFile: {},
}
this.plugin._buildIndex(data, source)
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
await this.plugin.gasService.showGasEstimates()
this.plugin.emit('astFinished')
}
this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined).then((result) => cb(null, result)).catch((error) => cb(error.message))) this.plugin._buildIndex(data, source)
this.compiler.event.register('compilationFinished', this.onAstFinished) // cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
await this.plugin.gasService.showGasEstimates()
this.plugin.emit('astFinished')
} }
// COMPILER this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.compiler.event.register('compilationFinished', this.onAstFinished)
}
/** // COMPILER
/**
* *
* @returns * @returns
*/ */
async compile() { async compile() {
try { try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const state = await this.plugin.call('solidity', 'getCompilerState') const state = await this.plugin.call('solidity', 'getCompilerState')
this.compiler.set('optimize', state.optimize) this.compiler.set('optimize', state.optimize)
this.compiler.set('evmVersion', state.evmVersion) this.compiler.set('evmVersion', state.evmVersion)
this.compiler.set('language', state.language) this.compiler.set('language', state.language)
this.compiler.set('runs', state.runs) this.compiler.set('runs', state.runs)
this.compiler.set('useFileConfiguration', true) this.compiler.set('useFileConfiguration', true)
this.compiler.set('compilerRetriggerMode', CompilerRetriggerMode.retrigger) this.compiler.set('compilerRetriggerMode', CompilerRetriggerMode.retrigger)
const configFileContent = { const configFileContent = {
"language": "Solidity", "language": "Solidity",
"settings": { "settings": {
"optimizer": { "optimizer": {
"enabled": state.optimize, "enabled": state.optimize,
"runs": state.runs "runs": state.runs
}, },
"outputSelection": { "outputSelection": {
"*": { "*": {
"": ["ast"], "": ["ast"],
"*": ["evm.gasEstimates"] "*": ["evm.gasEstimates"]
} }
}, },
"evmVersion": state.evmVersion && state.evmVersion.toString() || "berlin", "evmVersion": state.evmVersion && state.evmVersion.toString() || "berlin",
} }
}
this.compiler.set('configFileContent', JSON.stringify(configFileContent))
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.plugin.currentFile) return
const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
const sources = { [this.plugin.currentFile]: { content } }
this.compiler.compile(sources, this.plugin.currentFile)
}
} catch (e) {
// do nothing
} }
}
async addDecorators(allErrors: errorMarker[], sources: any) { this.compiler.set('configFileContent', JSON.stringify(configFileContent))
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!displayErrors) return if (!this.plugin.currentFile) return
const errorsPerFiles: { [fileName: string]: errorMarker[] } = {} const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
for (const error of allErrors) { const sources = { [this.plugin.currentFile]: { content } }
if (!errorsPerFiles[error.file]) { this.compiler.compile(sources, this.plugin.currentFile)
errorsPerFiles[error.file] = [] }
} } catch (e) {
errorsPerFiles[error.file].push(error) // do nothing
} }
}
const errorPriority = { async addDecorators(allErrors: errorMarker[], sources: any) {
'error': 0, const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
'warning': 1, if (!displayErrors) return
} const errorsPerFiles: { [fileName: string]: errorMarker[] } = {}
for (const error of allErrors) {
if (!errorsPerFiles[error.file]) {
errorsPerFiles[error.file] = []
}
errorsPerFiles[error.file].push(error)
}
// sort errorPerFiles by error priority const errorPriority = {
const sortedErrorsPerFiles: { [fileName: string]: errorMarker[] } = {} 'error': 0,
for (const fileName in errorsPerFiles) { 'warning': 1,
const errors = errorsPerFiles[fileName] }
errors.sort((a, b) => {
return errorPriority[a.severity] - errorPriority[b.severity]
}
)
sortedErrorsPerFiles[fileName] = errors
}
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName])
// add decorators
const decorators: fileDecoration[] = []
for (const fileName in sortedErrorsPerFiles) {
const errors = sortedErrorsPerFiles[fileName]
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: errors[0].severity == MarkerSeverity.Error ? fileDecorationType.Error : fileDecorationType.Warning,
fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: errors.length.toString(),
owner: 'code-parser',
bubble: true,
comment: errors.map((error) => error.message),
}
decorators.push(decorator)
}
for (const fileName of filesWithOutErrors) {
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors)
// sort errorPerFiles by error priority
const sortedErrorsPerFiles: { [fileName: string]: errorMarker[] } = {}
for (const fileName in errorsPerFiles) {
const errors = errorsPerFiles[fileName]
errors.sort((a, b) => {
return errorPriority[a.severity] - errorPriority[b.severity]
}
)
sortedErrorsPerFiles[fileName] = errors
}
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName])
// add decorators
const decorators: fileDecoration[] = []
for (const fileName in sortedErrorsPerFiles) {
const errors = sortedErrorsPerFiles[fileName]
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: errors[0].severity == MarkerSeverity.Error ? fileDecorationType.Error : fileDecorationType.Warning,
fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: errors.length.toString(),
owner: 'code-parser',
bubble: true,
comment: errors.map((error) => error.message),
}
decorators.push(decorator)
}
for (const fileName of filesWithOutErrors) {
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
} }
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors)
async createErrorMarker(error: any, filePath: string, lineColumn): Promise<errorMarker> { }
return {
message: error.formattedMessage, async createErrorMarker(error: any, filePath: string, lineColumn): Promise<errorMarker> {
severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning, return {
position: { message: error.formattedMessage,
start: { severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning,
line: ((lineColumn.start && lineColumn.start.line) || 0) + 1, position: {
column: ((lineColumn.start && lineColumn.start.column) || 0) + 1 start: {
}, line: ((lineColumn.start && lineColumn.start.line) || 0) + 1,
end: { column: ((lineColumn.start && lineColumn.start.column) || 0) + 1
line: ((lineColumn.end && lineColumn.end.line) || 0) + 1, },
column: ((lineColumn.end && lineColumn.end.column) || 0) + 1 end: {
} line: ((lineColumn.end && lineColumn.end.line) || 0) + 1,
} column: ((lineColumn.end && lineColumn.end.column) || 0) + 1
, file: filePath
} }
}
, file: filePath
} }
}
async clearDecorators(sources: any) { async clearDecorators(sources: any) {
const decorators: fileDecoration[] = [] const decorators: fileDecoration[] = []
if (!sources) return if (!sources) return
for (const fileName of Object.keys(sources)) { for (const fileName of Object.keys(sources)) {
const decorator: fileDecoration = { const decorator: fileDecoration = {
path: fileName, path: fileName,
isDirectory: false, isDirectory: false,
fileStateType: fileDecorationType.None, fileStateType: fileDecorationType.None,
fileStateLabelClass: '', fileStateLabelClass: '',
fileStateIconClass: '', fileStateIconClass: '',
fileStateIcon: '', fileStateIcon: '',
text: '', text: '',
owner: 'code-parser', owner: 'code-parser',
bubble: false bubble: false
} }
decorators.push(decorator) decorators.push(decorator)
} }
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
} }
async getPositionForImportErrors(importedFileName: string, text: string) { async getPositionForImportErrors(importedFileName: string, text: string) {
const re = new RegExp(importedFileName, 'gi') const re = new RegExp(importedFileName, 'gi')
const result: SearchResultLine[] = findLinesInStringWithMatch( const result: SearchResultLine[] = findLinesInStringWithMatch(
text, text,
re re
) )
return result return result
} }
} }

@ -3,76 +3,76 @@ import { lineText } from '@remix-ui/editor'
import { lastCompilationResult } from '@remixproject/plugin-api'; import { lastCompilationResult } from '@remixproject/plugin-api';
export default class CodeParserGasService { export default class CodeParserGasService {
plugin: CodeParser plugin: CodeParser
constructor(plugin: CodeParser) { constructor(plugin: CodeParser) {
this.plugin = plugin this.plugin = plugin
} }
async getGasEstimates(fileName: string) { async getGasEstimates(fileName: string) {
if (!fileName) { if (!fileName) {
fileName = await this.plugin.currentFile fileName = await this.plugin.currentFile
} }
if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) { if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
const estimates: any = [] const estimates: any = []
for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) { for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
if (this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes) { if (this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes) {
const nodes = this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes const nodes = this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes
for (const node of Object.values(nodes) as any[]) { for (const node of Object.values(nodes) as any[]) {
if (node.gasEstimate) { if (node.gasEstimate) {
estimates.push({ estimates.push({
node, node,
range: await this.plugin.getLineColumnOfNode(node) range: await this.plugin.getLineColumnOfNode(node)
}) })
}
}
}
} }
return estimates }
} }
}
return estimates
} }
}
async showGasEstimates() {
const showGasConfig = await this.plugin.call('config', 'getAppParameter', 'show-gas')
if(!showGasConfig) {
await this.plugin.call('editor', 'discardLineTexts')
return
}
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile) async showGasEstimates() {
const showGasConfig = await this.plugin.call('config', 'getAppParameter', 'show-gas')
if(!showGasConfig) {
await this.plugin.call('editor', 'discardLineTexts')
return
}
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile)
const friendlyNames = { const friendlyNames = {
'executionCost': 'Estimated execution cost', 'executionCost': 'Estimated execution cost',
'codeDepositCost': 'Estimated code deposit cost', 'codeDepositCost': 'Estimated code deposit cost',
'creationCost': 'Estimated creation cost', 'creationCost': 'Estimated creation cost',
}
await this.plugin.call('editor', 'discardLineTexts')
if (gasEstimates) {
for (const estimate of gasEstimates) {
const linetext: lineText = {
content: Object.entries(estimate.node.gasEstimate).map(([, value]) => `${value} gas`).join(' '),
position: estimate.range,
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
from: 'codeParser',
hoverMessage: [{
value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`,
},
],
} }
await this.plugin.call('editor', 'discardLineTexts')
if (gasEstimates) {
for (const estimate of gasEstimates) {
const linetext: lineText = {
content: Object.entries(estimate.node.gasEstimate).map(([, value]) => `${value} gas`).join(' '),
position: estimate.range,
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
from: 'codeParser',
hoverMessage: [{
value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`,
},
],
}
this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName) this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName)
} }
}
} }
}
} }

@ -8,81 +8,81 @@ export type CodeParserImportsData= {
} }
export default class CodeParserImports { export default class CodeParserImports {
plugin: CodeParser plugin: CodeParser
data: CodeParserImportsData = {} data: CodeParserImportsData = {}
constructor(plugin: CodeParser) { constructor(plugin: CodeParser) {
this.plugin = plugin this.plugin = plugin
this.init() this.init()
} }
async getImports(){ async getImports(){
return this.data return this.data
} }
async init() { async init() {
// @ts-ignore // @ts-ignore
const txt = await import('raw-loader!libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt') const txt = await import('raw-loader!libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt')
this.data.modules = txt.default.split('\n') this.data.modules = txt.default.split('\n')
.filter(x => x !== '') .filter(x => x !== '')
.map(x => x.replace('./node_modules/', '')) .map(x => x.replace('./node_modules/', ''))
.filter(x => { .filter(x => {
if(x.includes('@openzeppelin')) { if(x.includes('@openzeppelin')) {
return !x.includes('mock') return !x.includes('mock')
}else{ }else{
return true return true
} }
}) })
// get unique first words of the values in the array // get unique first words of the values in the array
this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))] this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))]
} }
setFileTree = async () => { setFileTree = async () => {
this.data.files = await this.getDirectory('/') this.data.files = await this.getDirectory('/')
this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git')) this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git'))
} }
getDirectory = async (dir: string) => { getDirectory = async (dir: string) => {
let result = [] let result = []
let files = {} let files = {}
try { try {
if (await this.plugin.call('fileManager', 'exists', dir)) { if (await this.plugin.call('fileManager', 'exists', dir)) {
files = await this.plugin.call('fileManager', 'readdir', dir) files = await this.plugin.call('fileManager', 'readdir', dir)
} }
} catch (e) {} } catch (e) {}
const fileArray = this.normalize(files) const fileArray = this.normalize(files)
for (const fi of fileArray) { for (const fi of fileArray) {
if (fi) { if (fi) {
const type = fi.data.isDirectory const type = fi.data.isDirectory
if (type === true) { if (type === true) {
result = [...result, ...(await this.getDirectory(`${fi.filename}`))] result = [...result, ...(await this.getDirectory(`${fi.filename}`))]
} else { } else {
result = [...result, fi.filename] result = [...result, fi.filename]
}
}
} }
return result }
} }
return result
}
normalize = filesList => { normalize = filesList => {
const folders = [] const folders = []
const files = [] const files = []
Object.keys(filesList || {}).forEach(key => { Object.keys(filesList || {}).forEach(key => {
if (filesList[key].isDirectory) { if (filesList[key].isDirectory) {
folders.push({ folders.push({
filename: key, filename: key,
data: filesList[key] data: filesList[key]
})
} else {
files.push({
filename: key,
data: filesList[key]
})
}
}) })
return [...folders, ...files] } else {
} files.push({
filename: key,
data: filesList[key]
})
}
})
return [...folders, ...files]
}
} }

@ -596,132 +596,132 @@ type ASTTypeMap = ASTMap<ASTNode>
export const astNodeTypes = [ export const astNodeTypes = [
'SourceUnit', 'SourceUnit',
'PragmaDirective', 'PragmaDirective',
'ImportDirective', 'ImportDirective',
'ContractDefinition', 'ContractDefinition',
'InheritanceSpecifier', 'InheritanceSpecifier',
'StateVariableDeclaration', 'StateVariableDeclaration',
'UsingForDeclaration', 'UsingForDeclaration',
'StructDefinition', 'StructDefinition',
'ModifierDefinition', 'ModifierDefinition',
'ModifierInvocation', 'ModifierInvocation',
'FunctionDefinition', 'FunctionDefinition',
'EventDefinition', 'EventDefinition',
'CustomErrorDefinition', 'CustomErrorDefinition',
'RevertStatement', 'RevertStatement',
'EnumValue', 'EnumValue',
'EnumDefinition', 'EnumDefinition',
'VariableDeclaration', 'VariableDeclaration',
'UserDefinedTypeName', 'UserDefinedTypeName',
'Mapping', 'Mapping',
'ArrayTypeName', 'ArrayTypeName',
'FunctionTypeName', 'FunctionTypeName',
'Block', 'Block',
'ExpressionStatement', 'ExpressionStatement',
'IfStatement', 'IfStatement',
'WhileStatement', 'WhileStatement',
'ForStatement', 'ForStatement',
'InlineAssemblyStatement', 'InlineAssemblyStatement',
'DoWhileStatement', 'DoWhileStatement',
'ContinueStatement', 'ContinueStatement',
'Break', 'Break',
'Continue', 'Continue',
'BreakStatement', 'BreakStatement',
'ReturnStatement', 'ReturnStatement',
'EmitStatement', 'EmitStatement',
'ThrowStatement', 'ThrowStatement',
'VariableDeclarationStatement', 'VariableDeclarationStatement',
'ElementaryTypeName', 'ElementaryTypeName',
'FunctionCall', 'FunctionCall',
'AssemblyBlock', 'AssemblyBlock',
'AssemblyCall', 'AssemblyCall',
'AssemblyLocalDefinition', 'AssemblyLocalDefinition',
'AssemblyAssignment', 'AssemblyAssignment',
'AssemblyStackAssignment', 'AssemblyStackAssignment',
'LabelDefinition', 'LabelDefinition',
'AssemblySwitch', 'AssemblySwitch',
'AssemblyCase', 'AssemblyCase',
'AssemblyFunctionDefinition', 'AssemblyFunctionDefinition',
'AssemblyFunctionReturns', 'AssemblyFunctionReturns',
'AssemblyFor', 'AssemblyFor',
'AssemblyIf', 'AssemblyIf',
'SubAssembly', 'SubAssembly',
'TupleExpression', 'TupleExpression',
'NameValueExpression', 'NameValueExpression',
'BooleanLiteral', 'BooleanLiteral',
'NumberLiteral', 'NumberLiteral',
'Identifier', 'Identifier',
'BinaryOperation', 'BinaryOperation',
'UnaryOperation', 'UnaryOperation',
'NewExpression', 'NewExpression',
'Conditional', 'Conditional',
'StringLiteral', 'StringLiteral',
'HexLiteral', 'HexLiteral',
'HexNumber', 'HexNumber',
'DecimalNumber', 'DecimalNumber',
'MemberAccess', 'MemberAccess',
'IndexAccess', 'IndexAccess',
'IndexRangeAccess', 'IndexRangeAccess',
'NameValueList', 'NameValueList',
'UncheckedStatement', 'UncheckedStatement',
'TryStatement', 'TryStatement',
'CatchClause', 'CatchClause',
'FileLevelConstant', 'FileLevelConstant',
'AssemblyMemberAccess', 'AssemblyMemberAccess',
'TypeDefinition', 'TypeDefinition',
'InvalidNode' 'InvalidNode'
] as const ] as const
export const binaryOpValues = [ export const binaryOpValues = [
'+', '+',
'-', '-',
'*', '*',
'/', '/',
'**', '**',
'%', '%',
'<<', '<<',
'>>', '>>',
'&&', '&&',
'||', '||',
',,', ',,',
'&', '&',
',', ',',
'^', '^',
'<', '<',
'>', '>',
'<=', '<=',
'>=', '>=',
'==', '==',
'!=', '!=',
'=', '=',
',=', ',=',
'^=', '^=',
'&=', '&=',
'<<=', '<<=',
'>>=', '>>=',
'+=', '+=',
'-=', '-=',
'*=', '*=',
'/=', '/=',
'%=', '%=',
'|', '|',
'|=', '|=',
] as const ] as const
export type BinOp = typeof binaryOpValues[number] export type BinOp = typeof binaryOpValues[number]
export const unaryOpValues = [ export const unaryOpValues = [
'-', '-',
'+', '+',
'++', '++',
'--', '--',
'~', '~',
'after', 'after',
'delete', 'delete',
'!', '!',
] as const ] as const
export type UnaryOp = typeof unaryOpValues[number] export type UnaryOp = typeof unaryOpValues[number]

@ -49,12 +49,12 @@ export class PermissionHandlerPlugin extends Plugin {
switchMode (from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) { switchMode (from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) {
if (sensitiveCall) { if (sensitiveCall) {
set set
? this.sessionPermissions[to.name][method][from.name] = {} ? this.sessionPermissions[to.name][method][from.name] = {}
: delete this.sessionPermissions[to.name][method][from.name] : delete this.sessionPermissions[to.name][method][from.name]
} else { } else {
set set
? this.permissions[to.name][method][from.name] = {} ? this.permissions[to.name][method][from.name] = {}
: delete this.permissions[to.name][method][from.name] : delete this.permissions[to.name][method][from.name]
} }
} }

@ -84,20 +84,20 @@ export class SolidityScript extends Plugin {
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div> const finalLogs = <div><div><b>console.log:</b></div>
{ {
hhlogs.map((log) => { hhlogs.map((log) => {
let formattedLog let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log, // Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args // which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
// For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6' // For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6'
// We check first arg to determine if 'util.format' is needed // We check first arg to determine if 'util.format' is needed
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) { if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) {
formattedLog = format(log[0], ...log.slice(1)) formattedLog = format(log[0], ...log.slice(1))
} else { } else {
formattedLog = log.join(' ') formattedLog = log.join(' ')
} }
return <div>{formattedLog}</div> return <div>{formattedLog}</div>
})} })}
</div> </div>
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs) this.call('terminal', 'logHtml', finalLogs)

@ -16,39 +16,39 @@ const parser = (window as any).SolidityParser
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
const profile = { const profile = {
name: 'solidityumlgen', name: 'solidityumlgen',
displayName: 'Solidity UML Generator', displayName: 'Solidity UML Generator',
description: 'Generates UML diagram in svg format from last compiled contract', description: 'Generates UML diagram in svg format from last compiled contract',
location: 'mainPanel', location: 'mainPanel',
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'], methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'],
events: [], events: [],
} }
const themeCollection = [ const themeCollection = [
{ themeName: 'HackerOwl', backgroundColor: '#011628', textColor: '#babbcc', { themeName: 'HackerOwl', backgroundColor: '#011628', textColor: '#babbcc',
shapeColor: '#8694a1',fillColor: '#011C32'}, shapeColor: '#8694a1',fillColor: '#011C32'},
{ themeName: 'Cerulean', backgroundColor: '#ffffff', textColor: '#343a40', { themeName: 'Cerulean', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8f9fa'}, shapeColor: '#343a40',fillColor: '#f8f9fa'},
{ themeName: 'Cyborg', backgroundColor: '#060606', textColor: '#adafae', { themeName: 'Cyborg', backgroundColor: '#060606', textColor: '#adafae',
shapeColor: '#adafae', fillColor: '#222222'}, shapeColor: '#adafae', fillColor: '#222222'},
{ themeName: 'Dark', backgroundColor: '#222336', textColor: '#babbcc', { themeName: 'Dark', backgroundColor: '#222336', textColor: '#babbcc',
shapeColor: '#babbcc',fillColor: '#2a2c3f'}, shapeColor: '#babbcc',fillColor: '#2a2c3f'},
{ themeName: 'Flatly', backgroundColor: '#ffffff', textColor: '#343a40', { themeName: 'Flatly', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#7b8a8b',fillColor: '#ffffff'}, shapeColor: '#7b8a8b',fillColor: '#ffffff'},
{ themeName: 'Black', backgroundColor: '#1a1a1a', textColor: '#babbcc', { themeName: 'Black', backgroundColor: '#1a1a1a', textColor: '#babbcc',
shapeColor: '#b5b4bc',fillColor: '#1f2020'}, shapeColor: '#b5b4bc',fillColor: '#1f2020'},
{ themeName: 'Light', backgroundColor: '#eef1f6', textColor: '#3b445e', { themeName: 'Light', backgroundColor: '#eef1f6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#ffffff'}, shapeColor: '#343a40',fillColor: '#ffffff'},
{ themeName: 'Midcentury', backgroundColor: '#DBE2E0', textColor: '#11556c', { themeName: 'Midcentury', backgroundColor: '#DBE2E0', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#eeede9'}, shapeColor: '#343a40',fillColor: '#eeede9'},
{ themeName: 'Spacelab', backgroundColor: '#ffffff', textColor: '#343a40', { themeName: 'Spacelab', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#333333', fillColor: '#eeeeee'}, shapeColor: '#333333', fillColor: '#eeeeee'},
{ themeName: 'Candy', backgroundColor: '#d5efff', textColor: '#11556c', { themeName: 'Candy', backgroundColor: '#d5efff', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#fbe7f8' }, shapeColor: '#343a40',fillColor: '#fbe7f8' },
{ themeName: 'Violet', backgroundColor: '#f1eef6', textColor: '#3b445e', { themeName: 'Violet', backgroundColor: '#f1eef6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#f8fafe' }, shapeColor: '#343a40',fillColor: '#f8fafe' },
{ themeName: 'Unicorn', backgroundColor: '#f1eef6', textColor: '#343a40', { themeName: 'Unicorn', backgroundColor: '#f1eef6', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8fafe' }, shapeColor: '#343a40',fillColor: '#f8fafe' },
] ]
/** /**
@ -121,7 +121,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
getThemeCssVariables(cssVars: string) { getThemeCssVariables(cssVars: string) {
return window.getComputedStyle(document.documentElement) return window.getComputedStyle(document.documentElement)
.getPropertyValue(cssVars) .getPropertyValue(cssVars)
} }
private handleThemeChange() { private handleThemeChange() {
@ -246,10 +246,10 @@ interface Sol2umlClassOptions extends ClassOptions {
import { dirname } from 'path' import { dirname } from 'path'
import { convertClass2Dot } from 'sol2uml/lib/converterClass2Dot' import { convertClass2Dot } from 'sol2uml/lib/converterClass2Dot'
import { import {
Association, Association,
ClassStereotype, ClassStereotype,
ReferenceType, ReferenceType,
UmlClass, UmlClass,
} from 'sol2uml/lib/umlClass' } from 'sol2uml/lib/umlClass'
import { findAssociatedClass } from 'sol2uml/lib/associations' import { findAssociatedClass } from 'sol2uml/lib/associations'
@ -264,11 +264,11 @@ import { findAssociatedClass } from 'sol2uml/lib/associations'
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters. * @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
*/ */
export function convertUmlClasses2Dot( export function convertUmlClasses2Dot(
umlClasses: UmlClass[], umlClasses: UmlClass[],
clusterFolders: boolean = false, clusterFolders: boolean = false,
classOptions: Sol2umlClassOptions = {} classOptions: Sol2umlClassOptions = {}
): string { ): string {
let dotString: string = ` let dotString: string = `
digraph UmlClassDiagram { digraph UmlClassDiagram {
rankdir=BT rankdir=BT
arrowhead=open arrowhead=open
@ -276,124 +276,124 @@ bgcolor="${classOptions.backColor}"
edge [color="${classOptions.shapeColor}"] edge [color="${classOptions.shapeColor}"]
node [shape=record, style=filled, color="${classOptions.shapeColor}", fillcolor="${classOptions.fillColor}", fontcolor="${classOptions.textColor}"]` node [shape=record, style=filled, color="${classOptions.shapeColor}", fillcolor="${classOptions.fillColor}", fontcolor="${classOptions.textColor}"]`
// Sort UML Classes by folder of source file // Sort UML Classes by folder of source file
const umlClassesSortedByCodePath = sortUmlClassesByCodePath(umlClasses) const umlClassesSortedByCodePath = sortUmlClassesByCodePath(umlClasses)
let currentCodeFolder = '' let currentCodeFolder = ''
for (const umlClass of umlClassesSortedByCodePath) { for (const umlClass of umlClassesSortedByCodePath) {
const codeFolder = dirname(umlClass.relativePath) const codeFolder = dirname(umlClass.relativePath)
if (currentCodeFolder !== codeFolder) { if (currentCodeFolder !== codeFolder) {
// Need to close off the last subgraph if not the first // Need to close off the last subgraph if not the first
if (currentCodeFolder != '') { if (currentCodeFolder != '') {
dotString += '\n}' dotString += '\n}'
} }
dotString += ` dotString += `
subgraph ${getSubGraphName(clusterFolders)} { subgraph ${getSubGraphName(clusterFolders)} {
label="${codeFolder}"` label="${codeFolder}"`
currentCodeFolder = codeFolder currentCodeFolder = codeFolder
}
dotString += convertClass2Dot(umlClass, classOptions)
} }
dotString += convertClass2Dot(umlClass, classOptions)
}
// Need to close off the last subgraph if not the first // Need to close off the last subgraph if not the first
if (currentCodeFolder != '') { if (currentCodeFolder != '') {
dotString += '\n}' dotString += '\n}'
} }
dotString += addAssociationsToDot(umlClasses, classOptions) dotString += addAssociationsToDot(umlClasses, classOptions)
// Need to close off the last the digraph // Need to close off the last the digraph
dotString += '\n}' dotString += '\n}'
// debug(dotString) // debug(dotString)
return dotString return dotString
} }
let subGraphCount = 0 let subGraphCount = 0
function getSubGraphName(clusterFolders: boolean = false) { function getSubGraphName(clusterFolders: boolean = false) {
if (clusterFolders) { if (clusterFolders) {
return ` cluster_${subGraphCount++}` return ` cluster_${subGraphCount++}`
} }
return ` graph_${subGraphCount++}` return ` graph_${subGraphCount++}`
} }
function sortUmlClassesByCodePath(umlClasses: UmlClass[]): UmlClass[] { function sortUmlClassesByCodePath(umlClasses: UmlClass[]): UmlClass[] {
return umlClasses.sort((a, b) => { return umlClasses.sort((a, b) => {
if (a.relativePath < b.relativePath) { if (a.relativePath < b.relativePath) {
return -1 return -1
} }
if (a.relativePath > b.relativePath) { if (a.relativePath > b.relativePath) {
return 1 return 1
} }
return 0 return 0
}) })
} }
export function addAssociationsToDot( export function addAssociationsToDot(
umlClasses: UmlClass[], umlClasses: UmlClass[],
classOptions: ClassOptions = {} classOptions: ClassOptions = {}
): string { ): string {
let dotString: string = '' let dotString: string = ''
// for each class // for each class
for (const sourceUmlClass of umlClasses) { for (const sourceUmlClass of umlClasses) {
if (!classOptions.hideEnums) { if (!classOptions.hideEnums) {
// for each enum in the class // for each enum in the class
sourceUmlClass.enums.forEach((enumId) => { sourceUmlClass.enums.forEach((enumId) => {
// Has the enum been filtered out? eg depth limited // Has the enum been filtered out? eg depth limited
const targetUmlClass = umlClasses.find((c) => c.id === enumId) const targetUmlClass = umlClasses.find((c) => c.id === enumId)
if (targetUmlClass) { if (targetUmlClass) {
// Draw aggregated link from contract to contract level Enum // Draw aggregated link from contract to contract level Enum
dotString += `\n${enumId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]` dotString += `\n${enumId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`
}
})
} }
if (!classOptions.hideStructs) { })
// for each struct in the class }
sourceUmlClass.structs.forEach((structId) => { if (!classOptions.hideStructs) {
// Has the struct been filtered out? eg depth limited // for each struct in the class
const targetUmlClass = umlClasses.find((c) => c.id === structId) sourceUmlClass.structs.forEach((structId) => {
if (targetUmlClass) { // Has the struct been filtered out? eg depth limited
// Draw aggregated link from contract to contract level Struct const targetUmlClass = umlClasses.find((c) => c.id === structId)
dotString += `\n${structId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]` if (targetUmlClass) {
} // Draw aggregated link from contract to contract level Struct
}) dotString += `\n${structId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`
} }
})
}
// for each association in that class // for each association in that class
for (const association of Object.values(sourceUmlClass.associations)) { for (const association of Object.values(sourceUmlClass.associations)) {
const targetUmlClass = findAssociatedClass( const targetUmlClass = findAssociatedClass(
association, association,
sourceUmlClass, sourceUmlClass,
umlClasses umlClasses
) )
if (targetUmlClass) { if (targetUmlClass) {
dotString += addAssociationToDot( dotString += addAssociationToDot(
sourceUmlClass, sourceUmlClass,
targetUmlClass, targetUmlClass,
association, association,
classOptions classOptions
) )
} }
}
} }
}
return dotString return dotString
} }
function addAssociationToDot( function addAssociationToDot(
sourceUmlClass: UmlClass, sourceUmlClass: UmlClass,
targetUmlClass: UmlClass, targetUmlClass: UmlClass,
association: Association, association: Association,
classOptions: ClassOptions = {} classOptions: ClassOptions = {}
): string { ): string {
// do not include library or interface associations if hidden // do not include library or interface associations if hidden
// Or associations to Structs, Enums or Constants if they are hidden // Or associations to Structs, Enums or Constants if they are hidden
if ( if (
(classOptions.hideLibraries && (classOptions.hideLibraries &&
(sourceUmlClass.stereotype === ClassStereotype.Library || (sourceUmlClass.stereotype === ClassStereotype.Library ||
targetUmlClass.stereotype === ClassStereotype.Library)) || targetUmlClass.stereotype === ClassStereotype.Library)) ||
(classOptions.hideInterfaces && (classOptions.hideInterfaces &&
@ -408,28 +408,28 @@ function addAssociationToDot(
targetUmlClass.stereotype === ClassStereotype.Enum) || targetUmlClass.stereotype === ClassStereotype.Enum) ||
(classOptions.hideConstants && (classOptions.hideConstants &&
targetUmlClass.stereotype === ClassStereotype.Constant) targetUmlClass.stereotype === ClassStereotype.Constant)
) { ) {
return '' return ''
} }
let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [` let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`
if ( if (
association.referenceType == ReferenceType.Memory || association.referenceType == ReferenceType.Memory ||
(association.realization && (association.realization &&
targetUmlClass.stereotype === ClassStereotype.Interface) targetUmlClass.stereotype === ClassStereotype.Interface)
) { ) {
dotString += 'style=dashed, ' dotString += 'style=dashed, '
} }
if (association.realization) { if (association.realization) {
dotString += 'arrowhead=empty, arrowsize=3, ' dotString += 'arrowhead=empty, arrowsize=3, '
if (!targetUmlClass.stereotype) { if (!targetUmlClass.stereotype) {
dotString += 'weight=4, ' dotString += 'weight=4, '
} else { } else {
dotString += 'weight=3, ' dotString += 'weight=3, '
}
} }
}
return dotString + ']' return dotString + ']'
} }

@ -29,23 +29,23 @@ export class CustomForkVMProvider extends BasicVMProvider {
const body = () => { const body = () => {
return <div> return <div>
<span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span> <span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span>
<div> <div>
<label className="mt-3 mb-1">Node URL</label> <label className="mt-3 mb-1">Node URL</label>
<input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" /> <input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input data-id="CustomForkBlockNumber" name="blockNumber" type="text" defaultValue="latest" placeholder='block number or "latest"' className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" defaultValue="merge" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return <option value={Hardfork[value]} key={index}>{value}</option>
})}
</select>
</div>
</div> </div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input data-id="CustomForkBlockNumber" name="blockNumber" type="text" defaultValue="latest" placeholder='block number or "latest"' className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" defaultValue="merge" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return <option value={Hardfork[value]} key={index}>{value}</option>
})}
</select>
</div>
</div>
} }
const result = await ((): Promise<any> => { const result = await ((): Promise<any> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

@ -4,35 +4,35 @@ type registryEntry = {
} }
export default class Registry { export default class Registry {
private static instance: Registry; private static instance: Registry;
private state: any private state: any
private constructor () { private constructor () {
this.state = {} this.state = {}
} }
public static getInstance (): Registry {
if (!Registry.instance) {
Registry.instance = new Registry()
}
return Registry.instance public static getInstance (): Registry {
if (!Registry.instance) {
Registry.instance = new Registry()
} }
public put (entry: registryEntry) { return Registry.instance
if (this.state[entry.name]) return this.state[entry.name] }
const server = {
public put (entry: registryEntry) {
if (this.state[entry.name]) return this.state[entry.name]
const server = {
// uid: serveruid, // uid: serveruid,
api: entry.api api: entry.api
}
this.state[entry.name] = { server }
return server
} }
this.state[entry.name] = { server }
return server
}
public get (name: string) { public get (name: string) {
const state = this.state[name] const state = this.state[name]
if (!state) return if (!state) return
const server = state.server const server = state.server
return server return server
} }
} }

@ -81,10 +81,10 @@ class AnalysisTab extends ViewPlugin {
updateComponent(state) { updateComponent(state) {
return <RemixUiStaticAnalyser return <RemixUiStaticAnalyser
registry={state.registry} registry={state.registry}
analysisModule={state.analysisModule} analysisModule={state.analysisModule}
event={state.event} event={state.event}
/> />
} }
renderComponent () { renderComponent () {

@ -3,31 +3,31 @@ import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { SearchTab } from '@remix-ui/search' import { SearchTab } from '@remix-ui/search'
const profile = { const profile = {
name: 'search', name: 'search',
displayName: 'Search in files', displayName: 'Search in files',
methods: [''], methods: [''],
events: [], events: [],
icon: 'assets/img/search_icon.webp', icon: 'assets/img/search_icon.webp',
description: 'Find and replace in file explorer', description: 'Find and replace in file explorer',
kind: '', kind: '',
location: 'sidePanel', location: 'sidePanel',
documentation: '', documentation: '',
version: packageJson.version, version: packageJson.version,
maintainedBy: 'Remix' maintainedBy: 'Remix'
} }
export class SearchPlugin extends ViewPlugin { export class SearchPlugin extends ViewPlugin {
constructor () { constructor () {
super(profile) super(profile)
} }
render() { render() {
return ( return (
<div id='searchTab'> <div id='searchTab'>
<SearchTab plugin={this}></SearchTab> <SearchTab plugin={this}></SearchTab>
</div> </div>
); );
} }
} }

@ -39,7 +39,7 @@ export class ThemeModule extends Plugin {
this.themes = {} this.themes = {}
themes.map((theme) => { themes.map((theme) => {
this.themes[theme.name.toLocaleLowerCase()] = { this.themes[theme.name.toLocaleLowerCase()] = {
...theme, ...theme,
url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url
} }
}) })

@ -60,8 +60,8 @@ export class Web3ProviderModule extends Plugin {
reject(new Error('User denied permission')) reject(new Error('User denied permission'))
} }
}).catch((e) => { }).catch((e) => {
reject(e) reject(e)
}) })
}) })
} }

@ -136,8 +136,8 @@ export class RunTab extends ViewPlugin {
if (window && window.ethereum && !(window.ethereum.isTrustWallet || window.ethereum.selectedProvider?.isTrustWallet)) { if (window && window.ethereum && !(window.ethereum.isTrustWallet || window.ethereum.selectedProvider?.isTrustWallet)) {
const displayNameInjected = `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ? const displayNameInjected = `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ?
window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' : window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' :
window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' : window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' :
window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}` window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}`
await addProvider('injected', displayNameInjected, true, false) await addProvider('injected', displayNameInjected, true, false)
} else if (window && !window.ethereum) { } else if (window && !window.ethereum) {
// we still add "injected" if there's no provider (just so it's visible to the user). // we still add "injected" if there's no provider (just so it's visible to the user).

@ -31,7 +31,7 @@ export class LandingPage extends ViewPlugin {
render () { render () {
return <div id='landingPageHomeContainer' className='remixui_homeContainer justify-content-between bg-light d-flex' data-id='landingPageHomeContainer'> return <div id='landingPageHomeContainer' className='remixui_homeContainer justify-content-between bg-light d-flex' data-id='landingPageHomeContainer'>
<RemixUiHomeTab plugin={this} /> <RemixUiHomeTab plugin={this} />
</div> </div>
} }
} }

@ -312,52 +312,52 @@ export class Blockchain extends Plugin {
} }
async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) { async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) {
const { contractName, implementationAddress } = contractObject const { contractName, implementationAddress } = contractObject
const networkName = networkInfo.name === 'custom' ? networkInfo.name + '-' + networkInfo.id : networkInfo.name const networkName = networkInfo.name === 'custom' ? networkInfo.name + '-' + networkInfo.id : networkInfo.name
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`) const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`)
// TODO: make deploys folder read only. // TODO: make deploys folder read only.
if (hasPreviousDeploys) { if (hasPreviousDeploys) {
const deployments = await this.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`) const deployments = await this.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`)
const parsedDeployments = JSON.parse(deployments) const parsedDeployments = JSON.parse(deployments)
const proxyDeployment = parsedDeployments.deployments[proxyAddress] const proxyDeployment = parsedDeployments.deployments[proxyAddress]
if (proxyDeployment) { if (proxyDeployment) {
const oldImplementationAddress = proxyDeployment.implementationAddress const oldImplementationAddress = proxyDeployment.implementationAddress
const hasPreviousBuild = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`) const hasPreviousBuild = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`)
if (hasPreviousBuild) await this.call('fileManager', 'remove', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`) if (hasPreviousBuild) await this.call('fileManager', 'remove', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`)
} }
parsedDeployments.deployments[proxyAddress] = { parsedDeployments.deployments[proxyAddress] = {
date: new Date().toISOString(), date: new Date().toISOString(),
contractName: contractName, contractName: contractName,
fork: networkInfo.currentFork, fork: networkInfo.currentFork,
implementationAddress: implementationAddress, implementationAddress: implementationAddress,
solcOutput: contractObject.compiler.data, solcOutput: contractObject.compiler.data,
solcInput: contractObject.compiler.source solcInput: contractObject.compiler.source
}
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2))
} else {
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify({
id: networkInfo.id,
network: networkInfo.name,
deployments: {
[proxyAddress]: {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress
}
}
}, null, 2))
} }
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2))
} else {
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify({
id: networkInfo.id,
network: networkInfo.name,
deployments: {
[proxyAddress]: {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress
}
}
}, null, 2))
}
} }
async getEncodedFunctionHex (args, funABI) { async getEncodedFunctionHex (args, funABI) {
@ -601,9 +601,9 @@ export class Blockchain extends Plugin {
if (viewEtherScanLink) { if (viewEtherScanLink) {
this.call('terminal', 'logHtml', this.call('terminal', 'logHtml',
(<a href={etherScanLink(network.name, txhash)} target="_blank"> (<a href={etherScanLink(network.name, txhash)} target="_blank">
view on etherscan view on etherscan
</a>)) </a>))
} }
}) })
}) })
@ -808,20 +808,20 @@ export class Blockchain extends Plugin {
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div> const finalLogs = <div><div><b>console.log:</b></div>
{ {
hhlogs.map((log) => { hhlogs.map((log) => {
let formattedLog let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log, // Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args // which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
// For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6' // For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6'
// We check first arg to determine if 'util.format' is needed // We check first arg to determine if 'util.format' is needed
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) { if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) {
formattedLog = format(log[0], ...log.slice(1)) formattedLog = format(log[0], ...log.slice(1))
} else { } else {
formattedLog = log.join(' ') formattedLog = log.join(' ')
} }
return <div>{formattedLog}</div> return <div>{formattedLog}</div>
})} })}
</div> </div>
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs) this.call('terminal', 'logHtml', finalLogs)

@ -1,14 +1,14 @@
const transactionDetailsLinks = { const transactionDetailsLinks = {
Main: 'https://www.etherscan.io/tx/', Main: 'https://www.etherscan.io/tx/',
Rinkeby: 'https://rinkeby.etherscan.io/tx/', Rinkeby: 'https://rinkeby.etherscan.io/tx/',
Ropsten: 'https://ropsten.etherscan.io/tx/', Ropsten: 'https://ropsten.etherscan.io/tx/',
Kovan: 'https://kovan.etherscan.io/tx/', Kovan: 'https://kovan.etherscan.io/tx/',
Goerli: 'https://goerli.etherscan.io/tx/', Goerli: 'https://goerli.etherscan.io/tx/',
Sepolia: 'https://sepolia.etherscan.io/tx/' Sepolia: 'https://sepolia.etherscan.io/tx/'
} }
export function etherScanLink (network: string, hash: string): string { export function etherScanLink (network: string, hash: string): string {
if (transactionDetailsLinks[network]) { if (transactionDetailsLinks[network]) {
return transactionDetailsLinks[network] + hash return transactionDetailsLinks[network] + hash
}
} }
}

@ -20,7 +20,7 @@ export class InjectedProvider {
} }
async resetEnvironment () { async resetEnvironment () {
/* Do nothing. */ /* Do nothing. */
} }
async getBalanceInEther (address) { async getBalanceInEther (address) {

@ -30,7 +30,7 @@ export class NodeProvider {
} }
async resetEnvironment () { async resetEnvironment () {
/* Do nothing. */ /* Do nothing. */
} }
async getBalanceInEther (address) { async getBalanceInEther (address) {

@ -59,12 +59,12 @@ export class VMProvider {
reject(new Error(msg.data.error)) reject(new Error(msg.data.error))
} }
} else if (msg.data.cmd === 'newAccountResult') { } else if (msg.data.cmd === 'newAccountResult') {
if (this.newAccountCallback[msg.data.stamp]) { if (this.newAccountCallback[msg.data.stamp]) {
this.newAccountCallback[msg.data.stamp](msg.data.error, msg.data.result) this.newAccountCallback[msg.data.stamp](msg.data.error, msg.data.result)
delete this.newAccountCallback[msg.data.stamp] delete this.newAccountCallback[msg.data.stamp]
}
} }
} })
})
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']}) this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']})
}) })
} }

@ -4,74 +4,74 @@ let provider: Provider = null
self.onmessage = (e: MessageEvent) => { self.onmessage = (e: MessageEvent) => {
const data = e.data const data = e.data
switch (data.cmd) { switch (data.cmd) {
case 'init': case 'init':
{ {
provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber }) provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber })
provider.init().then(() => { provider.init().then(() => {
self.postMessage({ self.postMessage({
cmd: 'initiateResult', cmd: 'initiateResult',
stamp: data.stamp stamp: data.stamp
}) })
}).catch((error) => { }).catch((error) => {
self.postMessage({
cmd: 'initiateResult',
error,
stamp: data.stamp
})
})
break
}
case 'sendAsync':
{
if (provider) {
provider.sendAsync(data.query, (error, result) => {
self.postMessage({ self.postMessage({
cmd: 'initiateResult', cmd: 'sendAsyncResult',
error, error,
result,
stamp: data.stamp stamp: data.stamp
}) })
}) })
break } else {
self.postMessage({
cmd: 'sendAsyncResult',
error: 'Provider not instantiated',
result: null,
stamp: data.stamp
})
} }
case 'sendAsync':
{
if (provider) {
provider.sendAsync(data.query, (error, result) => {
self.postMessage({
cmd: 'sendAsyncResult',
error,
result,
stamp: data.stamp
})
})
} else {
self.postMessage({
cmd: 'sendAsyncResult',
error: 'Provider not instantiated',
result: null,
stamp: data.stamp
})
}
break break
}
case 'addAccount':
{
if (provider) {
provider.Accounts._addAccount(data.privateKey, data.balance)
} }
case 'addAccount':
{
if (provider) {
provider.Accounts._addAccount(data.privateKey, data.balance)
}
break break
}
case 'newAccount':
{
if (provider) {
provider.Accounts.newAccount((error, address: string) => {
if (error) {
self.postMessage({
cmd: 'newAccountResult',
error,
stamp: data.stamp
})
} else {
self.postMessage({
cmd: 'newAccountResult',
result: address,
stamp: data.stamp
})
}
})
} }
case 'newAccount':
{
if (provider) {
provider.Accounts.newAccount((error, address: string) => {
if (error) {
self.postMessage({
cmd: 'newAccountResult',
error,
stamp: data.stamp
})
} else {
self.postMessage({
cmd: 'newAccountResult',
result: address,
stamp: data.stamp
})
}
})
}
break break
} }
} }
} }

Loading…
Cancel
Save