commit
736099dad6
@ -0,0 +1,41 @@ |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import EventEmitter from 'events' |
||||
|
||||
class ConnectToExternalHttpProvider extends EventEmitter { |
||||
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser { |
||||
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`, |
||||
(result) => { |
||||
if (result.status as any === -1) { |
||||
console.log("No connection to external provider found. Adding one.", url) |
||||
browser |
||||
.click({ |
||||
locateStrategy: 'css selector', |
||||
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]', |
||||
abortOnFailure: false, |
||||
suppressNotFoundErrors: true, |
||||
timeout: 5000 |
||||
}) |
||||
.switchEnvironment('External Http Provider') |
||||
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') |
||||
.execute(() => { |
||||
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus() |
||||
}, [], () => { }) |
||||
.setValue('[data-id="modalDialogCustomPromp"]', url) |
||||
.modalFooterOKClick('basic-http-provider') |
||||
.perform((done) => { |
||||
done() |
||||
this.emit('complete') |
||||
}) |
||||
} else { |
||||
this.api.perform((done) => { |
||||
done() |
||||
this.emit('complete') |
||||
}) |
||||
} |
||||
} |
||||
) |
||||
return this |
||||
} |
||||
} |
||||
|
||||
module.exports = ConnectToExternalHttpProvider |
@ -0,0 +1,173 @@ |
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const testContract = { |
||||
name: 'contracts/test.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "contracts/base.sol"; |
||||
import "contracts/import1.sol"; |
||||
|
||||
contract test is base { |
||||
string public publicstring; |
||||
string private privatestring; |
||||
string internal internalstring; |
||||
|
||||
struct TestBookDefinition {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
TestBookDefinition public mybook; |
||||
enum MyEnum{ SMALL, MEDIUM, LARGE } |
||||
event MyEvent(uint abc); |
||||
importcontract importedcontract; |
||||
|
||||
modifier costs(uint price) { |
||||
if (msg.value >= price) { |
||||
_; |
||||
} |
||||
} |
||||
constructor(){
|
||||
|
||||
} |
||||
|
||||
function testing() public view { |
||||
|
||||
} |
||||
|
||||
function myprivatefunction() private { |
||||
|
||||
} |
||||
|
||||
function myinternalfunction() internal { |
||||
|
||||
} |
||||
|
||||
function myexternalfunction() external { |
||||
|
||||
} |
||||
}`}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const baseContract = { |
||||
name: 'contracts/base.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "contracts/baseofbase.sol"; |
||||
|
||||
contract base is baseofbase { |
||||
event BaseEvent(address indexed _from, uint _value); |
||||
enum BaseEnum{ SMALL, MEDIUM, LARGE } |
||||
struct Book {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
Book public book; |
||||
}`}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const baseOfBaseContract = { |
||||
name: 'contracts/baseofbase.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
contract baseofbase { |
||||
|
||||
struct BaseBook {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
BaseBook public basebook; |
||||
|
||||
string private basestring; |
||||
string internal internalbasestring; |
||||
|
||||
function privatebase() private { |
||||
|
||||
} |
||||
|
||||
function internalbasefunction() internal { |
||||
|
||||
} |
||||
|
||||
function publicbasefunction() public { |
||||
|
||||
} |
||||
function externalbasefunction() external { |
||||
} |
||||
}`}
|
||||
|
||||
const import1Contract = { |
||||
name: 'contracts/import1.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "contracts/importbase.sol"; |
||||
import "contracts/secondimport.sol"; |
||||
|
||||
contract importcontract is importbase { |
||||
struct ImportedBook {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
ImportedBook public importedbook; |
||||
|
||||
string private importprivatestring; |
||||
string internal internalimportstring; |
||||
string public importpublicstring; |
||||
|
||||
function privateimport() private { |
||||
|
||||
} |
||||
|
||||
function internalimport() internal { |
||||
|
||||
} |
||||
|
||||
function publicimport() public { |
||||
|
||||
} |
||||
|
||||
function externalimport() external { |
||||
} |
||||
}`}
|
||||
|
||||
const importbase = { |
||||
name: 'contracts/importbase.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
contract importbase { |
||||
string public importbasestring; |
||||
} |
||||
`}
|
||||
|
||||
const secondimport = { |
||||
name: 'contracts/secondimport.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
contract secondimport { |
||||
string public secondimportstring; |
||||
} |
||||
`}
|
||||
|
||||
export default { |
||||
testContract, |
||||
baseContract, |
||||
baseOfBaseContract, |
||||
import1Contract, |
||||
importbase, |
||||
secondimport |
||||
} |
@ -0,0 +1,526 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
import examples from '../examples/editor-test-contracts' |
||||
|
||||
const autoCompleteLineElement = (name: string) => { |
||||
return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` |
||||
} |
||||
|
||||
module.exports = { |
||||
'@disabled': true, |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', false) |
||||
}, |
||||
|
||||
'Should enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
|
||||
'Should add test and base files #group1': function (browser: NightwatchBrowser) { |
||||
browser.addFile(examples.testContract.name, examples.testContract) |
||||
.addFile(examples.baseContract.name, examples.baseContract) |
||||
.addFile(examples.import1Contract.name, examples.import1Contract) |
||||
.addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract) |
||||
.addFile(examples.secondimport.name, examples.secondimport) |
||||
.addFile(examples.importbase.name, examples.importbase) |
||||
.openFile(examples.testContract.name) |
||||
}, |
||||
'Should put cursor in the () of the function #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(36) |
||||
const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]" |
||||
browser.waitForElementVisible('#editorView') |
||||
.useXpath() |
||||
.click(path).pause(1000) |
||||
}, |
||||
'Should complete variable declaration types in a function definition #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('uint25') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('uint256')) |
||||
.click(autoCompleteLineElement('uint256')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' abc') |
||||
.sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow
|
||||
.sendKeys(', testb') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"')) |
||||
.click(autoCompleteLineElement('"TestBookDefinition"')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' memo') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('memory')) |
||||
.click(autoCompleteLineElement('memory')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' btextbook') |
||||
.sendKeys(this.Keys.ENTER) |
||||
.sendKeys(', BaseB') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('"BaseBook"')) |
||||
.click(autoCompleteLineElement('"BaseBook"')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' stor') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('storage')) |
||||
.click(autoCompleteLineElement('storage')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' localbbook') |
||||
}).pause(3000) |
||||
}, |
||||
'Should put cursor at the end of function #group1': function (browser: NightwatchBrowser) { |
||||
|
||||
const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]" |
||||
browser |
||||
.useXpath() |
||||
.click(path).pause(1000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
// right arrow key
|
||||
sendKeys(this.Keys.ARROW_RIGHT). |
||||
sendKeys(this.Keys.ARROW_RIGHT) |
||||
}) |
||||
}, |
||||
'Should autcomplete address types #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('addre') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('address')) |
||||
.click(autoCompleteLineElement('address')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' someaddress;') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}).pause(2000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('someaddress.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('balance')) |
||||
.waitForElementVisible(autoCompleteLineElement('send')) |
||||
.waitForElementVisible(autoCompleteLineElement('transfer')) |
||||
.waitForElementVisible(autoCompleteLineElement('code')) |
||||
.click(autoCompleteLineElement('balance')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should autcomplete array types #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('uin') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('uint')) |
||||
.click(autoCompleteLineElement('uint')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('[] mem') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('memory')) |
||||
.click(autoCompleteLineElement('memory')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' somearray;') |
||||
} |
||||
).pause(2000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
.sendKeys('somearray.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('push')) |
||||
.waitForElementVisible(autoCompleteLineElement('pop')) |
||||
.waitForElementVisible(autoCompleteLineElement('length')) |
||||
.click(autoCompleteLineElement('length')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should see and autocomplete second import because it was imported by the first import #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('secondi') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('secondimport')) |
||||
.click(autoCompleteLineElement('secondimport')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' sec;') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}).pause(3000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('sec.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('secondimportstring')) |
||||
.click(autoCompleteLineElement('secondimportstring')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
|
||||
}, |
||||
'Should see and autocomplete imported local class #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('import') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('importedcontract')) |
||||
.click(autoCompleteLineElement('importedcontract')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('externalimport')) |
||||
.waitForElementVisible(autoCompleteLineElement('importbasestring')) |
||||
.waitForElementVisible(autoCompleteLineElement('importedbook')) |
||||
.waitForElementVisible(autoCompleteLineElement('importpublicstring')) |
||||
.waitForElementVisible(autoCompleteLineElement('publicimport')) |
||||
// no private
|
||||
.waitForElementNotPresent(autoCompleteLineElement('importprivatestring')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('privateimport')) |
||||
// no internal
|
||||
.waitForElementNotPresent(autoCompleteLineElement('importinternalstring')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('internalimport')) |
||||
.click(autoCompleteLineElement('importbasestring')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
|
||||
}, |
||||
'Should autocomplete derived and local event when not using this. #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('emit base') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('BaseEvent')) |
||||
.click(autoCompleteLineElement('BaseEvent')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys('msg.sender') |
||||
.sendKeys(this.Keys.TAB) |
||||
.sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium
|
||||
.sendKeys('3232') |
||||
.sendKeys(this.Keys.TAB) |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('emit MyEv') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('MyEvent')) |
||||
.click(autoCompleteLineElement('MyEvent')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys('3232') |
||||
.sendKeys(this.Keys.TAB) |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
|
||||
'Should type and get msg options #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('msg.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('sender')) |
||||
.waitForElementVisible(autoCompleteLineElement('data')) |
||||
.waitForElementVisible(autoCompleteLineElement('value')) |
||||
.waitForElementVisible(autoCompleteLineElement('gas')) |
||||
.waitForElementVisible(autoCompleteLineElement('sig')) |
||||
.click(autoCompleteLineElement('sender')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('balance')) |
||||
.waitForElementVisible(autoCompleteLineElement('code')) |
||||
.waitForElementVisible(autoCompleteLineElement('codehash')) |
||||
.waitForElementVisible(autoCompleteLineElement('send')) |
||||
.waitForElementVisible(autoCompleteLineElement('transfer')) |
||||
.click(autoCompleteLineElement('balance')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should bo and get book #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('bo') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('book')) |
||||
.click(autoCompleteLineElement('book')) |
||||
}, |
||||
'Should autcomplete derived struct #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should bo and get basebook #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('base') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('basebook')) |
||||
.click(autoCompleteLineElement('basebook')) |
||||
}, |
||||
'Should autcomplete derived struct from base class #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should block scoped localbbook #group1': function (browser: NightwatchBrowser) { |
||||
browser.pause(4000). |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('localb') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('localbbook')) |
||||
.click(autoCompleteLineElement('localbbook')) |
||||
}, |
||||
'Should autcomplete derived struct from block localbbook #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should block scoped btextbook #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('btext') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('btextbook')) |
||||
.click(autoCompleteLineElement('btextbook')) |
||||
}, |
||||
'Should autcomplete derived struct from block btextbook #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should find private and internal local functions #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('my') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('myprivatefunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('myinternalfunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('memory')) |
||||
.click(autoCompleteLineElement('myinternalfunction')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should find internal functions and var from base and owner #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('intern') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('internalbasefunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('internalstring')) |
||||
.waitForElementVisible(autoCompleteLineElement('internalbasestring')) |
||||
// keyword internal
|
||||
.waitForElementVisible(autoCompleteLineElement('internal keyword')) |
||||
.click(autoCompleteLineElement('internalbasefunction')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
|
||||
'Should not find external functions without this. #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('extern') |
||||
}) |
||||
.waitForElementNotPresent(autoCompleteLineElement('externalbasefunction')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('myexternalfunction')) |
||||
// keyword internal
|
||||
.waitForElementVisible(autoCompleteLineElement('external keyword')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
}) |
||||
}, |
||||
'Should find external functions using this. #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('this.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('externalbasefunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('myexternalfunction')) |
||||
}, |
||||
'Should find public functions and vars using this. but not private & other types of nodes #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible(autoCompleteLineElement('"publicbasefunction"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"publicstring"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"basebook"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"mybook"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"testing"')) |
||||
// but no private functions or vars or other types of nodes
|
||||
.waitForElementNotPresent(autoCompleteLineElement('"private"')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"')) |
||||
.click(autoCompleteLineElement('"publicbasefunction"')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should autocomplete local and derived ENUMS #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('BaseEnum.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('SMALL')) |
||||
.waitForElementVisible(autoCompleteLineElement('MEDIUM')) |
||||
.waitForElementVisible(autoCompleteLineElement('LARGE')) |
||||
.click(autoCompleteLineElement('SMALL')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
.sendKeys('MyEnum.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('SMALL')) |
||||
.waitForElementVisible(autoCompleteLineElement('MEDIUM')) |
||||
.waitForElementVisible(autoCompleteLineElement('LARGE')) |
||||
.click(autoCompleteLineElement('SMALL')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,244 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
const checkEditorHoverContent = (browser: NightwatchBrowser, path: string, expectedContent: string, offsetLeft: number = 0) => { |
||||
browser.useXpath() |
||||
.useXpath() |
||||
.moveToElement('//body', 0, 0) // always move away from the hover before the next test in case we hover in the same line on a different element
|
||||
.waitForElementVisible(path) |
||||
.moveToElement(path, offsetLeft, 0) |
||||
.useCss() |
||||
.waitForElementContainsText('.monaco-hover-content', expectedContent).pause(1000) |
||||
} |
||||
|
||||
module.exports = { |
||||
'@disabled': true, |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', false) |
||||
}, |
||||
|
||||
'Should enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
|
||||
'Should load the test file': function (browser: NightwatchBrowser) { |
||||
browser.openFile('contracts') |
||||
.openFile('contracts/3_Ballot.sol') |
||||
.waitForElementVisible('#editorView') |
||||
.setEditorValue(BallotWithARefToOwner) |
||||
.pause(4000) // wait for the compiler to finish
|
||||
.scrollToLine(37) |
||||
}, |
||||
'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path = "//*[contains(text(),'BallotHoverTest')]" |
||||
checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest') |
||||
checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0') |
||||
checkEditorHoverContent(browser, path, '@title Ballot') |
||||
}, |
||||
'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path = "//*[@class='view-line' and contains(.,'chairperson') and contains(.,'address') and contains(.,'public')]//span//span[contains(.,'chairperson')]" |
||||
const expectedContent = 'address public chairperson' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over constructor in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path: string = "//*[@class='view-line' and contains(.,'constructor') and contains(.,'bytes32') and contains(.,'memory')]//span//span[contains(.,'constructor')]" |
||||
const expectedContent = 'Estimated creation cost: infinite gas Estimated code deposit cost:' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over function in editor #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(58) |
||||
const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]" |
||||
let expectedContent = 'Estimated execution cost' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'" |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over var components in editor #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(37) |
||||
let path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'voters')]" |
||||
let expectedContent = 'mapping(address => struct BallotHoverTest.Voter) public voters' |
||||
checkEditorHoverContent(browser, path, expectedContent, 15) |
||||
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'chairperson')]" |
||||
expectedContent = 'address public chairperson' |
||||
checkEditorHoverContent(browser, path, expectedContent, 3) |
||||
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'weight')]" |
||||
expectedContent = 'uint256 internal weight' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over new contract creation in editor #group1': function (browser: NightwatchBrowser) { |
||||
let path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'cowner')]" |
||||
let expectedContent = 'contract Owner internal cowner' |
||||
checkEditorHoverContent(browser, path, expectedContent, 10) |
||||
path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'Owner')]" |
||||
expectedContent = 'contract Owner is Owner' |
||||
checkEditorHoverContent(browser, path, expectedContent, 10) |
||||
}, |
||||
'Should show hover over external class member in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" |
||||
let expectedContent = 'function getOwner () external view returns (address internal )' |
||||
checkEditorHoverContent(browser, path, expectedContent, 0) |
||||
expectedContent = 'contracts/2_Owner.sol' |
||||
checkEditorHoverContent(browser, path, expectedContent, 0) |
||||
expectedContent = '@dev Return owner address' |
||||
checkEditorHoverContent(browser, path, expectedContent, 0) |
||||
}, |
||||
'Should show hover over struct definition in editor #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(5) |
||||
const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]" |
||||
const expectedContent = 'StructDefinition' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
} |
||||
} |
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "./2_Owner.sol"; |
||||
|
||||
/** |
||||
* @title Ballot |
||||
* @dev Implements voting process along with vote delegation |
||||
*/ |
||||
contract BallotHoverTest { |
||||
Owner cowner; |
||||
struct Voter { |
||||
uint weight; // weight is accumulated by delegation
|
||||
bool voted; // if true, that person already voted
|
||||
address delegate; // person delegated to
|
||||
uint vote; // index of the voted proposal
|
||||
} |
||||
|
||||
struct Proposal { |
||||
// If you can limit the length to a certain number of bytes,
|
||||
// always use one of bytes1 to bytes32 because they are much cheaper
|
||||
bytes32 name; // short name (up to 32 bytes)
|
||||
uint voteCount; // number of accumulated votes
|
||||
} |
||||
|
||||
address public chairperson; |
||||
|
||||
mapping(address => Voter) public voters; |
||||
|
||||
Proposal[] public proposals; |
||||
|
||||
/** |
||||
* @dev Create a new ballot to choose one of 'proposalNames'. |
||||
* @param proposalNames names of proposals |
||||
*/ |
||||
constructor(bytes32[] memory proposalNames) { |
||||
cowner = new Owner(); |
||||
cowner.getOwner(); |
||||
chairperson = msg.sender; |
||||
voters[chairperson].weight = 1; |
||||
|
||||
for (uint i = 0; i < proposalNames.length; i++) { |
||||
// 'Proposal({...})' creates a temporary
|
||||
// Proposal object and 'proposals.push(...)'
|
||||
// appends it to the end of 'proposals'.
|
||||
proposals.push(Proposal({ |
||||
name: proposalNames[i], |
||||
voteCount: 0 |
||||
})); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. |
||||
* @param voter address of voter |
||||
*/ |
||||
function giveRightToVote(address voter) public { |
||||
require( |
||||
msg.sender == chairperson, |
||||
"Only chairperson can give right to vote." |
||||
); |
||||
require( |
||||
!voters[voter].voted, |
||||
"The voter already voted." |
||||
); |
||||
require(voters[voter].weight == 0); |
||||
voters[voter].weight = 1; |
||||
} |
||||
|
||||
/** |
||||
* @dev Delegate your vote to the voter 'to'. |
||||
* @param to address to which vote is delegated |
||||
*/ |
||||
function delegate(address to) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(!sender.voted, "You already voted."); |
||||
require(to != msg.sender, "Self-delegation is disallowed."); |
||||
|
||||
while (voters[to].delegate != address(0)) { |
||||
to = voters[to].delegate; |
||||
|
||||
// We found a loop in the delegation, not allowed.
|
||||
require(to != msg.sender, "Found loop in delegation."); |
||||
} |
||||
sender.voted = true; |
||||
sender.delegate = to; |
||||
Voter storage delegate_ = voters[to]; |
||||
if (delegate_.voted) { |
||||
// If the delegate already voted,
|
||||
// directly add to the number of votes
|
||||
proposals[delegate_.vote].voteCount += sender.weight; |
||||
} else { |
||||
// If the delegate did not vote yet,
|
||||
// add to her weight.
|
||||
delegate_.weight += sender.weight; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. |
||||
* @param proposal index of proposal in the proposals array |
||||
*/ |
||||
function vote(uint proposal) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(sender.weight != 0, "Has no right to vote"); |
||||
require(!sender.voted, "Already voted."); |
||||
sender.voted = true; |
||||
sender.vote = proposal; |
||||
|
||||
// If 'proposal' is out of the range of the array,
|
||||
// this will throw automatically and revert all
|
||||
// changes.
|
||||
proposals[proposal].voteCount += sender.weight; |
||||
} |
||||
|
||||
/** |
||||
* @dev Computes the winning proposal taking all previous votes into account. |
||||
* @return winningProposal_ index of winning proposal in the proposals array |
||||
*/ |
||||
function winningProposal() public view |
||||
returns (uint winningProposal_) |
||||
{ |
||||
uint winningVoteCount = 0; |
||||
for (uint p = 0; p < proposals.length; p++) { |
||||
if (proposals[p].voteCount > winningVoteCount) { |
||||
winningVoteCount = proposals[p].voteCount; |
||||
winningProposal_ = p; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then |
||||
* @return winnerName_ the name of the winner |
||||
*/ |
||||
function winnerName() public view |
||||
returns (bytes32 winnerName_) |
||||
{ |
||||
winnerName_ = proposals[winningProposal()].name; |
||||
} |
||||
} |
||||
` |
@ -0,0 +1,203 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
const openReferences = (browser: NightwatchBrowser, path: string) => { |
||||
(browser as any).useXpath() |
||||
.useXpath() |
||||
.waitForElementVisible(path) |
||||
.click(path) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
keyDown(this.Keys.SHIFT). |
||||
sendKeys(this.Keys.F12) |
||||
}) |
||||
} |
||||
|
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', false) |
||||
}, |
||||
|
||||
'Should enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
'Should load the test file': function (browser: NightwatchBrowser) { |
||||
browser.openFile('contracts') |
||||
.openFile('contracts/3_Ballot.sol') |
||||
.waitForElementVisible('#editorView') |
||||
.setEditorValue(BallotWithARefToOwner) |
||||
.pause(10000) // wait for the compiler to finish
|
||||
.scrollToLine(37) |
||||
}, |
||||
'Should show local references': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(48) |
||||
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]" |
||||
openReferences(browser, path) |
||||
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]") |
||||
.keys(browser.Keys.ESCAPE) |
||||
}, |
||||
'Should show references of getOwner': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(39) |
||||
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" |
||||
openReferences(browser, path) |
||||
browser.useXpath() |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]") |
||||
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]") |
||||
.keys(browser.Keys.ESCAPE) |
||||
} |
||||
} |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "./2_Owner.sol"; |
||||
|
||||
/** |
||||
* @title Ballot |
||||
* @dev Implements voting process along with vote delegation |
||||
*/ |
||||
contract BallotHoverTest { |
||||
Owner cowner; |
||||
struct Voter { |
||||
uint weight; // weight is accumulated by delegation
|
||||
bool voted; // if true, that person already voted
|
||||
address delegate; // person delegated to
|
||||
uint vote; // index of the voted proposal
|
||||
} |
||||
|
||||
struct Proposal { |
||||
// If you can limit the length to a certain number of bytes,
|
||||
// always use one of bytes1 to bytes32 because they are much cheaper
|
||||
bytes32 name; // short name (up to 32 bytes)
|
||||
uint voteCount; // number of accumulated votes
|
||||
} |
||||
|
||||
address public chairperson; |
||||
|
||||
mapping(address => Voter) public voters; |
||||
|
||||
Proposal[] public proposals; |
||||
|
||||
/** |
||||
* @dev Create a new ballot to choose one of 'proposalNames'. |
||||
* @param proposalNames names of proposals |
||||
*/ |
||||
constructor(bytes32[] memory proposalNames) { |
||||
cowner = new Owner(); |
||||
cowner.getOwner(); |
||||
chairperson = msg.sender; |
||||
voters[chairperson].weight = 1; |
||||
|
||||
for (uint i = 0; i < proposalNames.length; i++) { |
||||
// 'Proposal({...})' creates a temporary
|
||||
// Proposal object and 'proposals.push(...)'
|
||||
// appends it to the end of 'proposals'.
|
||||
proposals.push(Proposal({ |
||||
name: proposalNames[i], |
||||
voteCount: 0 |
||||
})); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. |
||||
* @param voter address of voter |
||||
*/ |
||||
function giveRightToVote(address voter) public { |
||||
require( |
||||
msg.sender == chairperson, |
||||
"Only chairperson can give right to vote." |
||||
); |
||||
require( |
||||
!voters[voter].voted, |
||||
"The voter already voted." |
||||
); |
||||
require(voters[voter].weight == 0); |
||||
voters[voter].weight = 1; |
||||
} |
||||
|
||||
/** |
||||
* @dev Delegate your vote to the voter 'to'. |
||||
* @param to address to which vote is delegated |
||||
*/ |
||||
function delegate(address to) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(!sender.voted, "You already voted."); |
||||
require(to != msg.sender, "Self-delegation is disallowed."); |
||||
|
||||
while (voters[to].delegate != address(0)) { |
||||
to = voters[to].delegate; |
||||
|
||||
// We found a loop in the delegation, not allowed.
|
||||
require(to != msg.sender, "Found loop in delegation."); |
||||
} |
||||
sender.voted = true; |
||||
sender.delegate = to; |
||||
Voter storage delegate_ = voters[to]; |
||||
if (delegate_.voted) { |
||||
// If the delegate already voted,
|
||||
// directly add to the number of votes
|
||||
proposals[delegate_.vote].voteCount += sender.weight; |
||||
} else { |
||||
// If the delegate did not vote yet,
|
||||
// add to her weight.
|
||||
delegate_.weight += sender.weight; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. |
||||
* @param proposal index of proposal in the proposals array |
||||
*/ |
||||
function vote(uint proposal) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(sender.weight != 0, "Has no right to vote"); |
||||
require(!sender.voted, "Already voted."); |
||||
sender.voted = true; |
||||
sender.vote = proposal; |
||||
|
||||
// If 'proposal' is out of the range of the array,
|
||||
// this will throw automatically and revert all
|
||||
// changes.
|
||||
proposals[proposal].voteCount += sender.weight; |
||||
} |
||||
|
||||
/** |
||||
* @dev Computes the winning proposal taking all previous votes into account. |
||||
* @return winningProposal_ index of winning proposal in the proposals array |
||||
*/ |
||||
function winningProposal() public view |
||||
returns (uint winningProposal_) |
||||
{ |
||||
uint winningVoteCount = 0; |
||||
for (uint p = 0; p < proposals.length; p++) { |
||||
if (proposals[p].voteCount > winningVoteCount) { |
||||
winningVoteCount = proposals[p].voteCount; |
||||
winningProposal_ = p; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then |
||||
* @return winnerName_ the name of the winner |
||||
*/ |
||||
function winnerName() public view |
||||
returns (bytes32 winnerName_) |
||||
{ |
||||
winnerName_ = proposals[winningProposal()].name; |
||||
} |
||||
} |
||||
` |
@ -0,0 +1,91 @@ |
||||
'use strict' |
||||
|
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
module.exports = { |
||||
|
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', true) |
||||
}, |
||||
'Should enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
'Should add error marker': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.openFile('contracts') |
||||
.openFile('contracts/1_Storage.sol') |
||||
.addFile('scripts/adderror.ts', {content: addErrorMarker}) |
||||
.pause(4000) |
||||
.executeScriptInTerminal('remix.exeCurrent()') |
||||
.pause(4000) |
||||
.openFile('contracts/1_Storage.sol') |
||||
.useXpath() |
||||
.waitForElementVisible("//*[@class='cdr squiggly-error']") |
||||
.waitForElementVisible("//*[@class='cdr squiggly-warning']") |
||||
}, |
||||
'Should clear error marker': function (browser: NightwatchBrowser) { |
||||
browser
|
||||
.useCss() |
||||
.addFile('scripts/clear.ts', {content: clearMarkers}) |
||||
.pause(4000) |
||||
.executeScriptInTerminal('remix.exeCurrent()') |
||||
.pause(4000) |
||||
.openFile('contracts/1_Storage.sol') |
||||
.useXpath() |
||||
.waitForElementNotPresent("//*[@class='cdr squiggly-error']") |
||||
.waitForElementNotPresent("//*[@class='cdr squiggly-warning']") |
||||
} |
||||
} |
||||
|
||||
const clearMarkers =` |
||||
(async () => { |
||||
await remix.call('editor', 'clearErrorMarkers' as any, ['contracts/1_Storage.sol']) |
||||
})()` |
||||
|
||||
const addErrorMarker = ` |
||||
(async () => { |
||||
|
||||
|
||||
let errors = [ |
||||
{ |
||||
position: { |
||||
start: { |
||||
line: 10, |
||||
column: 1, |
||||
}, |
||||
end: { |
||||
line: 10, |
||||
column: 10 |
||||
} |
||||
}, |
||||
message: 'testing', |
||||
severity: 'error', |
||||
file: 'contracts/1_Storage.sol' |
||||
}, |
||||
{ |
||||
position: { |
||||
start: { |
||||
line: 18, |
||||
column: 1, |
||||
}, |
||||
end: { |
||||
line: 18, |
||||
column: 10 |
||||
} |
||||
}, |
||||
message: 'testing2', |
||||
severity: 'warning', |
||||
file: 'contracts/1_Storage.sol' |
||||
}, |
||||
] |
||||
|
||||
|
||||
await remix.call('editor', 'addErrorMarker' as any, errors) |
||||
|
||||
|
||||
})()` |
@ -0,0 +1,45 @@ |
||||
'use strict' |
||||
|
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
const sources = [] |
||||
|
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done) |
||||
}, |
||||
'@sources': function () { |
||||
return sources |
||||
}, |
||||
'Deploy SampleERC721 whose bytecode is very similar to ERC721': function (browser: NightwatchBrowser) { |
||||
browser.clickLaunchIcon('filePanel') |
||||
.click('*[data-id="workspaceCreate"]') |
||||
// create contract
|
||||
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') |
||||
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') |
||||
// eslint-disable-next-line dot-notation
|
||||
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' }) |
||||
.click('select[id="wstemplate"]') |
||||
.click('select[id="wstemplate"] option[value=ozerc721]') |
||||
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') |
||||
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) |
||||
.pause(100) |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/SampleERC721.sol"]') |
||||
.openFile('contracts/SampleERC721.sol') |
||||
.verifyContracts(['SampleERC721']) |
||||
// deploy contract
|
||||
.clickLaunchIcon('udapp') |
||||
.selectContract('SampleERC721') |
||||
.createContract('E,E') |
||||
.testFunction('last', |
||||
{ |
||||
status: 'true Transaction mined and execution succeed', |
||||
'decoded input': { |
||||
'string tokenName': 'E', |
||||
'string tokenSymbol': 'E' |
||||
} |
||||
}).end() |
||||
} |
||||
} |
@ -0,0 +1,653 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||
import { CompilationResult } from '@remix-project/remix-solidity' |
||||
import CodeParserGasService from './services/code-parser-gas-service' |
||||
import CodeParserCompiler from './services/code-parser-compiler' |
||||
import CodeParserAntlrService from './services/code-parser-antlr-service' |
||||
import React from 'react' |
||||
import { Profile } from '@remixproject/plugin-utils' |
||||
import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from 'dist/libs/remix-analyzer/src/types' |
||||
import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api' |
||||
import { antlr } from './types' |
||||
import { ParseResult } from './types/antlr-types' |
||||
|
||||
const profile: Profile = { |
||||
name: 'codeParser', |
||||
methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates'], |
||||
events: [], |
||||
version: '0.0.1' |
||||
} |
||||
|
||||
export function isNodeDefinition(node: genericASTNode) { |
||||
return node.nodeType === 'ContractDefinition' || |
||||
node.nodeType === 'FunctionDefinition' || |
||||
node.nodeType === 'ModifierDefinition' || |
||||
node.nodeType === 'VariableDeclaration' || |
||||
node.nodeType === 'StructDefinition' || |
||||
node.nodeType === 'EventDefinition' |
||||
} |
||||
|
||||
export type genericASTNode = |
||||
ContractDefinitionAstNode |
||||
| FunctionDefinitionAstNode |
||||
| ModifierDefinitionAstNode |
||||
| VariableDeclarationAstNode |
||||
| StructDefinitionAstNode |
||||
| EventDefinitionAstNode |
||||
| IdentifierAstNode |
||||
| FunctionCallAstNode |
||||
| ImportDirectiveAstNode |
||||
| SourceUnitAstNode |
||||
|
||||
interface flatReferenceIndexNode { |
||||
[id: number]: genericASTNode |
||||
} |
||||
|
||||
interface declarationIndexNode { |
||||
[id: number]: genericASTNode[] |
||||
} |
||||
|
||||
interface codeParserIndex { |
||||
declarations: declarationIndexNode, |
||||
flatReferences: flatReferenceIndexNode, |
||||
nodesPerFile: any |
||||
} |
||||
|
||||
export class CodeParser extends Plugin { |
||||
|
||||
antlrParserResult: antlr.ParseResult // contains the simple parsed AST for the current file
|
||||
|
||||
compilerAbstract: CompilerAbstract |
||||
currentFile: string |
||||
nodeIndex: codeParserIndex |
||||
astWalker: any |
||||
errorState: boolean = false |
||||
|
||||
gastEstimateTimeOut: any |
||||
|
||||
gasService: CodeParserGasService |
||||
compilerService: CodeParserCompiler |
||||
antlrService: CodeParserAntlrService |
||||
|
||||
parseSolidity: (text: string) => Promise<antlr.ParseResult> |
||||
getLastNodeInLine: (ast: string) => Promise<any> |
||||
listAstNodes: () => Promise<any> |
||||
getANTLRBlockAtPosition: (position: any, text?: string) => Promise<any> |
||||
getCurrentFileAST: (text?: string) => Promise<ParseResult> |
||||
|
||||
constructor(astWalker: any) { |
||||
super(profile) |
||||
this.astWalker = astWalker |
||||
this.nodeIndex = { |
||||
declarations: [[]], |
||||
flatReferences: [], |
||||
nodesPerFile: {} |
||||
} |
||||
} |
||||
|
||||
async handleChangeEvents() { |
||||
const completionSettings = await this.call('config', 'getAppParameter', 'auto-completion') |
||||
if (completionSettings) { |
||||
await this.antlrService.getCurrentFileAST() |
||||
} |
||||
const showGasSettings = await this.call('config', 'getAppParameter', 'show-gas') |
||||
const showErrorSettings = await this.call('config', 'getAppParameter', 'display-errors') |
||||
if(showGasSettings || showErrorSettings || completionSettings) { |
||||
await this.compilerService.compile() |
||||
} |
||||
} |
||||
|
||||
async onActivation() { |
||||
|
||||
this.gasService = new CodeParserGasService(this) |
||||
this.compilerService = new CodeParserCompiler(this) |
||||
this.antlrService = new CodeParserAntlrService(this) |
||||
|
||||
this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService) |
||||
this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(this.antlrService) |
||||
this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService) |
||||
this.getANTLRBlockAtPosition = this.antlrService.getANTLRBlockAtPosition.bind(this.antlrService) |
||||
this.getCurrentFileAST = this.antlrService.getCurrentFileAST.bind(this.antlrService) |
||||
|
||||
|
||||
this.on('editor', 'didChangeFile', async (file) => { |
||||
await this.call('editor', 'discardLineTexts') |
||||
await this.handleChangeEvents() |
||||
}) |
||||
|
||||
this.on('filePanel', 'setWorkspace', async () => { |
||||
await this.call('fileDecorator', 'clearFileDecorators') |
||||
}) |
||||
|
||||
|
||||
this.on('fileManager', 'currentFileChanged', async () => { |
||||
await this.call('editor', 'discardLineTexts') |
||||
await this.handleChangeEvents() |
||||
}) |
||||
|
||||
this.on('solidity', 'loadingCompiler', async (url) => { |
||||
this.compilerService.compiler.loadVersion(true, `${url}?t=${Date.now()}`) |
||||
}) |
||||
|
||||
await this.compilerService.init() |
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
*
|
||||
* @returns
|
||||
*/ |
||||
async getLastCompilationResult() { |
||||
return this.compilerAbstract |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
getSubNodes<T extends genericASTNode>(node: T): number[] { |
||||
return node.nodeType == "ContractDefinition" && node.contractDependencies; |
||||
} |
||||
|
||||
/** |
||||
* Builds a flat index and declarations of all the nodes in the compilation result |
||||
* @param compilationResult
|
||||
* @param source
|
||||
*/ |
||||
_buildIndex(compilationResult: CompilationResult, source) { |
||||
if (compilationResult && compilationResult.sources) { |
||||
const callback = (node: genericASTNode) => { |
||||
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { |
||||
if (!this.nodeIndex.declarations[node.referencedDeclaration]) { |
||||
this.nodeIndex.declarations[node.referencedDeclaration] = [] |
||||
} |
||||
this.nodeIndex.declarations[node.referencedDeclaration].push(node) |
||||
} |
||||
this.nodeIndex.flatReferences[node.id] = node |
||||
} |
||||
for (const s in compilationResult.sources) { |
||||
this.astWalker.walkFull(compilationResult.sources[s].ast, callback) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
// NODE HELPERS
|
||||
|
||||
_getInputParams(node: FunctionDefinitionAstNode) { |
||||
const params = [] |
||||
const target = node.parameters |
||||
if (target) { |
||||
const children = target.parameters |
||||
for (const j in children) { |
||||
if (children[j].nodeType === 'VariableDeclaration') { |
||||
params.push(children[j].typeDescriptions.typeString) |
||||
} |
||||
} |
||||
} |
||||
return '(' + params.toString() + ')' |
||||
} |
||||
|
||||
|
||||
_flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) { |
||||
const index = {} |
||||
const contractName: string = contractNode.name |
||||
const callback = (node) => { |
||||
if (inScope && node.scope !== contractNode.id |
||||
&& !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition')) |
||||
return |
||||
if (inScope) node.isClassNode = true; |
||||
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) |
||||
node.functionName = node.name + this._getInputParams(node) |
||||
node.contractName = contractName |
||||
node.contractId = contractNode.id |
||||
index[node.id] = node |
||||
} |
||||
this.astWalker.walkFull(contractNode, callback) |
||||
return index |
||||
} |
||||
|
||||
_extractFileNodes(fileName: string, compilationResult: lastCompilationResult) { |
||||
if (compilationResult && compilationResult.data.sources && compilationResult.data.sources[fileName]) { |
||||
const source = compilationResult.data.sources[fileName] |
||||
const nodesByContract: any = {} |
||||
nodesByContract.imports = {} |
||||
nodesByContract.contracts = {} |
||||
this.astWalker.walkFull(source.ast, async (node) => { |
||||
if (node.nodeType === 'ContractDefinition') { |
||||
const flatNodes = this._flatNodeList(node, fileName, false, compilationResult) |
||||
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult) |
||||
nodesByContract.contracts[node.name] = { contractDefinition: node, contractNodes: flatNodes } |
||||
const baseNodes = {} |
||||
const baseNodesWithBaseContractScope = {} |
||||
if (node.linearizedBaseContracts) { |
||||
for (const id of node.linearizedBaseContracts) { |
||||
if (id !== node.id) { |
||||
const baseContract = await this.getNodeById(id) |
||||
const callback = (node) => { |
||||
node.contractName = (baseContract as any).name |
||||
node.contractId = (baseContract as any).id |
||||
node.isBaseNode = true; |
||||
baseNodes[node.id] = node |
||||
if ((node.scope && node.scope === baseContract.id) |
||||
|| node.nodeType === 'EnumDefinition' |
||||
|| node.nodeType === 'EventDefinition' |
||||
) { |
||||
baseNodesWithBaseContractScope[node.id] = node |
||||
} |
||||
if (node.members) { |
||||
for (const member of node.members) { |
||||
member.contractName = (baseContract as any).name |
||||
member.contractId = (baseContract as any).id |
||||
member.isBaseNode = true; |
||||
} |
||||
} |
||||
} |
||||
this.astWalker.walkFull(baseContract, callback) |
||||
} |
||||
} |
||||
} |
||||
nodesByContract.contracts[node.name].baseNodes = baseNodes |
||||
nodesByContract.contracts[node.name].baseNodesWithBaseContractScope = baseNodesWithBaseContractScope |
||||
nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult) |
||||
} |
||||
if (node.nodeType === 'ImportDirective') { |
||||
|
||||
const imported = await this.resolveImports(node, {}) |
||||
|
||||
for (const importedNode of (Object.values(imported) as any)) { |
||||
if (importedNode.nodes) |
||||
for (const subNode of importedNode.nodes) { |
||||
nodesByContract.imports[subNode.id] = subNode |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
return nodesByContract |
||||
} |
||||
} |
||||
|
||||
_getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) { |
||||
|
||||
const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile] |
||||
for (const name in contracts) { |
||||
if (name === contractName) { |
||||
const contract = contracts[name] |
||||
const estimationObj = contract.evm && contract.evm.gasEstimates |
||||
|
||||
let executionCost = null |
||||
if (node.nodeType === 'FunctionDefinition') { |
||||
const visibility = node.visibility |
||||
if (node.kind !== 'constructor') { |
||||
const fnName = node.name |
||||
const fn = fnName + this._getInputParams(node) |
||||
|
||||
if (visibility === 'public' || visibility === 'external') { |
||||
executionCost = estimationObj === null ? '-' : estimationObj.external[fn] |
||||
} else if (visibility === 'private' || visibility === 'internal') { |
||||
executionCost = estimationObj === null ? '-' : estimationObj.internal[fn] |
||||
} |
||||
return { executionCost } |
||||
} else { |
||||
return { |
||||
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, |
||||
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Nodes at position where position is a number, offset |
||||
* @param position
|
||||
* @param type
|
||||
* @returns
|
||||
*/ |
||||
async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> { |
||||
const lastCompilationResult = this.compilerAbstract |
||||
if (!lastCompilationResult) return [] |
||||
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) |
||||
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { |
||||
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) |
||||
return nodes |
||||
} |
||||
return [] |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param id
|
||||
* @returns
|
||||
*/ |
||||
async getNodeById(id: number) { |
||||
for (const key in this.nodeIndex.flatReferences) { |
||||
if (this.nodeIndex.flatReferences[key].id === id) { |
||||
return this.nodeIndex.flatReferences[key] |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param id
|
||||
* @returns
|
||||
*/ |
||||
async getDeclaration(id: number) { |
||||
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) return this.nodeIndex.declarations[id] |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param scope
|
||||
* @returns
|
||||
*/ |
||||
async getNodesWithScope(scope: number) { |
||||
const nodes = [] |
||||
for (const node of Object.values(this.nodeIndex.flatReferences)) { |
||||
if (node.scope === scope) nodes.push(node) |
||||
} |
||||
return nodes |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param name
|
||||
* @returns
|
||||
*/ |
||||
async getNodesWithName(name: string) { |
||||
const nodes: genericASTNode[] = [] |
||||
for (const node of Object.values(this.nodeIndex.flatReferences)) { |
||||
if (node.name === name) nodes.push(node) |
||||
} |
||||
return nodes |
||||
} |
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
declarationOf<T extends genericASTNode>(node: T) { |
||||
if (node && ('referencedDeclaration' in node) && node.referencedDeclaration) { |
||||
return this.nodeIndex.flatReferences[node.referencedDeclaration] |
||||
} |
||||
return null |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param position
|
||||
* @returns
|
||||
*/ |
||||
async definitionAtPosition(position: number) { |
||||
const nodes = await this.nodesAtPosition(position) |
||||
let nodeDefinition: any |
||||
let node: genericASTNode |
||||
if (nodes && nodes.length && !this.errorState) { |
||||
node = nodes[nodes.length - 1] |
||||
nodeDefinition = node |
||||
if (!isNodeDefinition(node)) { |
||||
nodeDefinition = await this.declarationOf(node) || node |
||||
} |
||||
if (node.nodeType === 'ImportDirective') { |
||||
for (const key in this.nodeIndex.flatReferences) { |
||||
if (this.nodeIndex.flatReferences[key].id === node.sourceUnit) { |
||||
nodeDefinition = this.nodeIndex.flatReferences[key] |
||||
} |
||||
} |
||||
} |
||||
return nodeDefinition |
||||
} else { |
||||
const astNodes = await this.antlrService.listAstNodes() |
||||
for (const node of astNodes) { |
||||
if (node.range[0] <= position && node.range[1] >= position) { |
||||
if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) { |
||||
nodeDefinition = node |
||||
} |
||||
if (!nodeDefinition) nodeDefinition = node |
||||
} |
||||
} |
||||
if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') { |
||||
const nodeForIdentifier = await this.findIdentifier(nodeDefinition) |
||||
if (nodeForIdentifier) nodeDefinition = nodeForIdentifier |
||||
} |
||||
return nodeDefinition |
||||
} |
||||
|
||||
} |
||||
|
||||
async getContractNodes(contractName: string) { |
||||
if (this.nodeIndex.nodesPerFile |
||||
&& this.nodeIndex.nodesPerFile[this.currentFile] |
||||
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] |
||||
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes) { |
||||
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] |
||||
} |
||||
return false |
||||
} |
||||
|
||||
async getCurrentFileNodes() { |
||||
if (this.nodeIndex.nodesPerFile |
||||
&& this.nodeIndex.nodesPerFile[this.currentFile]) { |
||||
return this.nodeIndex.nodesPerFile[this.currentFile] |
||||
} |
||||
return false |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param identifierNode
|
||||
* @returns
|
||||
*/ |
||||
async findIdentifier(identifierNode: any) { |
||||
const astNodes = await this.antlrService.listAstNodes() |
||||
for (const node of astNodes) { |
||||
if (node.name === identifierNode.name && node.nodeType !== 'Identifier') { |
||||
return node |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
async positionOfDefinition(node: genericASTNode): Promise<any | null> { |
||||
if (node) { |
||||
if (node.src) { |
||||
const position = sourceMappingDecoder.decode(node.src) |
||||
if (position) { |
||||
return position |
||||
} |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @param imported
|
||||
* @returns
|
||||
*/ |
||||
async resolveImports(node: any, imported = {}) { |
||||
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { |
||||
const importNode: any = await this.getNodeById(node.sourceUnit) |
||||
imported[importNode.id] = importNode |
||||
if (importNode.nodes) { |
||||
for (const child of importNode.nodes) { |
||||
imported = await this.resolveImports(child, imported) |
||||
} |
||||
} |
||||
} |
||||
return imported |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
referencesOf(node: genericASTNode) { |
||||
const results: genericASTNode[] = [] |
||||
const highlights = (id: number) => { |
||||
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) { |
||||
const refs = this.nodeIndex.declarations[id] |
||||
for (const ref in refs) { |
||||
const node = refs[ref] |
||||
results.push(node) |
||||
} |
||||
} |
||||
} |
||||
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { |
||||
highlights(node.referencedDeclaration) |
||||
const current = this.nodeIndex.flatReferences[node.referencedDeclaration] |
||||
results.push(current) |
||||
} else { |
||||
highlights(node.id) |
||||
} |
||||
return results |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param position
|
||||
* @returns
|
||||
*/ |
||||
async referrencesAtPosition(position: any): Promise<genericASTNode[]> { |
||||
const nodes = await this.nodesAtPosition(position) |
||||
if (nodes && nodes.length) { |
||||
const node = nodes[nodes.length - 1] |
||||
if (node) { |
||||
return this.referencesOf(node) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @returns
|
||||
*/ |
||||
async getNodes(): Promise<flatReferenceIndexNode> { |
||||
return this.nodeIndex.flatReferences |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
async getNodeLink(node: genericASTNode) { |
||||
const lineColumn = await this.getLineColumnOfNode(node) |
||||
const position = await this.positionOfDefinition(node) |
||||
if (this.compilerAbstract && this.compilerAbstract.source) { |
||||
const fileName = this.compilerAbstract.getSourceName(position.file) |
||||
return lineColumn ? `${fileName} ${lineColumn.start.line}:${lineColumn.start.column}` : null |
||||
} |
||||
return '' |
||||
} |
||||
|
||||
/* |
||||
* @param node
|
||||
*/ |
||||
async getLineColumnOfNode(node: any) { |
||||
const position = await this.positionOfDefinition(node) |
||||
return this.getLineColumnOfPosition(position) |
||||
} |
||||
|
||||
/* |
||||
* @param position |
||||
*/ |
||||
async getLineColumnOfPosition(position: any) { |
||||
if (position) { |
||||
const fileName = this.compilerAbstract.getSourceName(position.file) |
||||
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.compilerAbstract.source.sources[fileName].content) |
||||
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks) |
||||
return lineColumn |
||||
} |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
async getNodeDocumentation(node: genericASTNode) { |
||||
if (("documentation" in node) && node.documentation && (node.documentation as any).text) { |
||||
let text = ''; |
||||
(node.documentation as any).text.split('\n').forEach(line => { |
||||
text += `${line.trim()}\n` |
||||
}) |
||||
return text |
||||
} |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
async getVariableDeclaration(node: any) { |
||||
const nodeVisibility = node.visibility && node.visibility.length ? node.visibility + ' ' : '' |
||||
const nodeName = node.name && node.name.length ? node.name : '' |
||||
if (node.typeDescriptions && node.typeDescriptions.typeString) { |
||||
return `${node.typeDescriptions.typeString} ${nodeVisibility}${nodeName}` |
||||
} else { |
||||
if (node.typeName && node.typeName.name) { |
||||
return `${node.typeName.name} ${nodeVisibility}${nodeName}` |
||||
} |
||||
else if (node.typeName && node.typeName.namePath) { |
||||
return `${node.typeName.namePath} ${nodeVisibility}${nodeName}` |
||||
} |
||||
else { |
||||
return `${nodeName}${nodeName}` |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
async getFunctionParamaters(node: any) { |
||||
const localParam = (node.parameters && node.parameters.parameters) || (node.parameters) |
||||
if (localParam) { |
||||
const params = [] |
||||
for (const param of localParam) { |
||||
params.push(await this.getVariableDeclaration(param)) |
||||
} |
||||
return `(${params.join(', ')})` |
||||
} |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param node
|
||||
* @returns
|
||||
*/ |
||||
async getFunctionReturnParameters(node: any) { |
||||
const localParam = (node.returnParameters && node.returnParameters.parameters) |
||||
if (localParam) { |
||||
const params = [] |
||||
for (const param of localParam) { |
||||
params.push(await this.getVariableDeclaration(param)) |
||||
} |
||||
return `(${params.join(', ')})` |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
} |
@ -0,0 +1,158 @@ |
||||
'use strict' |
||||
|
||||
import { AstNode } from "@remix-project/remix-solidity-ts" |
||||
import { CodeParser } from "../code-parser" |
||||
import { antlr } from '../types' |
||||
|
||||
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || [] |
||||
|
||||
export default class CodeParserAntlrService { |
||||
plugin: CodeParser |
||||
constructor(plugin: CodeParser) { |
||||
this.plugin = plugin |
||||
} |
||||
|
||||
/* |
||||
* simple parsing is used to quickly parse the current file or a text source without using the compiler or having to resolve imports |
||||
*/ |
||||
|
||||
async parseSolidity(text: string) { |
||||
const ast: antlr.ParseResult = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true }) |
||||
return 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 |
||||
* @param text
|
||||
* @returns
|
||||
*/ |
||||
async getCurrentFileAST(text: string | null = null) { |
||||
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') |
||||
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { |
||||
if (!this.plugin.currentFile) return |
||||
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) |
||||
try { |
||||
const ast = await this.parseSolidity(fileContent) |
||||
this.plugin.antlrParserResult = ast |
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
return this.plugin.antlrParserResult |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Lists the AST nodes from the current file parser |
||||
* These nodes need to be changed to match the node types returned by the compiler |
||||
* @returns
|
||||
*/ |
||||
async listAstNodes() { |
||||
await this.getCurrentFileAST(); |
||||
const nodes: AstNode[] = []; |
||||
(SolidityParser as any).visit(this.plugin.antlrParserResult, { |
||||
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => { |
||||
if (node.variables) { |
||||
for (const variable of node.variables) { |
||||
nodes.push({ ...variable, nodeType: 'VariableDeclaration', id: null, src: null }) |
||||
} |
||||
} |
||||
}, |
||||
VariableDeclaration: (node: antlr.VariableDeclaration) => { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
UserDefinedTypeName: (node: antlr.UserDefinedTypeName) => { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
FunctionDefinition: (node: antlr.FunctionDefinition) => { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
ContractDefinition: (node: antlr.ContractDefinition) => { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
MemberAccess: function (node: antlr.MemberAccess) { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
Identifier: function (node: antlr.Identifier) { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
EventDefinition: function (node: antlr.EventDefinition) { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
ModifierDefinition: function (node: antlr.ModifierDefinition) { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
InvalidNode: function (node: antlr.InvalidNode) { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
EnumDefinition: function (node: antlr.EnumDefinition) { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
}, |
||||
StructDefinition: function (node: antlr.StructDefinition) { |
||||
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) |
||||
} |
||||
|
||||
}) |
||||
return nodes |
||||
} |
||||
|
||||
|
||||
/** |
||||
*
|
||||
* @param ast
|
||||
* @returns
|
||||
*/ |
||||
async getLastNodeInLine(ast: string) { |
||||
let lastNode: any |
||||
const checkLastNode = (node: antlr.MemberAccess | antlr.Identifier) => { |
||||
if (lastNode && lastNode.range && lastNode.range[1]) { |
||||
if (node.range[1] > lastNode.range[1]) { |
||||
lastNode = node |
||||
} |
||||
} else { |
||||
lastNode = node |
||||
} |
||||
} |
||||
|
||||
(SolidityParser as any).visit(ast, { |
||||
MemberAccess: function (node: antlr.MemberAccess) { |
||||
checkLastNode(node) |
||||
}, |
||||
Identifier: function (node: antlr.Identifier) { |
||||
checkLastNode(node) |
||||
} |
||||
}) |
||||
if (lastNode && lastNode.expression) { |
||||
return lastNode.expression |
||||
} |
||||
return lastNode |
||||
} |
||||
|
||||
/** |
||||
* Returns the block surrounding the given position |
||||
* For example if the position is in the middle of a function, it will return the function |
||||
* @param {position} position |
||||
* @param {string} text // optional
|
||||
* @return {any} |
||||
* */ |
||||
async getANTLRBlockAtPosition(position: any, text: string = null) { |
||||
await this.getCurrentFileAST(text) |
||||
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] |
||||
const walkAst = (node: any) => { |
||||
if (node.loc.start.line <= position.lineNumber && node.loc.end.line >= position.lineNumber) { |
||||
const children = node.children || node.subNodes |
||||
if (children && allowedTypes.indexOf(node.type) !== -1) { |
||||
for (const child of children) { |
||||
const result = walkAst(child) |
||||
if (result) return result |
||||
} |
||||
} |
||||
return node |
||||
} |
||||
return null |
||||
} |
||||
if (!this.plugin.antlrParserResult) return |
||||
return walkAst(this.plugin.antlrParserResult) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,235 @@ |
||||
'use strict' |
||||
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||
import { Compiler } from '@remix-project/remix-solidity' |
||||
|
||||
import { CompilationResult, CompilationSource } from '@remix-project/remix-solidity' |
||||
import { CodeParser } from "../code-parser"; |
||||
import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' |
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
import { CompilerRetriggerMode } from '@remix-project/remix-solidity-ts'; |
||||
import { MarkerSeverity } from 'monaco-editor'; |
||||
|
||||
type errorMarker = { |
||||
message: string |
||||
severity: MarkerSeverity |
||||
position: { |
||||
start: { |
||||
line: number |
||||
column: number |
||||
}, |
||||
end: { |
||||
line: number |
||||
column: number |
||||
} |
||||
}, |
||||
file: string |
||||
} |
||||
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: CompilationSource, input: any, version: any) => Promise<void>; |
||||
errorState: boolean; |
||||
gastEstimateTimeOut: any |
||||
constructor( |
||||
plugin: CodeParser |
||||
) { |
||||
this.plugin = plugin |
||||
} |
||||
|
||||
init() { |
||||
|
||||
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, 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) { |
||||
const sources = result.getSourceCode().sources |
||||
for (const error of data.errors) { |
||||
|
||||
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 |
||||
|
||||
allErrors = [...allErrors, { |
||||
message: error.formattedMessage, |
||||
severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning, |
||||
position: { |
||||
start: { |
||||
line: ((lineColumn.start && lineColumn.start.line) || 0) + 1, |
||||
column: ((lineColumn.start && lineColumn.start.column) || 0) + 1 |
||||
}, |
||||
end: { |
||||
line: ((lineColumn.end && lineColumn.end.line) || 0) + 1, |
||||
column: ((lineColumn.end && lineColumn.end.column) || 0) + 1 |
||||
} |
||||
} |
||||
, file: filePath |
||||
}] |
||||
} |
||||
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: {}, |
||||
} |
||||
|
||||
|
||||
this.plugin._buildIndex(data, source) |
||||
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract) |
||||
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.compiler.event.register('compilationFinished', this.onAstFinished) |
||||
} |
||||
|
||||
// COMPILER
|
||||
|
||||
/** |
||||
*
|
||||
* @returns
|
||||
*/ |
||||
async compile() { |
||||
try { |
||||
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') |
||||
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { |
||||
const state = await this.plugin.call('solidity', 'getCompilerState') |
||||
this.compiler.set('optimize', state.optimize) |
||||
this.compiler.set('evmVersion', state.evmVersion) |
||||
this.compiler.set('language', state.language) |
||||
this.compiler.set('runs', state.runs) |
||||
this.compiler.set('useFileConfiguration', true) |
||||
this.compiler.set('compilerRetriggerMode', CompilerRetriggerMode.retrigger) |
||||
const configFileContent = { |
||||
"language": "Solidity", |
||||
"settings": { |
||||
"optimizer": { |
||||
"enabled": false, |
||||
"runs": 200 |
||||
}, |
||||
"outputSelection": { |
||||
"*": { |
||||
"": ["ast"], |
||||
"*": ["evm.gasEstimates"] |
||||
} |
||||
}, |
||||
"evmVersion": state.evmVersion && state.evmVersion.toString() || "byzantium", |
||||
} |
||||
} |
||||
|
||||
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) { |
||||
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors') |
||||
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) |
||||
} |
||||
|
||||
const errorPriority = { |
||||
'error': 0, |
||||
'warning': 1, |
||||
} |
||||
|
||||
// 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 decorator: fileDecoration = { |
||||
path: fileName, |
||||
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 decorator: fileDecoration = { |
||||
path: fileName, |
||||
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 clearDecorators(sources: any) { |
||||
const decorators: fileDecoration[] = [] |
||||
for (const fileName of Object.keys(sources)) { |
||||
const decorator: fileDecoration = { |
||||
path: fileName, |
||||
isDirectory: false, |
||||
fileStateType: fileDecorationType.None, |
||||
fileStateLabelClass: '', |
||||
fileStateIconClass: '', |
||||
fileStateIcon: '', |
||||
text: '', |
||||
owner: 'code-parser', |
||||
bubble: false |
||||
} |
||||
decorators.push(decorator) |
||||
} |
||||
|
||||
|
||||
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,76 @@ |
||||
import { CodeParser, genericASTNode } from "../code-parser"; |
||||
import { lineText } from '@remix-ui/editor' |
||||
|
||||
export default class CodeParserGasService { |
||||
plugin: CodeParser |
||||
|
||||
constructor(plugin: CodeParser) { |
||||
this.plugin = plugin |
||||
} |
||||
|
||||
async getGasEstimates(fileName: string) { |
||||
if (!fileName) { |
||||
fileName = await this.plugin.currentFile |
||||
} |
||||
if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) { |
||||
const estimates: any = [] |
||||
for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) { |
||||
if (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[]) { |
||||
if (node.gasEstimate) { |
||||
estimates.push({ |
||||
node, |
||||
range: await this.plugin.getLineColumnOfNode(node) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
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') |
||||
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract) |
||||
|
||||
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile) |
||||
|
||||
const friendlyNames = { |
||||
'executionCost': 'Estimated execution cost', |
||||
'codeDepositCost': 'Estimated code deposit 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(' ')}`, |
||||
}, |
||||
], |
||||
} |
||||
|
||||
this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName) |
||||
|
||||
|
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,732 @@ |
||||
// Base on the original type definitions for solidity-parser-antlr 0.2
|
||||
// by Leonid Logvinov <https://github.com/LogvinovLeon>
|
||||
// Alex Browne <https://github.com/albrow>
|
||||
// Xiao Liang <https://github.com/yxliang01>
|
||||
|
||||
export type ParseResult = SourceUnit & { |
||||
errors?: any[] |
||||
tokens?: Token[] |
||||
} |
||||
|
||||
interface Token { |
||||
type: string |
||||
value: string | undefined |
||||
range?: [number, number] |
||||
loc?: { |
||||
start: { |
||||
line: number |
||||
column: number |
||||
} |
||||
end: { |
||||
line: number |
||||
column: number |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
interface Location { |
||||
start: { |
||||
line: number |
||||
column: number |
||||
} |
||||
end: { |
||||
line: number |
||||
column: number |
||||
} |
||||
} |
||||
|
||||
export interface BaseASTNode { |
||||
type: ASTNodeTypeString |
||||
range?: [number, number] |
||||
loc?: Location |
||||
} |
||||
|
||||
export interface SourceUnit extends BaseASTNode { |
||||
type: 'SourceUnit' |
||||
children: ASTNode[] |
||||
} |
||||
|
||||
export interface ContractDefinition extends BaseASTNode { |
||||
type: 'ContractDefinition' |
||||
name: string |
||||
baseContracts: InheritanceSpecifier[] |
||||
kind: string |
||||
subNodes: BaseASTNode[] |
||||
} |
||||
|
||||
export interface InheritanceSpecifier extends BaseASTNode { |
||||
type: 'InheritanceSpecifier' |
||||
baseName: UserDefinedTypeName |
||||
arguments: Expression[] |
||||
} |
||||
|
||||
export interface UserDefinedTypeName extends BaseASTNode { |
||||
type: 'UserDefinedTypeName' |
||||
namePath: string |
||||
} |
||||
|
||||
export type ASTNodeTypeString = typeof astNodeTypes[number] |
||||
export interface InvalidNode extends BaseASTNode { |
||||
type: 'InvalidNode' |
||||
name?: string |
||||
value?: string |
||||
} |
||||
export interface PragmaDirective extends BaseASTNode { |
||||
type: 'PragmaDirective' |
||||
name: string |
||||
value: string |
||||
} |
||||
export interface ImportDirective extends BaseASTNode { |
||||
type: 'ImportDirective' |
||||
path: string |
||||
pathLiteral: StringLiteral |
||||
unitAlias: string | null |
||||
unitAliasIdentifier: Identifier | null |
||||
symbolAliases: Array<[string, string | null]> | null |
||||
symbolAliasesIdentifiers: Array<[Identifier, Identifier | null]> | null |
||||
} |
||||
export interface StateVariableDeclaration extends BaseASTNode { |
||||
type: 'StateVariableDeclaration' |
||||
variables: StateVariableDeclarationVariable[] |
||||
initialValue: Expression | null |
||||
} |
||||
export interface FileLevelConstant extends BaseASTNode { |
||||
type: 'FileLevelConstant' |
||||
typeName: TypeName |
||||
name: string |
||||
initialValue: Expression |
||||
isDeclaredConst: boolean |
||||
isImmutable: boolean |
||||
} |
||||
export interface UsingForDeclaration extends BaseASTNode { |
||||
type: 'UsingForDeclaration' |
||||
typeName: TypeName | null |
||||
functions: string[] |
||||
libraryName: string | null |
||||
isGlobal: boolean; |
||||
} |
||||
export interface StructDefinition extends BaseASTNode { |
||||
type: 'StructDefinition' |
||||
name: string |
||||
members: VariableDeclaration[] |
||||
} |
||||
export interface ModifierDefinition extends BaseASTNode { |
||||
type: 'ModifierDefinition' |
||||
name: string |
||||
parameters: null | VariableDeclaration[] |
||||
isVirtual: boolean |
||||
override: null | UserDefinedTypeName[] |
||||
body: Block | null |
||||
} |
||||
export interface ModifierInvocation extends BaseASTNode { |
||||
type: 'ModifierInvocation' |
||||
name: string |
||||
arguments: Expression[] | null |
||||
} |
||||
export interface FunctionDefinition extends BaseASTNode { |
||||
type: 'FunctionDefinition' |
||||
name: string | null |
||||
parameters: VariableDeclaration[] |
||||
modifiers: ModifierInvocation[] |
||||
stateMutability: 'pure' | 'constant' | 'payable' | 'view' | null |
||||
visibility: 'default' | 'external' | 'internal' | 'public' | 'private' |
||||
returnParameters: VariableDeclaration[] | null |
||||
body: Block | null |
||||
override: UserDefinedTypeName[] | null |
||||
isConstructor: boolean |
||||
isReceiveEther: boolean |
||||
isFallback: boolean |
||||
isVirtual: boolean |
||||
} |
||||
|
||||
export interface CustomErrorDefinition extends BaseASTNode { |
||||
type: 'CustomErrorDefinition' |
||||
name: string |
||||
parameters: VariableDeclaration[] |
||||
} |
||||
|
||||
export interface TypeDefinition extends BaseASTNode { |
||||
type: 'TypeDefinition' |
||||
name: string |
||||
definition: ElementaryTypeName |
||||
} |
||||
|
||||
export interface RevertStatement extends BaseASTNode { |
||||
type: 'RevertStatement' |
||||
revertCall: FunctionCall |
||||
} |
||||
export interface EventDefinition extends BaseASTNode { |
||||
type: 'EventDefinition' |
||||
name: string |
||||
parameters: VariableDeclaration[] |
||||
isAnonymous: boolean |
||||
} |
||||
export interface EnumValue extends BaseASTNode { |
||||
type: 'EnumValue' |
||||
name: string |
||||
} |
||||
export interface EnumDefinition extends BaseASTNode { |
||||
type: 'EnumDefinition' |
||||
name: string |
||||
members: EnumValue[] |
||||
} |
||||
export interface VariableDeclaration extends BaseASTNode { |
||||
type: 'VariableDeclaration' |
||||
isIndexed: boolean |
||||
isStateVar: boolean |
||||
typeName: TypeName | null |
||||
name: string | null |
||||
identifier: Identifier | null |
||||
isDeclaredConst?: boolean |
||||
storageLocation: string | null |
||||
expression: Expression | null |
||||
visibility?: 'public' | 'private' | 'internal' | 'default' |
||||
} |
||||
export interface StateVariableDeclarationVariable extends VariableDeclaration { |
||||
override: null | UserDefinedTypeName[] |
||||
isImmutable: boolean |
||||
} |
||||
export interface ArrayTypeName extends BaseASTNode { |
||||
type: 'ArrayTypeName' |
||||
baseTypeName: TypeName |
||||
length: Expression | null |
||||
} |
||||
export interface Mapping extends BaseASTNode { |
||||
type: 'Mapping' |
||||
keyType: ElementaryTypeName | UserDefinedTypeName |
||||
valueType: TypeName |
||||
} |
||||
export interface FunctionTypeName extends BaseASTNode { |
||||
type: 'FunctionTypeName' |
||||
parameterTypes: VariableDeclaration[] |
||||
returnTypes: VariableDeclaration[] |
||||
visibility: string |
||||
stateMutability: string | null |
||||
} |
||||
|
||||
export interface Block extends BaseASTNode { |
||||
type: 'Block' |
||||
statements: BaseASTNode[] |
||||
} |
||||
export interface ExpressionStatement extends BaseASTNode { |
||||
type: 'ExpressionStatement' |
||||
expression: Expression | null |
||||
} |
||||
export interface IfStatement extends BaseASTNode { |
||||
type: 'IfStatement' |
||||
condition: Expression |
||||
trueBody: Statement |
||||
falseBody: Statement | null |
||||
} |
||||
export interface UncheckedStatement extends BaseASTNode { |
||||
type: 'UncheckedStatement' |
||||
block: Block |
||||
} |
||||
export interface TryStatement extends BaseASTNode { |
||||
type: 'TryStatement' |
||||
expression: Expression |
||||
returnParameters: VariableDeclaration[] | null |
||||
body: Block |
||||
catchClauses: CatchClause[] |
||||
} |
||||
export interface CatchClause extends BaseASTNode { |
||||
type: 'CatchClause' |
||||
isReasonStringType: boolean |
||||
kind: string | null |
||||
parameters: VariableDeclaration[] | null |
||||
body: Block |
||||
} |
||||
export interface WhileStatement extends BaseASTNode { |
||||
type: 'WhileStatement' |
||||
condition: Expression |
||||
body: Statement |
||||
} |
||||
export interface ForStatement extends BaseASTNode { |
||||
type: 'ForStatement' |
||||
initExpression: SimpleStatement | null |
||||
conditionExpression?: Expression |
||||
loopExpression: ExpressionStatement |
||||
body: Statement |
||||
} |
||||
export interface InlineAssemblyStatement extends BaseASTNode { |
||||
type: 'InlineAssemblyStatement' |
||||
language: string | null |
||||
flags: string[] |
||||
body: AssemblyBlock |
||||
} |
||||
export interface DoWhileStatement extends BaseASTNode { |
||||
type: 'DoWhileStatement' |
||||
condition: Expression |
||||
body: Statement |
||||
} |
||||
export interface ContinueStatement extends BaseASTNode { |
||||
type: 'ContinueStatement' |
||||
} |
||||
export interface Break extends BaseASTNode { |
||||
type: 'Break' |
||||
} |
||||
export interface Continue extends BaseASTNode { |
||||
type: 'Continue' |
||||
} |
||||
export interface BreakStatement extends BaseASTNode { |
||||
type: 'BreakStatement' |
||||
} |
||||
export interface ReturnStatement extends BaseASTNode { |
||||
type: 'ReturnStatement' |
||||
expression: Expression | null |
||||
} |
||||
export interface EmitStatement extends BaseASTNode { |
||||
type: 'EmitStatement' |
||||
eventCall: FunctionCall |
||||
} |
||||
export interface ThrowStatement extends BaseASTNode { |
||||
type: 'ThrowStatement' |
||||
} |
||||
export interface VariableDeclarationStatement extends BaseASTNode { |
||||
type: 'VariableDeclarationStatement' |
||||
variables: Array<BaseASTNode | null> |
||||
initialValue: Expression | null |
||||
} |
||||
export interface ElementaryTypeName extends BaseASTNode { |
||||
type: 'ElementaryTypeName' |
||||
name: string |
||||
stateMutability: string | null |
||||
} |
||||
export interface FunctionCall extends BaseASTNode { |
||||
type: 'FunctionCall' |
||||
expression: Expression |
||||
arguments: Expression[] |
||||
names: string[] |
||||
identifiers: Identifier[] |
||||
} |
||||
export interface AssemblyBlock extends BaseASTNode { |
||||
type: 'AssemblyBlock' |
||||
operations: AssemblyItem[] |
||||
} |
||||
export interface AssemblyCall extends BaseASTNode { |
||||
type: 'AssemblyCall' |
||||
functionName: string |
||||
arguments: AssemblyExpression[] |
||||
} |
||||
export interface AssemblyLocalDefinition extends BaseASTNode { |
||||
type: 'AssemblyLocalDefinition' |
||||
names: Identifier[] | AssemblyMemberAccess[] |
||||
expression: AssemblyExpression | null |
||||
} |
||||
export interface AssemblyAssignment extends BaseASTNode { |
||||
type: 'AssemblyAssignment' |
||||
names: Identifier[] | AssemblyMemberAccess[] |
||||
expression: AssemblyExpression |
||||
} |
||||
export interface AssemblyStackAssignment extends BaseASTNode { |
||||
type: 'AssemblyStackAssignment' |
||||
name: string |
||||
expression: AssemblyExpression |
||||
} |
||||
export interface LabelDefinition extends BaseASTNode { |
||||
type: 'LabelDefinition' |
||||
name: string |
||||
} |
||||
export interface AssemblySwitch extends BaseASTNode { |
||||
type: 'AssemblySwitch' |
||||
expression: AssemblyExpression |
||||
cases: AssemblyCase[] |
||||
} |
||||
export interface AssemblyCase extends BaseASTNode { |
||||
type: 'AssemblyCase' |
||||
value: AssemblyLiteral | null |
||||
block: AssemblyBlock |
||||
default: boolean |
||||
} |
||||
export interface AssemblyFunctionDefinition extends BaseASTNode { |
||||
type: 'AssemblyFunctionDefinition' |
||||
name: string |
||||
arguments: Identifier[] |
||||
returnArguments: Identifier[] |
||||
body: AssemblyBlock |
||||
} |
||||
export interface AssemblyFunctionReturns extends BaseASTNode { |
||||
type: 'AssemblyFunctionReturns' |
||||
} |
||||
export interface AssemblyFor extends BaseASTNode { |
||||
type: 'AssemblyFor' |
||||
pre: AssemblyBlock | AssemblyExpression |
||||
condition: AssemblyExpression |
||||
post: AssemblyBlock | AssemblyExpression |
||||
body: AssemblyBlock |
||||
} |
||||
export interface AssemblyIf extends BaseASTNode { |
||||
type: 'AssemblyIf' |
||||
condition: AssemblyExpression |
||||
body: AssemblyBlock |
||||
} |
||||
export type AssemblyLiteral = |
||||
| StringLiteral |
||||
| DecimalNumber |
||||
| HexNumber |
||||
| HexLiteral |
||||
export interface SubAssembly extends BaseASTNode { |
||||
type: 'SubAssembly' |
||||
} |
||||
export interface AssemblyMemberAccess extends BaseASTNode { |
||||
type: 'AssemblyMemberAccess' |
||||
expression: Identifier |
||||
memberName: Identifier |
||||
} |
||||
export interface NewExpression extends BaseASTNode { |
||||
type: 'NewExpression' |
||||
typeName: TypeName |
||||
} |
||||
export interface TupleExpression extends BaseASTNode { |
||||
type: 'TupleExpression' |
||||
components: Array<BaseASTNode | null> |
||||
isArray: boolean |
||||
} |
||||
export interface NameValueExpression extends BaseASTNode { |
||||
type: 'NameValueExpression' |
||||
expression: Expression |
||||
arguments: NameValueList |
||||
} |
||||
export interface NumberLiteral extends BaseASTNode { |
||||
type: 'NumberLiteral' |
||||
number: string |
||||
subdenomination: |
||||
| null |
||||
| 'wei' |
||||
| 'szabo' |
||||
| 'finney' |
||||
| 'ether' |
||||
| 'seconds' |
||||
| 'minutes' |
||||
| 'hours' |
||||
| 'days' |
||||
| 'weeks' |
||||
| 'years' |
||||
} |
||||
export interface BooleanLiteral extends BaseASTNode { |
||||
type: 'BooleanLiteral' |
||||
value: boolean |
||||
} |
||||
export interface HexLiteral extends BaseASTNode { |
||||
type: 'HexLiteral' |
||||
value: string |
||||
parts: string[] |
||||
} |
||||
export interface StringLiteral extends BaseASTNode { |
||||
type: 'StringLiteral' |
||||
value: string |
||||
parts: string[] |
||||
isUnicode: boolean[] |
||||
} |
||||
export interface Identifier extends BaseASTNode { |
||||
type: 'Identifier' |
||||
name: string |
||||
} |
||||
|
||||
export interface BinaryOperation extends BaseASTNode { |
||||
type: 'BinaryOperation' |
||||
left: Expression |
||||
right: Expression |
||||
operator: BinOp |
||||
} |
||||
export interface UnaryOperation extends BaseASTNode { |
||||
type: 'UnaryOperation' |
||||
operator: UnaryOp |
||||
subExpression: Expression |
||||
isPrefix: boolean |
||||
} |
||||
export interface Conditional extends BaseASTNode { |
||||
type: 'Conditional' |
||||
condition: Expression |
||||
trueExpression: Expression |
||||
falseExpression: Expression |
||||
} |
||||
export interface IndexAccess extends BaseASTNode { |
||||
type: 'IndexAccess' |
||||
base: Expression |
||||
index: Expression |
||||
} |
||||
export interface IndexRangeAccess extends BaseASTNode { |
||||
type: 'IndexRangeAccess' |
||||
base: Expression |
||||
indexStart?: Expression |
||||
indexEnd?: Expression |
||||
} |
||||
export interface MemberAccess extends BaseASTNode { |
||||
type: 'MemberAccess' |
||||
expression: Expression |
||||
memberName: string |
||||
} |
||||
export interface HexNumber extends BaseASTNode { |
||||
type: 'HexNumber' |
||||
value: string |
||||
} |
||||
export interface DecimalNumber extends BaseASTNode { |
||||
type: 'DecimalNumber' |
||||
value: string |
||||
} |
||||
export interface NameValueList extends BaseASTNode { |
||||
type: 'NameValueList' |
||||
names: string[] |
||||
identifiers: Identifier[] |
||||
arguments: Expression[] |
||||
} |
||||
|
||||
export type ASTNode = |
||||
| SourceUnit |
||||
| PragmaDirective |
||||
| ImportDirective |
||||
| ContractDefinition |
||||
| InheritanceSpecifier |
||||
| StateVariableDeclaration |
||||
| UsingForDeclaration |
||||
| StructDefinition |
||||
| ModifierDefinition |
||||
| ModifierInvocation |
||||
| FunctionDefinition |
||||
| EventDefinition |
||||
| CustomErrorDefinition |
||||
| EnumValue |
||||
| EnumDefinition |
||||
| VariableDeclaration |
||||
| TypeName |
||||
| UserDefinedTypeName |
||||
| Mapping |
||||
| FunctionTypeName |
||||
| Block |
||||
| Statement |
||||
| ElementaryTypeName |
||||
| AssemblyBlock |
||||
| AssemblyCall |
||||
| AssemblyLocalDefinition |
||||
| AssemblyAssignment |
||||
| AssemblyStackAssignment |
||||
| LabelDefinition |
||||
| AssemblySwitch |
||||
| AssemblyCase |
||||
| AssemblyFunctionDefinition |
||||
| AssemblyFunctionReturns |
||||
| AssemblyFor |
||||
| AssemblyIf |
||||
| AssemblyLiteral |
||||
| SubAssembly |
||||
| TupleExpression |
||||
| BinaryOperation |
||||
| Conditional |
||||
| IndexAccess |
||||
| IndexRangeAccess |
||||
| AssemblyItem |
||||
| Expression |
||||
| NameValueList |
||||
| AssemblyMemberAccess |
||||
| CatchClause |
||||
| FileLevelConstant |
||||
| TypeDefinition |
||||
| InvalidNode |
||||
|
||||
export type AssemblyItem = |
||||
| Identifier |
||||
| AssemblyBlock |
||||
| AssemblyExpression |
||||
| AssemblyLocalDefinition |
||||
| AssemblyAssignment |
||||
| AssemblyStackAssignment |
||||
| LabelDefinition |
||||
| AssemblySwitch |
||||
| AssemblyFunctionDefinition |
||||
| AssemblyFor |
||||
| AssemblyIf |
||||
| Break |
||||
| Continue |
||||
| SubAssembly |
||||
| NumberLiteral |
||||
| StringLiteral |
||||
| HexNumber |
||||
| HexLiteral |
||||
| DecimalNumber |
||||
export type AssemblyExpression = AssemblyCall | AssemblyLiteral |
||||
export type Expression = |
||||
| IndexAccess |
||||
| IndexRangeAccess |
||||
| TupleExpression |
||||
| BinaryOperation |
||||
| Conditional |
||||
| MemberAccess |
||||
| FunctionCall |
||||
| UnaryOperation |
||||
| NewExpression |
||||
| PrimaryExpression |
||||
| NameValueExpression |
||||
export type PrimaryExpression = |
||||
| BooleanLiteral |
||||
| HexLiteral |
||||
| StringLiteral |
||||
| NumberLiteral |
||||
| Identifier |
||||
| TupleExpression |
||||
| TypeName |
||||
export type SimpleStatement = VariableDeclarationStatement | ExpressionStatement |
||||
export type TypeName = |
||||
| ElementaryTypeName |
||||
| UserDefinedTypeName |
||||
| Mapping |
||||
| ArrayTypeName |
||||
| FunctionTypeName |
||||
export type Statement = |
||||
| IfStatement |
||||
| WhileStatement |
||||
| ForStatement |
||||
| Block |
||||
| InlineAssemblyStatement |
||||
| DoWhileStatement |
||||
| ContinueStatement |
||||
| BreakStatement |
||||
| ReturnStatement |
||||
| EmitStatement |
||||
| ThrowStatement |
||||
| SimpleStatement |
||||
| VariableDeclarationStatement |
||||
| UncheckedStatement |
||||
| TryStatement |
||||
| RevertStatement |
||||
|
||||
type ASTMap<U> = { [K in ASTNodeTypeString]: U extends { type: K } ? U : never } |
||||
type ASTTypeMap = ASTMap<ASTNode> |
||||
|
||||
|
||||
export const astNodeTypes = [ |
||||
'SourceUnit', |
||||
'PragmaDirective', |
||||
'ImportDirective', |
||||
'ContractDefinition', |
||||
'InheritanceSpecifier', |
||||
'StateVariableDeclaration', |
||||
'UsingForDeclaration', |
||||
'StructDefinition', |
||||
'ModifierDefinition', |
||||
'ModifierInvocation', |
||||
'FunctionDefinition', |
||||
'EventDefinition', |
||||
'CustomErrorDefinition', |
||||
'RevertStatement', |
||||
'EnumValue', |
||||
'EnumDefinition', |
||||
'VariableDeclaration', |
||||
'UserDefinedTypeName', |
||||
'Mapping', |
||||
'ArrayTypeName', |
||||
'FunctionTypeName', |
||||
'Block', |
||||
'ExpressionStatement', |
||||
'IfStatement', |
||||
'WhileStatement', |
||||
'ForStatement', |
||||
'InlineAssemblyStatement', |
||||
'DoWhileStatement', |
||||
'ContinueStatement', |
||||
'Break', |
||||
'Continue', |
||||
'BreakStatement', |
||||
'ReturnStatement', |
||||
'EmitStatement', |
||||
'ThrowStatement', |
||||
'VariableDeclarationStatement', |
||||
'ElementaryTypeName', |
||||
'FunctionCall', |
||||
'AssemblyBlock', |
||||
'AssemblyCall', |
||||
'AssemblyLocalDefinition', |
||||
'AssemblyAssignment', |
||||
'AssemblyStackAssignment', |
||||
'LabelDefinition', |
||||
'AssemblySwitch', |
||||
'AssemblyCase', |
||||
'AssemblyFunctionDefinition', |
||||
'AssemblyFunctionReturns', |
||||
'AssemblyFor', |
||||
'AssemblyIf', |
||||
'SubAssembly', |
||||
'TupleExpression', |
||||
'NameValueExpression', |
||||
'BooleanLiteral', |
||||
'NumberLiteral', |
||||
'Identifier', |
||||
'BinaryOperation', |
||||
'UnaryOperation', |
||||
'NewExpression', |
||||
'Conditional', |
||||
'StringLiteral', |
||||
'HexLiteral', |
||||
'HexNumber', |
||||
'DecimalNumber', |
||||
'MemberAccess', |
||||
'IndexAccess', |
||||
'IndexRangeAccess', |
||||
'NameValueList', |
||||
'UncheckedStatement', |
||||
'TryStatement', |
||||
'CatchClause', |
||||
'FileLevelConstant', |
||||
'AssemblyMemberAccess', |
||||
'TypeDefinition', |
||||
'InvalidNode' |
||||
] as const |
||||
|
||||
|
||||
|
||||
|
||||
export const binaryOpValues = [ |
||||
'+', |
||||
'-', |
||||
'*', |
||||
'/', |
||||
'**', |
||||
'%', |
||||
'<<', |
||||
'>>', |
||||
'&&', |
||||
'||', |
||||
',,', |
||||
'&', |
||||
',', |
||||
'^', |
||||
'<', |
||||
'>', |
||||
'<=', |
||||
'>=', |
||||
'==', |
||||
'!=', |
||||
'=', |
||||
',=', |
||||
'^=', |
||||
'&=', |
||||
'<<=', |
||||
'>>=', |
||||
'+=', |
||||
'-=', |
||||
'*=', |
||||
'/=', |
||||
'%=', |
||||
'|', |
||||
'|=', |
||||
] as const |
||||
export type BinOp = typeof binaryOpValues[number] |
||||
|
||||
export const unaryOpValues = [ |
||||
'-', |
||||
'+', |
||||
'++', |
||||
'--', |
||||
'~', |
||||
'after', |
||||
'delete', |
||||
'!', |
||||
] as const |
||||
export type UnaryOp = typeof unaryOpValues[number] |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1 @@ |
||||
export * as antlr from './antlr-types' |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,251 @@ |
||||
T__0=1 |
||||
T__1=2 |
||||
T__2=3 |
||||
T__3=4 |
||||
T__4=5 |
||||
T__5=6 |
||||
T__6=7 |
||||
T__7=8 |
||||
T__8=9 |
||||
T__9=10 |
||||
T__10=11 |
||||
T__11=12 |
||||
T__12=13 |
||||
T__13=14 |
||||
T__14=15 |
||||
T__15=16 |
||||
T__16=17 |
||||
T__17=18 |
||||
T__18=19 |
||||
T__19=20 |
||||
T__20=21 |
||||
T__21=22 |
||||
T__22=23 |
||||
T__23=24 |
||||
T__24=25 |
||||
T__25=26 |
||||
T__26=27 |
||||
T__27=28 |
||||
T__28=29 |
||||
T__29=30 |
||||
T__30=31 |
||||
T__31=32 |
||||
T__32=33 |
||||
T__33=34 |
||||
T__34=35 |
||||
T__35=36 |
||||
T__36=37 |
||||
T__37=38 |
||||
T__38=39 |
||||
T__39=40 |
||||
T__40=41 |
||||
T__41=42 |
||||
T__42=43 |
||||
T__43=44 |
||||
T__44=45 |
||||
T__45=46 |
||||
T__46=47 |
||||
T__47=48 |
||||
T__48=49 |
||||
T__49=50 |
||||
T__50=51 |
||||
T__51=52 |
||||
T__52=53 |
||||
T__53=54 |
||||
T__54=55 |
||||
T__55=56 |
||||
T__56=57 |
||||
T__57=58 |
||||
T__58=59 |
||||
T__59=60 |
||||
T__60=61 |
||||
T__61=62 |
||||
T__62=63 |
||||
T__63=64 |
||||
T__64=65 |
||||
T__65=66 |
||||
T__66=67 |
||||
T__67=68 |
||||
T__68=69 |
||||
T__69=70 |
||||
T__70=71 |
||||
T__71=72 |
||||
T__72=73 |
||||
T__73=74 |
||||
T__74=75 |
||||
T__75=76 |
||||
T__76=77 |
||||
T__77=78 |
||||
T__78=79 |
||||
T__79=80 |
||||
T__80=81 |
||||
T__81=82 |
||||
T__82=83 |
||||
T__83=84 |
||||
T__84=85 |
||||
T__85=86 |
||||
T__86=87 |
||||
T__87=88 |
||||
T__88=89 |
||||
T__89=90 |
||||
T__90=91 |
||||
T__91=92 |
||||
T__92=93 |
||||
T__93=94 |
||||
T__94=95 |
||||
T__95=96 |
||||
T__96=97 |
||||
Int=98 |
||||
Uint=99 |
||||
Byte=100 |
||||
Fixed=101 |
||||
Ufixed=102 |
||||
BooleanLiteral=103 |
||||
DecimalNumber=104 |
||||
HexNumber=105 |
||||
NumberUnit=106 |
||||
HexLiteralFragment=107 |
||||
ReservedKeyword=108 |
||||
AnonymousKeyword=109 |
||||
BreakKeyword=110 |
||||
ConstantKeyword=111 |
||||
ImmutableKeyword=112 |
||||
ContinueKeyword=113 |
||||
LeaveKeyword=114 |
||||
ExternalKeyword=115 |
||||
IndexedKeyword=116 |
||||
InternalKeyword=117 |
||||
PayableKeyword=118 |
||||
PrivateKeyword=119 |
||||
PublicKeyword=120 |
||||
VirtualKeyword=121 |
||||
PureKeyword=122 |
||||
TypeKeyword=123 |
||||
ViewKeyword=124 |
||||
GlobalKeyword=125 |
||||
ConstructorKeyword=126 |
||||
FallbackKeyword=127 |
||||
ReceiveKeyword=128 |
||||
Identifier=129 |
||||
StringLiteralFragment=130 |
||||
VersionLiteral=131 |
||||
WS=132 |
||||
COMMENT=133 |
||||
LINE_COMMENT=134 |
||||
'pragma'=1 |
||||
';'=2 |
||||
'*'=3 |
||||
'||'=4 |
||||
'^'=5 |
||||
'~'=6 |
||||
'>='=7 |
||||
'>'=8 |
||||
'<'=9 |
||||
'<='=10 |
||||
'='=11 |
||||
'as'=12 |
||||
'import'=13 |
||||
'from'=14 |
||||
'{'=15 |
||||
','=16 |
||||
'}'=17 |
||||
'abstract'=18 |
||||
'contract'=19 |
||||
'interface'=20 |
||||
'library'=21 |
||||
'is'=22 |
||||
'('=23 |
||||
')'=24 |
||||
'error'=25 |
||||
'using'=26 |
||||
'for'=27 |
||||
'struct'=28 |
||||
'modifier'=29 |
||||
'function'=30 |
||||
'returns'=31 |
||||
'event'=32 |
||||
'enum'=33 |
||||
'['=34 |
||||
']'=35 |
||||
'address'=36 |
||||
'.'=37 |
||||
'mapping'=38 |
||||
'=>'=39 |
||||
'memory'=40 |
||||
'storage'=41 |
||||
'calldata'=42 |
||||
'if'=43 |
||||
'else'=44 |
||||
'try'=45 |
||||
'catch'=46 |
||||
'while'=47 |
||||
'unchecked'=48 |
||||
'assembly'=49 |
||||
'do'=50 |
||||
'return'=51 |
||||
'throw'=52 |
||||
'emit'=53 |
||||
'revert'=54 |
||||
'var'=55 |
||||
'bool'=56 |
||||
'string'=57 |
||||
'byte'=58 |
||||
'++'=59 |
||||
'--'=60 |
||||
'new'=61 |
||||
':'=62 |
||||
'+'=63 |
||||
'-'=64 |
||||
'after'=65 |
||||
'delete'=66 |
||||
'!'=67 |
||||
'**'=68 |
||||
'/'=69 |
||||
'%'=70 |
||||
'<<'=71 |
||||
'>>'=72 |
||||
'&'=73 |
||||
'|'=74 |
||||
'=='=75 |
||||
'!='=76 |
||||
'&&'=77 |
||||
'?'=78 |
||||
'|='=79 |
||||
'^='=80 |
||||
'&='=81 |
||||
'<<='=82 |
||||
'>>='=83 |
||||
'+='=84 |
||||
'-='=85 |
||||
'*='=86 |
||||
'/='=87 |
||||
'%='=88 |
||||
'let'=89 |
||||
':='=90 |
||||
'=:'=91 |
||||
'switch'=92 |
||||
'case'=93 |
||||
'default'=94 |
||||
'->'=95 |
||||
'callback'=96 |
||||
'override'=97 |
||||
'anonymous'=109 |
||||
'break'=110 |
||||
'constant'=111 |
||||
'immutable'=112 |
||||
'continue'=113 |
||||
'leave'=114 |
||||
'external'=115 |
||||
'indexed'=116 |
||||
'internal'=117 |
||||
'payable'=118 |
||||
'private'=119 |
||||
'public'=120 |
||||
'virtual'=121 |
||||
'pure'=122 |
||||
'type'=123 |
||||
'view'=124 |
||||
'global'=125 |
||||
'constructor'=126 |
||||
'fallback'=127 |
||||
'receive'=128 |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,75 +0,0 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import * as packageJson from '../../../../package.json' |
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
|
||||
const profile = { |
||||
name: 'offsetToLineColumnConverter', |
||||
methods: ['offsetToLineColumn'], |
||||
events: [], |
||||
version: packageJson.version |
||||
} |
||||
|
||||
export class OffsetToLineColumnConverter extends Plugin { |
||||
constructor () { |
||||
super(profile) |
||||
this.lineBreakPositionsByContent = {} |
||||
this.sourceMappingDecoder = sourceMappingDecoder |
||||
} |
||||
|
||||
/** |
||||
* Convert offset representation with line/column representation. |
||||
* This is also used to resolve the content: |
||||
* @arg file is the index of the file in the content sources array and content sources array does have filename as key and not index. |
||||
* So we use the asts (which references both index and filename) to look up the actual content targeted by the @arg file index. |
||||
* @param {{start, length}} rawLocation - offset location |
||||
* @param {number} file - The index where to find the source in the sources parameters |
||||
* @param {Object.<string, {content}>} sources - Map of content sources |
||||
* @param {Object.<string, {ast, id}>} asts - Map of content sources |
||||
*/ |
||||
offsetToLineColumn (rawLocation, file, sources, asts) { |
||||
if (!this.lineBreakPositionsByContent[file]) { |
||||
const sourcesArray = Object.keys(sources) |
||||
if (!asts || (file === 0 && sourcesArray.length === 1)) { |
||||
// if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12)
|
||||
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content) |
||||
} else { |
||||
for (var filename in asts) { |
||||
const source = asts[filename] |
||||
if (source.id === file) { |
||||
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) |
||||
} |
||||
|
||||
/** |
||||
* Convert offset representation with line/column representation. |
||||
* @param {{start, length}} rawLocation - offset location |
||||
* @param {number} file - The index where to find the source in the sources parameters |
||||
* @param {string} content - source |
||||
*/ |
||||
offsetToLineColumnWithContent (rawLocation, file, content) { |
||||
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(content) |
||||
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) |
||||
} |
||||
|
||||
/** |
||||
* Clear the cache |
||||
*/ |
||||
clear () { |
||||
this.lineBreakPositionsByContent = {} |
||||
} |
||||
|
||||
/** |
||||
* called by plugin API |
||||
*/ |
||||
activate () { |
||||
this.on('solidity', 'compilationFinished', () => { |
||||
this.clear() |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
# vyper-remix |
||||
Vyper Plugin for Remix IDE. |
||||
|
||||
|
||||
## How to get started |
||||
### Remote plugin |
||||
This plugin is hosted at https://vyper.remixproject.org . |
||||
To use it, open the plugin manager in Remix and click the `Activate` button in front of the `Vyper` button in the plugin section. |
||||
|
||||
### Local plugin |
||||
You can host this plugin in your local environment. |
||||
|
||||
```git clone https://github.com/ethereum/remix-project``` |
||||
|
||||
```cd remix-project``` |
||||
|
||||
```yarn install``` |
||||
|
||||
```nx build vyper``` |
||||
|
||||
```nx serve vyper``` |
||||
|
||||
## How to use plugin |
||||
1. Write vyper code(.vy) in the editor |
||||
2. Click Compile button |
||||
3. Now you can deploy the contract in the Run tab! |
||||
|
||||
|
||||
## Load example contracts |
||||
It is possible to clone the Vyper repository in Remix in order to use example contracts. Click on `Clone Vyper Repository`. |
||||
Once it is cloned, you will find the contract in the `examples` folder. |
||||
|
||||
### Local Vyper Compiler |
||||
You can use your local Vyper compiler by selecting the radio button `Local` . |
||||
First, you need to install Vyper. It is strongly recommended to install Vyper in a virtual Python environment. |
||||
|
||||
```pip3 install vyper``` |
||||
|
||||
(see [installing-vyper](https://vyper.readthedocs.io/en/latest/installing-vyper.html#installing-vyper)). |
||||
|
||||
Then, Vyper compiler starts with this command (default: http://localhost:8000). |
||||
|
||||
```vyper-serve``` |
@ -1,161 +0,0 @@ |
||||
export const Ballot = { |
||||
name: 'browser/ballot.vy', |
||||
content: `# Voting with delegation.
|
||||
|
||||
# Information about voters |
||||
struct Voter: |
||||
# weight is accumulated by delegation |
||||
weight: int128 |
||||
# if true, that person already voted (which includes voting by delegating) |
||||
voted: bool |
||||
# person delegated to |
||||
delegate: address |
||||
# index of the voted proposal, which is not meaningful unless 'voted' is True. |
||||
vote: int128 |
||||
|
||||
# Users can create proposals |
||||
struct Proposal: |
||||
# short name (up to 32 bytes) |
||||
name: bytes32 |
||||
# number of accumulated votes |
||||
voteCount: int128 |
||||
|
||||
voters: public(map(address, Voter)) |
||||
proposals: public(map(int128, Proposal)) |
||||
voterCount: public(int128) |
||||
chairperson: public(address) |
||||
int128Proposals: public(int128) |
||||
|
||||
|
||||
@public |
||||
@constant |
||||
def delegated(addr: address) -> bool: |
||||
return self.voters[addr].delegate != ZERO_ADDRESS |
||||
|
||||
|
||||
@public |
||||
@constant |
||||
def directlyVoted(addr: address) -> bool: |
||||
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS) |
||||
|
||||
|
||||
# Setup global variables |
||||
@public |
||||
def __init__(_proposalNames: bytes32[2]): |
||||
self.chairperson = msg.sender |
||||
self.voterCount = 0 |
||||
for i in range(2): |
||||
self.proposals[i] = Proposal({ |
||||
name: _proposalNames[i], |
||||
voteCount: 0 |
||||
}) |
||||
self.int128Proposals += 1 |
||||
|
||||
# Give a 'voter' the right to vote on this ballot. |
||||
# This may only be called by the 'chairperson'. |
||||
@public |
||||
def giveRightToVote(voter: address): |
||||
# Throws if the sender is not the chairperson. |
||||
assert msg.sender == self.chairperson |
||||
# Throws if the voter has already voted. |
||||
assert not self.voters[voter].voted |
||||
# Throws if the voter's voting weight isn't 0. |
||||
assert self.voters[voter].weight == 0 |
||||
self.voters[voter].weight = 1 |
||||
self.voterCount += 1 |
||||
|
||||
# Used by 'delegate' below, and can be called by anyone. |
||||
@public |
||||
def forwardWeight(delegate_with_weight_to_forward: address): |
||||
assert self.delegated(delegate_with_weight_to_forward) |
||||
# Throw if there is nothing to do: |
||||
assert self.voters[delegate_with_weight_to_forward].weight > 0 |
||||
|
||||
target: address = self.voters[delegate_with_weight_to_forward].delegate |
||||
for i in range(4): |
||||
if self.delegated(target): |
||||
target = self.voters[target].delegate |
||||
# The following effectively detects cycles of length <= 5, |
||||
# in which the delegation is given back to the delegator. |
||||
# This could be done for any int128ber of loops, |
||||
# or even infinitely with a while loop. |
||||
# However, cycles aren't actually problematic for correctness; |
||||
# they just result in spoiled votes. |
||||
# So, in the production version, this should instead be |
||||
# the responsibility of the contract's client, and this |
||||
# check should be removed. |
||||
assert target != delegate_with_weight_to_forward |
||||
else: |
||||
# Weight will be moved to someone who directly voted or |
||||
# hasn't voted. |
||||
break |
||||
|
||||
weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight |
||||
self.voters[delegate_with_weight_to_forward].weight = 0 |
||||
self.voters[target].weight += weight_to_forward |
||||
|
||||
if self.directlyVoted(target): |
||||
self.proposals[self.voters[target].vote].voteCount += weight_to_forward |
||||
self.voters[target].weight = 0 |
||||
|
||||
# To reiterate: if target is also a delegate, this function will need |
||||
# to be called again, similarly to as above. |
||||
|
||||
# Delegate your vote to the voter 'to'. |
||||
@public |
||||
def delegate(to: address): |
||||
# Throws if the sender has already voted |
||||
assert not self.voters[msg.sender].voted |
||||
# Throws if the sender tries to delegate their vote to themselves or to |
||||
# the default address value of 0x0000000000000000000000000000000000000000 |
||||
# (the latter might not be problematic, but I don't want to think about it). |
||||
assert to != msg.sender |
||||
assert to != ZERO_ADDRESS |
||||
|
||||
self.voters[msg.sender].voted = True |
||||
self.voters[msg.sender].delegate = to |
||||
|
||||
# This call will throw if and only if this delegation would cause a loop |
||||
# of length <= 5 that ends up delegating back to the delegator. |
||||
self.forwardWeight(msg.sender) |
||||
|
||||
# Give your vote (including votes delegated to you) |
||||
# to proposal 'proposals[proposal].name'. |
||||
@public |
||||
def vote(proposal: int128): |
||||
# can't vote twice |
||||
assert not self.voters[msg.sender].voted |
||||
# can only vote on legitimate proposals |
||||
assert proposal < self.int128Proposals |
||||
|
||||
self.voters[msg.sender].vote = proposal |
||||
self.voters[msg.sender].voted = True |
||||
|
||||
# transfer msg.sender's weight to proposal |
||||
self.proposals[proposal].voteCount += self.voters[msg.sender].weight |
||||
self.voters[msg.sender].weight = 0 |
||||
|
||||
# Computes the winning proposal taking all |
||||
# previous votes into account. |
||||
@public |
||||
@constant |
||||
def winningProposal() -> int128: |
||||
winning_vote_count: int128 = 0 |
||||
winning_proposal: int128 = 0 |
||||
for i in range(2): |
||||
if self.proposals[i].voteCount > winning_vote_count: |
||||
winning_vote_count = self.proposals[i].voteCount |
||||
winning_proposal = i |
||||
return winning_proposal |
||||
|
||||
# Calls winningProposal() function to get the index |
||||
# of the winner contained in the proposals array and then |
||||
# returns the name of the winner |
||||
@public |
||||
@constant |
||||
def winnerName() -> bytes32: |
||||
return self.proposals[self.winningProposal()].name |
||||
|
||||
` |
||||
} |
||||
|
@ -0,0 +1,4 @@ |
||||
|
||||
const nxPreset = require('@nrwl/jest/preset'); |
||||
|
||||
module.exports = { ...nxPreset } |
@ -1,244 +0,0 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
|
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
|
||||
const profile = { |
||||
name: 'contextualListener', |
||||
methods: ['referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf'], |
||||
events: [], |
||||
version: '0.0.1' |
||||
} |
||||
|
||||
/* |
||||
trigger contextChanged(nodes) |
||||
*/ |
||||
export class EditorContextListener extends Plugin { |
||||
_index: any |
||||
_activeHighlights: Array<any> |
||||
astWalker: any |
||||
currentPosition: any |
||||
currentFile: string |
||||
nodes: Array<any> |
||||
results: any |
||||
estimationObj: any |
||||
creationCost: any |
||||
codeDepositCost: any |
||||
contract: any |
||||
activated: boolean |
||||
|
||||
constructor (astWalker) { |
||||
super(profile) |
||||
this.activated = false |
||||
this._index = { |
||||
Declarations: {}, |
||||
FlatReferences: {} |
||||
} |
||||
this._activeHighlights = [] |
||||
|
||||
this.astWalker = astWalker |
||||
} |
||||
|
||||
onActivation () { |
||||
this.on('editor', 'contentChanged', () => { this._stopHighlighting() }) |
||||
|
||||
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => { |
||||
if (languageVersion.indexOf('soljson') !== 0) return |
||||
this._stopHighlighting() |
||||
this._index = { |
||||
Declarations: {}, |
||||
FlatReferences: {} |
||||
} |
||||
this._buildIndex(data, source) |
||||
}) |
||||
|
||||
setInterval(async () => { |
||||
const compilationResult = await this.call('compilerArtefacts', 'getLastCompilationResult') |
||||
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { |
||||
let currentFile |
||||
try { |
||||
currentFile = await this.call('fileManager', 'file') |
||||
} catch (error) { |
||||
if (error.message !== 'Error: No such file or directory No file selected') throw error |
||||
} |
||||
this._highlightItems( |
||||
await this.call('editor', 'getCursorPosition'), |
||||
compilationResult, |
||||
currentFile |
||||
) |
||||
} |
||||
}, 1000) |
||||
|
||||
this.activated = true |
||||
} |
||||
|
||||
getActiveHighlights () { |
||||
return [...this._activeHighlights] |
||||
} |
||||
|
||||
declarationOf (node) { |
||||
if (node && node.referencedDeclaration) { |
||||
return this._index.FlatReferences[node.referencedDeclaration] |
||||
} |
||||
return null |
||||
} |
||||
|
||||
referencesOf (node) { |
||||
return this._index.Declarations[node.id] |
||||
} |
||||
|
||||
async _highlightItems (cursorPosition, compilationResult, file) { |
||||
if (this.currentPosition === cursorPosition) return |
||||
this._stopHighlighting() |
||||
this.currentPosition = cursorPosition |
||||
this.currentFile = file |
||||
if (compilationResult && compilationResult.data && compilationResult.data.sources && compilationResult.data.sources[file]) { |
||||
const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file]) |
||||
this.nodes = nodes |
||||
if (nodes && nodes.length && nodes[nodes.length - 1]) { |
||||
await this._highlightExpressions(nodes[nodes.length - 1], compilationResult) |
||||
} |
||||
this.emit('contextChanged', nodes) |
||||
} |
||||
} |
||||
|
||||
_buildIndex (compilationResult, source) { |
||||
if (compilationResult && compilationResult.sources) { |
||||
const callback = (node) => { |
||||
if (node && node.referencedDeclaration) { |
||||
if (!this._index.Declarations[node.referencedDeclaration]) { |
||||
this._index.Declarations[node.referencedDeclaration] = [] |
||||
} |
||||
this._index.Declarations[node.referencedDeclaration].push(node) |
||||
} |
||||
this._index.FlatReferences[node.id] = node |
||||
} |
||||
for (const s in compilationResult.sources) { |
||||
this.astWalker.walkFull(compilationResult.sources[s].ast, callback) |
||||
} |
||||
} |
||||
} |
||||
|
||||
async _highlight (node, compilationResult) { |
||||
if (!node) return |
||||
const position = sourceMappingDecoder.decode(node.src) |
||||
const fileTarget = compilationResult.getSourceName(position.file) |
||||
const nodeFound = this._activeHighlights.find((el) => el.fileTarget === fileTarget && el.position.file === position.file && el.position.length === position.length && el.position.start === position.start) |
||||
if (nodeFound) return // if the content is already highlighted, do nothing.
|
||||
|
||||
await this._highlightInternal(position, node, compilationResult) |
||||
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { |
||||
this._activeHighlights.push({ position, fileTarget, nodeId: node.id }) |
||||
} |
||||
} |
||||
|
||||
async _highlightInternal (position, node, compilationResult) { |
||||
if (node.nodeType === 'Block') return |
||||
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { |
||||
let lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, position.file, compilationResult.getSourceCode().sources, compilationResult.getAsts()) |
||||
if (node.nodes && node.nodes.length) { |
||||
// If node has children, highlight the entire line. if not, just highlight the current source position of the node.
|
||||
lineColumn = { |
||||
start: { |
||||
line: lineColumn.start.line, |
||||
column: 0 |
||||
}, |
||||
end: { |
||||
line: lineColumn.start.line + 1, |
||||
column: 0 |
||||
} |
||||
} |
||||
} |
||||
const fileName = compilationResult.getSourceName(position.file) |
||||
if (fileName) { |
||||
return await this.call('editor', 'highlight', lineColumn, fileName, '', { focus: false }) |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
async _highlightExpressions (node, compilationResult) { |
||||
const highlights = async (id) => { |
||||
if (this._index.Declarations && this._index.Declarations[id]) { |
||||
const refs = this._index.Declarations[id] |
||||
for (const ref in refs) { |
||||
const node = refs[ref] |
||||
await this._highlight(node, compilationResult) |
||||
} |
||||
} |
||||
} |
||||
if (node && node.referencedDeclaration) { |
||||
await highlights(node.referencedDeclaration) |
||||
const current = this._index.FlatReferences[node.referencedDeclaration] |
||||
await this._highlight(current, compilationResult) |
||||
} else { |
||||
await highlights(node.id) |
||||
await this._highlight(node, compilationResult) |
||||
} |
||||
this.results = compilationResult |
||||
} |
||||
|
||||
_stopHighlighting () { |
||||
this.call('editor', 'discardHighlight') |
||||
this.emit('stopHighlighting') |
||||
this._activeHighlights = [] |
||||
} |
||||
|
||||
gasEstimation (node) { |
||||
this._loadContractInfos(node) |
||||
let executionCost, codeDepositCost |
||||
if (node.nodeType === 'FunctionDefinition') { |
||||
const visibility = node.visibility |
||||
if (node.kind !== 'constructor') { |
||||
const fnName = node.name |
||||
const fn = fnName + this._getInputParams(node) |
||||
if (visibility === 'public' || visibility === 'external') { |
||||
executionCost = this.estimationObj === null ? '-' : this.estimationObj.external[fn] |
||||
} else if (visibility === 'private' || visibility === 'internal') { |
||||
executionCost = this.estimationObj === null ? '-' : this.estimationObj.internal[fn] |
||||
} |
||||
} else { |
||||
executionCost = this.creationCost |
||||
codeDepositCost = this.codeDepositCost |
||||
} |
||||
} else { |
||||
executionCost = '-' |
||||
} |
||||
return { executionCost, codeDepositCost } |
||||
} |
||||
|
||||
_loadContractInfos (node) { |
||||
const path = (this.nodes.length && this.nodes[0].absolutePath) || this.results.source.target |
||||
for (const i in this.nodes) { |
||||
if (this.nodes[i].id === node.scope) { |
||||
const contract = this.nodes[i] |
||||
this.contract = this.results.data.contracts[path][contract.name] |
||||
if (contract) { |
||||
this.estimationObj = this.contract.evm.gasEstimates |
||||
this.creationCost = this.estimationObj === null ? '-' : this.estimationObj.creation.totalCost |
||||
this.codeDepositCost = this.estimationObj === null ? '-' : this.estimationObj.creation.codeDepositCost |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
_getInputParams (node) { |
||||
const params = [] |
||||
const target = node.parameters |
||||
// for (const i in node.children) {
|
||||
// if (node.children[i].name === 'ParameterList') {
|
||||
// target = node.children[i]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
if (target) { |
||||
const children = target.parameters |
||||
for (const j in children) { |
||||
if (children[j].nodeType === 'VariableDeclaration') { |
||||
params.push(children[j].typeDescriptions.typeString) |
||||
} |
||||
} |
||||
} |
||||
return '(' + params.toString() + ')' |
||||
} |
||||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue