Merge pull request #2774 from ethereum/editorcontext_merge
editor autocomplete/hover/referencespull/5370/head
commit
5df7fb5b93
@ -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,517 @@ |
||||
'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 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,236 @@ |
||||
'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 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,196 @@ |
||||
'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 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,643 @@ |
||||
'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 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.antlrService.getCurrentFileAST() |
||||
await this.compilerService.compile() |
||||
}) |
||||
|
||||
this.on('filePanel', 'setWorkspace', async () => { |
||||
await this.call('fileDecorator', 'clearFileDecorators') |
||||
}) |
||||
|
||||
|
||||
this.on('fileManager', 'currentFileChanged', async () => { |
||||
await this.call('editor', 'discardLineTexts') |
||||
await this.antlrService.getCurrentFileAST() |
||||
await this.compilerService.compile() |
||||
}) |
||||
|
||||
this.on('solidity', 'loadingCompiler', async (url) => { |
||||
this.compilerService.compiler.loadVersion(true, url) |
||||
}) |
||||
|
||||
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' |
@ -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,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() + ')' |
||||
} |
||||
} |
@ -1,4 +0,0 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
@ -1,19 +0,0 @@ |
||||
{ |
||||
"env": { |
||||
"browser": true, |
||||
"es6": true |
||||
}, |
||||
"extends": "../../../.eslintrc", |
||||
"globals": { |
||||
"Atomics": "readonly", |
||||
"SharedArrayBuffer": "readonly" |
||||
}, |
||||
"parserOptions": { |
||||
"ecmaVersion": 11, |
||||
"sourceType": "module" |
||||
}, |
||||
"rules": { |
||||
"no-unused-vars": "off", |
||||
"@typescript-eslint/no-unused-vars": "error" |
||||
} |
||||
} |
@ -1,7 +0,0 @@ |
||||
# remix-ui-editor-context-view |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-editor-context-view` to execute the unit tests via [Jest](https://jestjs.io). |
@ -1 +0,0 @@ |
||||
export * from './lib/remix-ui-editor-context-view' |
@ -1,43 +0,0 @@ |
||||
|
||||
.container-context-view { |
||||
padding : 1px 15px; |
||||
} |
||||
.line { |
||||
display : flex; |
||||
align-items : center; |
||||
text-overflow : ellipsis; |
||||
overflow : hidden; |
||||
white-space : nowrap; |
||||
font-size : 13px; |
||||
} |
||||
.type { |
||||
font-style : italic; |
||||
margin-right : 5px; |
||||
} |
||||
.name { |
||||
font-weight : bold; |
||||
} |
||||
.jump { |
||||
cursor : pointer; |
||||
margin : 0 5px; |
||||
} |
||||
.jump:hover { |
||||
color : var(--secondary); |
||||
} |
||||
.referencesnb { |
||||
float : right; |
||||
margin-left : 15px; |
||||
} |
||||
.gasEstimation { |
||||
margin-right : 15px; |
||||
display : flex; |
||||
align-items : center; |
||||
} |
||||
.gasStationIcon { |
||||
margin-right : 5px; |
||||
} |
||||
.contextviewcontainer { |
||||
z-index : 50; |
||||
border-radius : 1px; |
||||
border : 2px solid var(--secondary); |
||||
} |
@ -1,207 +0,0 @@ |
||||
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
|
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
|
||||
import './remix-ui-editor-context-view.css' |
||||
|
||||
/* eslint-disable-next-line */ |
||||
|
||||
export type astNode = { |
||||
name: string, |
||||
id: number, |
||||
children?: Array<any>, |
||||
typeDescriptions: any, |
||||
nodeType: string, |
||||
src: string // e.g "142:1361:0"
|
||||
} |
||||
|
||||
export type nodePositionLight = { |
||||
file: number, |
||||
length: number, |
||||
start: number |
||||
} |
||||
|
||||
export type astNodeLight = { |
||||
fileTarget: string, |
||||
nodeId: number, |
||||
position: nodePositionLight |
||||
} |
||||
|
||||
export type onContextListenerChangedListener = (nodes: Array<astNode>) => void |
||||
export type ononCurrentFileChangedListener = (name: string) => void |
||||
|
||||
export type gasEstimationType = { |
||||
executionCost: string, |
||||
codeDepositCost: string |
||||
} |
||||
export interface RemixUiEditorContextViewProps { |
||||
hide: boolean, |
||||
gotoLine: (line: number, column: number) => void, |
||||
openFile: (fileName: string) => void, |
||||
getLastCompilationResult: () => any, |
||||
offsetToLineColumn: (position: any, file: any, sources: any, asts: any) => any, |
||||
getCurrentFileName: () => string |
||||
onContextListenerChanged: (listener: onContextListenerChangedListener) => void |
||||
onCurrentFileChanged: (listener: ononCurrentFileChangedListener) => void |
||||
referencesOf: (nodes: astNode) => Array<astNode> |
||||
getActiveHighlights: () => Array<astNodeLight> |
||||
gasEstimation: (node: astNode) => gasEstimationType |
||||
declarationOf: (node: astNode) => astNode |
||||
} |
||||
|
||||
function isDefinition (node: any) { |
||||
return node.nodeType === 'ContractDefinition' || |
||||
node.nodeType === 'FunctionDefinition' || |
||||
node.nodeType === 'ModifierDefinition' || |
||||
node.nodeType === 'VariableDeclaration' || |
||||
node.nodeType === 'StructDefinition' || |
||||
node.nodeType === 'EventDefinition' |
||||
} |
||||
|
||||
type nullableAstNode = astNode | null |
||||
|
||||
export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) { |
||||
const loopOverReferences = useRef(0) |
||||
const currentNodeDeclaration = useRef<nullableAstNode>(null) |
||||
const [state, setState] = useState<{ |
||||
nodes: Array<astNode>, |
||||
activeHighlights: Array<any> |
||||
gasEstimation: gasEstimationType |
||||
}>({ |
||||
nodes: [], |
||||
activeHighlights: [], |
||||
gasEstimation: { executionCost: '', codeDepositCost: '' } |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
props.onCurrentFileChanged(() => { |
||||
currentNodeDeclaration.current = null |
||||
setState(prevState => { |
||||
return { ...prevState, nodes: [], activeHighlights: [] } |
||||
}) |
||||
}) |
||||
|
||||
props.onContextListenerChanged(async (nodes: Array<astNode>) => { |
||||
let nextNodeDeclaration |
||||
let nextNode |
||||
if (!props.hide && nodes && nodes.length) { |
||||
nextNode = nodes[nodes.length - 1] |
||||
if (!isDefinition(nextNode)) { |
||||
nextNodeDeclaration = await props.declarationOf(nextNode) |
||||
} else { |
||||
nextNodeDeclaration = nextNode |
||||
} |
||||
} |
||||
if (nextNodeDeclaration && currentNodeDeclaration.current && nextNodeDeclaration.id === currentNodeDeclaration.current.id) return |
||||
|
||||
currentNodeDeclaration.current = nextNodeDeclaration |
||||
|
||||
let gasEstimation |
||||
if (currentNodeDeclaration.current) { |
||||
if (currentNodeDeclaration.current.nodeType === 'FunctionDefinition') { |
||||
gasEstimation = await props.gasEstimation(currentNodeDeclaration.current) |
||||
} |
||||
} |
||||
const activeHighlights: Array<astNodeLight> = await props.getActiveHighlights() |
||||
if (nextNode && activeHighlights && activeHighlights.length) { |
||||
loopOverReferences.current = activeHighlights.findIndex((el: astNodeLight) => `${el.position.start}:${el.position.length}:${el.position.file}` === nextNode.src) |
||||
loopOverReferences.current = loopOverReferences.current === -1 ? 0 : loopOverReferences.current |
||||
} else { |
||||
loopOverReferences.current = 0 |
||||
} |
||||
setState(prevState => { |
||||
return { ...prevState, nodes, activeHighlights, gasEstimation } |
||||
}) |
||||
}) |
||||
}, []) |
||||
|
||||
/* |
||||
* show gas estimation |
||||
*/ |
||||
const gasEstimation = (node) => { |
||||
if (node.nodeType === 'FunctionDefinition') { |
||||
const result: gasEstimationType = state.gasEstimation |
||||
const executionCost = ' Execution cost: ' + result.executionCost + ' gas' |
||||
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' |
||||
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` |
||||
return ( |
||||
<div className="gasEstimation"> |
||||
<i className="fas fa-gas-pump gasStationIcon" title='Gas estimation'></i> |
||||
<span>{estimatedGas}</span> |
||||
</div> |
||||
) |
||||
} else { |
||||
return (<div></div>) |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* onClick jump to ast node in the editor |
||||
*/ |
||||
const _jumpToInternal = async (position: any) => { |
||||
const jumpToLine = async (fileName: string, lineColumn: any) => { |
||||
if (fileName !== await props.getCurrentFileName()) { |
||||
await props.openFile(fileName) |
||||
} |
||||
if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) { |
||||
props.gotoLine(lineColumn.start.line, lineColumn.end.column + 1) |
||||
} |
||||
} |
||||
const lastCompilationResult = await props.getLastCompilationResult() |
||||
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { |
||||
const lineColumn = await props.offsetToLineColumn( |
||||
position, |
||||
position.file, |
||||
lastCompilationResult.getSourceCode().sources, |
||||
lastCompilationResult.getAsts()) |
||||
const filename = lastCompilationResult.getSourceName(position.file) |
||||
// TODO: refactor with rendererAPI.errorClick
|
||||
jumpToLine(filename, lineColumn) |
||||
} |
||||
} |
||||
|
||||
const _render = () => { |
||||
const node = currentNodeDeclaration.current |
||||
if (!node) return (<div></div>) |
||||
const references = state.activeHighlights |
||||
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType |
||||
const referencesCount = `${references ? references.length : '0'} reference(s)` |
||||
|
||||
const nodes: Array<astNodeLight> = state.activeHighlights |
||||
|
||||
const jumpTo = () => { |
||||
if (node && node.src) { |
||||
const position = sourceMappingDecoder.decode(node.src) |
||||
if (position) { |
||||
_jumpToInternal(position) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// JUMP BETWEEN REFERENCES
|
||||
const jump = (e: any) => { |
||||
e.target.dataset.action === 'next' ? loopOverReferences.current++ : loopOverReferences.current-- |
||||
if (loopOverReferences.current < 0) loopOverReferences.current = nodes.length - 1 |
||||
if (loopOverReferences.current >= nodes.length) loopOverReferences.current = 0 |
||||
_jumpToInternal(nodes[loopOverReferences.current].position) |
||||
} |
||||
|
||||
return ( |
||||
<div className="line">{gasEstimation(node)} |
||||
<div title={type} className="type">{type}</div> |
||||
<div title={node.name} className="name mr-2">{node.name}</div> |
||||
<i className="fas fa-share jump" data-action='gotoref' aria-hidden="true" onClick={jumpTo}></i> |
||||
<span className="referencesnb">{referencesCount}</span> |
||||
<i data-action='previous' className="fas fa-chevron-up jump" aria-hidden="true" onClick={jump}></i> |
||||
<i data-action='next' className="fas fa-chevron-down jump" aria-hidden="true" onClick={jump}></i> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
!props.hide && <div className="container-context-view contextviewcontainer bg-light text-dark border-0 py-1"> |
||||
{_render()} |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default RemixUiEditorContextView |
@ -1,16 +0,0 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -1,13 +0,0 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
@ -0,0 +1,662 @@ |
||||
import { IRange } from "monaco-editor"; |
||||
import monaco from "../../../types/monaco"; |
||||
|
||||
export function getStringCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: 'concatenate an arbitrary number of string values', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'concat(${1:string})', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'concat()', |
||||
range, |
||||
}, |
||||
] |
||||
} |
||||
|
||||
export function getBytesCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: 'concatenate an arbitrary number of values', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'concat(${1:bytes})', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'concat()', |
||||
range, |
||||
}, |
||||
] |
||||
} |
||||
|
||||
|
||||
export function getBlockCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: '(address): Current block miner’s address', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'coinbase', |
||||
label: 'coinbase', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '(uint): Current block’s base fee', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'basefee', |
||||
label: 'basefee', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '(uint): Current chain id', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'chainid', |
||||
label: 'chainid', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '(bytes32): DEPRICATED In 0.4.22 use blockhash(uint) instead. Hash of the given block - only works for 256 most recent blocks excluding current', |
||||
insertText: 'blockhash(${1:blockNumber});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'blockhash', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(uint): current block difficulty', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
label: 'difficulty', |
||||
insertText: 'difficulty', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(uint): current block gaslimit', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
label: 'gaslimit', |
||||
insertText: 'gaslimit', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(uint): current block number', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
label: 'number', |
||||
insertText: 'number', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(uint): current block timestamp as seconds since unix epoch', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
label: 'timestamp', |
||||
insertText: 'timestamp', |
||||
range |
||||
}, |
||||
]; |
||||
} |
||||
|
||||
export function getCompletionSnippets(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
label: 'contract', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'contract ${1:Name} {\n\t$0\n}', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'library', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'library ${1:Name} {\n\t$0\n}', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'interface', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'interface ${1:Name} {\n\t$0\n}', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'enum', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'enum ${1:Name} {${2:item1}, ${3:item2} }', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'function', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'function ${1:name}(${2:params}) {\n\t${3:code}\n}', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'constructor', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'constructor(${1:params}) {\n\t${2:code}\n}', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'ifstatement', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'if (${1:condition}) {\n\t${2:code}\n}', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'ifstatementelse', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'if (${1:condition}) {\n\t${2:code}\n} else {\n\t${3:code}\n}', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'pragma', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: '// SPDX-License-Identifier: MIT\npragma solidity ${1:version};', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'import', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: 'import "${1:library}";', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
}, |
||||
{ |
||||
label: 'SPDX-License-Identifier', |
||||
kind: monaco.languages.CompletionItemKind.Snippet, |
||||
insertText: '// SPDX-License-Identifier: MIT', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range |
||||
} |
||||
] |
||||
} |
||||
|
||||
export function getTxCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: '(uint): gas price of the transaction', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'gas', |
||||
label: 'gas', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(address): sender of the transaction (full call chain)', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'origin', |
||||
label: 'origin', |
||||
range |
||||
}, |
||||
]; |
||||
} |
||||
|
||||
export function getMsgCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: '(bytes): complete calldata', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'data', |
||||
label: 'data', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(uint): remaining gas DEPRICATED in 0.4.21 use gasleft()', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'gas', |
||||
label: 'gas', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(address): sender of the message (current call)', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'sender', |
||||
label: 'sender', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(bytes4): first four bytes of the calldata (i.e. export function identifier)', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'sig', |
||||
label: 'sig', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(uint): number of wei sent with the message', |
||||
kind: monaco.languages.CompletionItemKind.Property, |
||||
insertText: 'value', |
||||
label: 'value', |
||||
range |
||||
}, |
||||
]; |
||||
} |
||||
|
||||
export function getAbiCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: 'encode(..) returs (bytes): ABI-encodes the given arguments', |
||||
insertText: 'encode(${1:arg});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'encode', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'encodeCall(function functionPointer, (...)) returns (bytes memory) ABI-encodes a call to functionPointer with the arguments found in the tuple', |
||||
insertText: 'encode(${1:arg});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'encodecall', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'encodePacked(..) returns (bytes): Performes packed encoding of the given arguments', |
||||
insertText: 'encodePacked(${1:arg});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'encodePacked', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'encodeWithSelector(bytes4,...) returns (bytes): ABI-encodes the given arguments starting from the second and prepends the given four-byte selector', |
||||
insertText: 'encodeWithSelector(${1:bytes4}, ${2:arg});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'encodeWithSelector', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'encodeWithSignature(string,...) returns (bytes): Equivalent to abi.encodeWithSelector(bytes4(keccak256(signature), ...)`', |
||||
insertText: 'encodeWithSignature(${1:signatureString}, ${2:arg});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'encodeWithSignature', |
||||
range |
||||
}, |
||||
]; |
||||
} |
||||
|
||||
|
||||
export function GetCompletionTypes(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
const completionItems = []; |
||||
const types = ['address', 'string', 'bytes', 'byte', 'int', 'uint', 'bool', 'hash']; |
||||
for (let index = 8; index <= 256; index += 8) { |
||||
types.push('int' + index); |
||||
types.push('uint' + index); |
||||
types.push('bytes' + index / 8); |
||||
} |
||||
types.forEach(type => { |
||||
const completionItem = CreateCompletionItem(type, monaco.languages.CompletionItemKind.Keyword, type + ' type', range); |
||||
completionItems.push(completionItem); |
||||
}); |
||||
// add mapping
|
||||
return completionItems; |
||||
} |
||||
|
||||
function CreateCompletionItem(label: string, kind: monaco.languages.CompletionItemKind, detail: string, range: IRange) { |
||||
const completionItem: monaco.languages.CompletionItem = { |
||||
label, |
||||
kind, |
||||
detail, |
||||
insertText: label, |
||||
range |
||||
} |
||||
completionItem.kind = kind; |
||||
completionItem.detail = detail; |
||||
return completionItem; |
||||
} |
||||
|
||||
export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
const completionItems = []; |
||||
const keywords = ['modifier', 'mapping', 'break', 'continue', 'delete', 'else', 'for', |
||||
'after', 'promise', 'alias', 'apply','auto', 'copyof', 'default', 'define', 'final', 'implements', |
||||
'inline', 'let', 'macro', 'match', 'mutable', 'null', 'of', 'partial', 'reference', 'relocatable', |
||||
'sealed', 'sizeof', 'static', 'supports', 'switch', 'typedef', |
||||
'if', 'new', 'return', 'returns', 'while', 'using', 'emit', 'anonymous', 'indexed',
|
||||
'private', 'public', 'external', 'internal', 'payable', 'nonpayable', 'view', 'pure', 'case', 'do', 'else', 'finally', |
||||
'in', 'instanceof', 'return', 'throw', 'try', 'catch', 'typeof', 'yield', 'void', 'virtual', 'override']; |
||||
keywords.forEach(unit => { |
||||
const completionItem: monaco.languages.CompletionItem = { |
||||
label: unit, |
||||
kind: monaco.languages.CompletionItemKind.Keyword, |
||||
detail: unit + ' keyword', |
||||
insertText: `${unit} `, |
||||
range |
||||
} |
||||
completionItems.push(completionItem); |
||||
}); |
||||
|
||||
completionItems.push(CreateCompletionItem('contract', monaco.languages.CompletionItemKind.Class, null, range)); |
||||
completionItems.push(CreateCompletionItem('library', monaco.languages.CompletionItemKind.Class, null, range)); |
||||
completionItems.push(CreateCompletionItem('storage', monaco.languages.CompletionItemKind.Field, null, range)); |
||||
completionItems.push(CreateCompletionItem('calldata', monaco.languages.CompletionItemKind.Field, null, range)); |
||||
completionItems.push(CreateCompletionItem('memory', monaco.languages.CompletionItemKind.Field, null, range)); |
||||
completionItems.push(CreateCompletionItem('var', monaco.languages.CompletionItemKind.Field, null, range)); |
||||
completionItems.push(CreateCompletionItem('constant', monaco.languages.CompletionItemKind.Constant, null, range)); |
||||
completionItems.push(CreateCompletionItem('immutable', monaco.languages.CompletionItemKind.Keyword, null, range)); |
||||
completionItems.push(CreateCompletionItem('constructor', monaco.languages.CompletionItemKind.Constructor, null, range)); |
||||
completionItems.push(CreateCompletionItem('event', monaco.languages.CompletionItemKind.Event, null, range)); |
||||
completionItems.push(CreateCompletionItem('import', monaco.languages.CompletionItemKind.Module, null, range)); |
||||
completionItems.push(CreateCompletionItem('enum', monaco.languages.CompletionItemKind.Enum, null, range)); |
||||
completionItems.push(CreateCompletionItem('struct', monaco.languages.CompletionItemKind.Struct, null, range)); |
||||
completionItems.push(CreateCompletionItem('function', monaco.languages.CompletionItemKind.Function, null, range)); |
||||
|
||||
return completionItems; |
||||
} |
||||
|
||||
|
||||
export function GeCompletionUnits(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
const completionItems = []; |
||||
const etherUnits = ['wei', 'gwei', 'finney', 'szabo', 'ether']; |
||||
etherUnits.forEach(unit => { |
||||
const completionItem = CreateCompletionItem(unit, monaco.languages.CompletionItemKind.Unit, unit + ': ether unit', range); |
||||
completionItems.push(completionItem); |
||||
}); |
||||
|
||||
const timeUnits = ['seconds', 'minutes', 'hours', 'days', 'weeks', 'years']; |
||||
timeUnits.forEach(unit => { |
||||
const completionItem = CreateCompletionItem(unit, monaco.languages.CompletionItemKind.Unit, unit + ': time unit', range); |
||||
completionItem.kind = monaco.languages.CompletionItemKind.Unit; |
||||
|
||||
if (unit !== 'years') { |
||||
completionItem.detail = unit + ': time unit'; |
||||
} else { |
||||
completionItem.detail = 'DEPRECATED: ' + unit + ': time unit'; |
||||
} |
||||
completionItems.push(completionItem); |
||||
}); |
||||
|
||||
return completionItems; |
||||
} |
||||
|
||||
export function GetGlobalVariable(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: 'Current block', |
||||
kind: monaco.languages.CompletionItemKind.Variable, |
||||
insertText: 'block', |
||||
label: 'block', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'Current Message', |
||||
kind: monaco.languages.CompletionItemKind.Variable, |
||||
insertText: 'msg', |
||||
label: 'msg', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '(uint): current block timestamp (alias for block.timestamp)', |
||||
kind: monaco.languages.CompletionItemKind.Variable, |
||||
insertText: 'now', |
||||
label: 'now', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'Current transaction', |
||||
kind: monaco.languages.CompletionItemKind.Variable, |
||||
label: 'tx', |
||||
insertText: 'tx', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'ABI encoding / decoding', |
||||
kind: monaco.languages.CompletionItemKind.Variable, |
||||
label: 'abi', |
||||
insertText: 'abi', |
||||
range |
||||
}, |
||||
{ |
||||
detail: '', |
||||
kind: monaco.languages.CompletionItemKind.Variable, |
||||
label: 'this', |
||||
insertText: 'this', |
||||
range |
||||
}, |
||||
]; |
||||
} |
||||
|
||||
export function GetGlobalFunctions(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: 'assert(bool condition): throws if the condition is not met - to be used for internal errors.', |
||||
insertText: 'assert(${1:condition});', |
||||
kind: monaco.languages.CompletionItemKind.Function, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'assert', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'gasleft(): returns the remaining gas', |
||||
insertText: 'gasleft();', |
||||
kind: monaco.languages.CompletionItemKind.Function, |
||||
label: 'gasleft', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'unicode: converts string into unicode', |
||||
insertText: 'unicode"${1:text}"', |
||||
kind: monaco.languages.CompletionItemKind.Function, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'unicode', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'blockhash(uint blockNumber): hash of the given block - only works for 256 most recent, excluding current, blocks', |
||||
insertText: 'blockhash(${1:blockNumber});', |
||||
kind: monaco.languages.CompletionItemKind.Function, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'blockhash', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'require(bool condition): reverts if the condition is not met - to be used for errors in inputs or external components.', |
||||
insertText: 'require(${1:condition});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'require', |
||||
range |
||||
}, |
||||
{ |
||||
// tslint:disable-next-line:max-line-length
|
||||
detail: 'require(bool condition, string message): reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.', |
||||
insertText: 'require(${1:condition}, ${2:message});', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'require', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'revert(): abort execution and revert state changes', |
||||
insertText: 'revert();', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
label: 'revert', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'addmod(uint x, uint y, uint k) returns (uint):' + |
||||
'compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256', |
||||
insertText: 'addmod(${1:x}, ${2:y}, ${3:k})', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'addmod', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'mulmod(uint x, uint y, uint k) returns (uint):' + |
||||
'compute (x * y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2**256', |
||||
insertText: 'mulmod(${1:x}, ${2:y}, ${3:k})', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'mulmod', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'keccak256(...) returns (bytes32):' + |
||||
'compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments', |
||||
insertText: 'keccak256(${1:x})', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'keccak256', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'sha256(...) returns (bytes32):' + |
||||
'compute the SHA-256 hash of the (tightly packed) arguments', |
||||
insertText: 'sha256(${1:x})', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
label: 'sha256', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'sha3(...) returns (bytes32):' + |
||||
'alias to keccak256', |
||||
insertText: 'sha3(${1:x})', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
label: 'sha3', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'ripemd160(...) returns (bytes20):' + |
||||
'compute RIPEMD-160 hash of the (tightly packed) arguments', |
||||
insertText: 'ripemd160(${1:x})', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
label: 'ripemd160', |
||||
range |
||||
}, |
||||
{ |
||||
detail: 'ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):' + |
||||
'recover the address associated with the public key from elliptic curve signature or return zero on error', |
||||
insertText: 'ecrecover(${1:hash}, ${2:v}, ${3:r}, ${4:s})', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
label: 'ecrecover', |
||||
range |
||||
}, |
||||
|
||||
]; |
||||
} |
||||
|
||||
export function getContextualAutoCompleteByGlobalVariable(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
if (word === 'block') { |
||||
return getBlockCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'string') { |
||||
return getStringCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'bytes') { |
||||
return getBytesCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'msg') { |
||||
return getMsgCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'tx') { |
||||
return getTxCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'abi') { |
||||
return getAbiCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'sender') { |
||||
return getAddressCompletionItems(range, monaco); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
export function getArrayCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: '', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'length;', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'length', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'push(${1:value});', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'push(value)', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'push();', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'push()', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'pop();', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'pop()', |
||||
range, |
||||
}, |
||||
] |
||||
} |
||||
|
||||
export function getAddressCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
return [ |
||||
{ |
||||
detail: '(uint256): balance of the Address in Wei', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'balance;', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'balance', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '(bytes memory): code at the Address (can be empty)', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'code;', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'code', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '(bytes32): the codehash of the Address', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'codehash;', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'codehash', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '(uint256 amount) returns (bool): send given amount of Wei to Address, returns false on failure', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'send(${1:value});', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'send()', |
||||
range, |
||||
}, |
||||
{ |
||||
detail: '(uint256 amount): send given amount of Wei to Address, throws on failure', |
||||
kind: monaco.languages.CompletionItemKind.Method, |
||||
insertText: 'transfer(${1:value});', |
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
label: 'transfer()', |
||||
range, |
||||
}, |
||||
] |
||||
|
||||
} |
||||
|
||||
export function getContextualAutoCompleteBTypeName(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] { |
||||
if (word === 'ArrayTypeName') { |
||||
return getArrayCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'bytes') { |
||||
return getBytesCompletionItems(range, monaco); |
||||
} |
||||
if (word === 'address') { |
||||
return getAddressCompletionItems(range, monaco); |
||||
} |
||||
return []; |
||||
} |
@ -0,0 +1,457 @@ |
||||
import { sourceMappingDecoder } from "@remix-project/remix-debug" |
||||
import { AstNode } from "@remix-project/remix-solidity-ts" |
||||
import { isArray } from "lodash" |
||||
import { editor, languages, Position } from "monaco-editor" |
||||
import monaco from "../../types/monaco" |
||||
import { EditorUIProps } from "../remix-ui-editor" |
||||
import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable } from "./completion/completionGlobals" |
||||
|
||||
export class RemixCompletionProvider implements languages.CompletionItemProvider { |
||||
|
||||
props: EditorUIProps |
||||
monaco: any |
||||
|
||||
constructor(props: any, monaco: any) { |
||||
this.props = props |
||||
this.monaco = monaco |
||||
} |
||||
|
||||
triggerCharacters = ['.', ''] |
||||
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> { |
||||
|
||||
const completionSettings = await this.props.plugin.call('config', 'getAppParameter', 'settings/auto-completion') |
||||
if(!completionSettings) return |
||||
const word = model.getWordUntilPosition(position); |
||||
const range = { |
||||
startLineNumber: position.lineNumber, |
||||
endLineNumber: position.lineNumber, |
||||
startColumn: word.startColumn, |
||||
endColumn: word.endColumn |
||||
}; |
||||
|
||||
const line = model.getLineContent(position.lineNumber) |
||||
let nodes: AstNode[] = [] |
||||
let suggestions: monaco.languages.CompletionItem[] = [] |
||||
|
||||
if (context.triggerCharacter === '.') { |
||||
const lineTextBeforeCursor: string = line.substring(0, position.column - 1) |
||||
const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) |
||||
const expressionElements = lineTextBeforeCursor.split('.') |
||||
|
||||
let dotCompleted = false |
||||
|
||||
// handles completion from for builtin types
|
||||
if(lastNodeInExpression.memberName === 'sender') { // exception for this member
|
||||
lastNodeInExpression.name = 'sender' |
||||
} |
||||
const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) |
||||
if (globalCompletion) { |
||||
dotCompleted = true |
||||
suggestions = [...suggestions, ...globalCompletion] |
||||
setTimeout(() => { |
||||
// eslint-disable-next-line no-debugger
|
||||
// debugger
|
||||
}, 2000) |
||||
} |
||||
// handle completion for global THIS.
|
||||
if (lastNodeInExpression.name === 'this') { |
||||
dotCompleted = true |
||||
nodes = [...nodes, ...await this.getThisCompletions(position)] |
||||
} |
||||
// handle completion for other dot completions
|
||||
if (expressionElements.length > 1 && !dotCompleted) { |
||||
|
||||
const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName |
||||
const dotCompletions = await this.getDotCompletions(position, nameOfLastTypedExpression, range) |
||||
nodes = [...nodes, ...dotCompletions.nodes] |
||||
suggestions = [...suggestions, ...dotCompletions.suggestions] |
||||
} |
||||
} else { |
||||
|
||||
// handles contract completions and other suggestions
|
||||
suggestions = [...suggestions, |
||||
...GetGlobalVariable(range, this.monaco), |
||||
...getCompletionSnippets(range, this.monaco), |
||||
...GetCompletionTypes(range, this.monaco), |
||||
...GetCompletionKeywords(range, this.monaco), |
||||
...GetGlobalFunctions(range, this.monaco), |
||||
...GeCompletionUnits(range, this.monaco), |
||||
] |
||||
let contractCompletions = await this.getContractCompletions(position) |
||||
|
||||
// we can't have external nodes without using this.
|
||||
contractCompletions = contractCompletions.filter(node => { |
||||
if (node.visibility && node.visibility === 'external') { |
||||
return false |
||||
} |
||||
return true |
||||
}) |
||||
nodes = [...nodes, ...contractCompletions] |
||||
|
||||
} |
||||
|
||||
// remove duplicates
|
||||
const nodeIds = {}; |
||||
const filteredNodes = nodes.filter((node) => { |
||||
if (node.id) { |
||||
if (nodeIds[node.id]) { |
||||
return false; |
||||
} |
||||
nodeIds[node.id] = true; |
||||
} |
||||
return true; |
||||
}); |
||||
|
||||
const getNodeLink = async (node: any) => { |
||||
return await this.props.plugin.call('codeParser', 'getNodeLink', node) |
||||
} |
||||
|
||||
const getDocs = async (node: any) => { |
||||
return await this.props.plugin.call('codeParser', 'getNodeDocumentation', node) |
||||
} |
||||
|
||||
const getParamaters = async (node: any) => { |
||||
return await this.props.plugin.call('codeParser', 'getFunctionParamaters', node) |
||||
} |
||||
|
||||
const completeParameters = async (parameters: any) => { |
||||
const localParam = (parameters && parameters.parameters) || (parameters) |
||||
if (localParam) { |
||||
const params = [] |
||||
for (const key in localParam) { |
||||
params.push('${' + (key + 1) + ':' + localParam[key].name + '}') |
||||
} |
||||
return `(${params.join(', ')})` |
||||
} |
||||
} |
||||
|
||||
const getVariableDeclaration = async (node: any) => { |
||||
let variableDeclaration = await this.props.plugin.call('codeParser', 'getVariableDeclaration', node) |
||||
if (node.scope) { |
||||
const scopeNode = await this.props.plugin.call('codeParser', 'getNodeById', node.scope) |
||||
if (scopeNode) { |
||||
variableDeclaration = `${scopeNode.name}.${variableDeclaration}` |
||||
} |
||||
} |
||||
return variableDeclaration |
||||
} |
||||
|
||||
|
||||
for (const node of Object.values(filteredNodes) as any[]) { |
||||
if (!node.name) continue |
||||
if (node.nodeType === 'VariableDeclaration') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${await getVariableDeclaration(node)}` }, |
||||
kind: this.monaco.languages.CompletionItemKind.Variable, |
||||
insertText: node.name, |
||||
range: range, |
||||
documentation: await getDocs(node) |
||||
} |
||||
suggestions.push(completion) |
||||
} else if (node.nodeType === 'FunctionDefinition') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` }, |
||||
kind: this.monaco.languages.CompletionItemKind.Function, |
||||
insertText: `${node.name}${await completeParameters(node.parameters)};`, |
||||
insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range: range, |
||||
documentation: await getDocs(node) |
||||
} |
||||
suggestions.push(completion) |
||||
} else if |
||||
(node.nodeType === 'ContractDefinition') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` }, |
||||
kind: this.monaco.languages.CompletionItemKind.Interface, |
||||
insertText: node.name, |
||||
range: range, |
||||
documentation: await getDocs(node) |
||||
} |
||||
suggestions.push(completion) |
||||
} else if |
||||
(node.nodeType === 'StructDefinition') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` }, |
||||
kind: this.monaco.languages.CompletionItemKind.Struct, |
||||
insertText: node.name, |
||||
range: range, |
||||
documentation: await getDocs(node) |
||||
} |
||||
suggestions.push(completion) |
||||
} else if |
||||
(node.nodeType === 'EnumDefinition') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` }, |
||||
kind: this.monaco.languages.CompletionItemKind.Enum, |
||||
insertText: node.name, |
||||
range: range, |
||||
documentation: await getDocs(node) |
||||
} |
||||
suggestions.push(completion) |
||||
} else if |
||||
(node.nodeType === 'EventDefinition') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` }, |
||||
kind: this.monaco.languages.CompletionItemKind.Event, |
||||
insertText: `${node.name}${await completeParameters(node.parameters)};`, |
||||
insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
||||
range: range, |
||||
documentation: await getDocs(node) |
||||
} |
||||
suggestions.push(completion) |
||||
} else if |
||||
(node.nodeType === 'ModifierDefinition') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` }, |
||||
kind: this.monaco.languages.CompletionItemKind.Method, |
||||
insertText: node.name, |
||||
range: range, |
||||
documentation: await getDocs(node) |
||||
} |
||||
suggestions.push(completion) |
||||
} else if |
||||
(node.nodeType === 'EnumValue' || node.type === 'EnumValue') { |
||||
const completion = { |
||||
label: { label: `"${node.name}"` }, |
||||
kind: this.monaco.languages.CompletionItemKind.EnumMember, |
||||
insertText: node.name, |
||||
range: range, |
||||
documentation: null |
||||
} |
||||
suggestions.push(completion) |
||||
|
||||
}
|
||||
} |
||||
|
||||
return { |
||||
suggestions |
||||
} |
||||
} |
||||
|
||||
private getBlockNodesAtPosition = async (position: Position) => { |
||||
let nodes: any[] = [] |
||||
const cursorPosition = this.props.editorAPI.getCursorPosition() |
||||
const nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition) |
||||
// try to get the block from ANTLR of which the position is in
|
||||
const ANTLRBlock = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null) |
||||
// if the block has a name and a type we can maybe find it in the contract nodes
|
||||
const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes') |
||||
|
||||
if (isArray(nodesAtPosition) && nodesAtPosition.length) { |
||||
for (const node of nodesAtPosition) { |
||||
// try to find the real block in the AST and get the nodes in that scope
|
||||
if (node.nodeType === 'ContractDefinition') { |
||||
const contractNodes = fileNodes.contracts[node.name].contractNodes |
||||
for (const contractNode of Object.values(contractNodes)) { |
||||
if (contractNode['name'] === ANTLRBlock.name) { |
||||
let nodeOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', (contractNode as any).id) |
||||
nodes = [...nodes, ...nodeOfScope] |
||||
if (contractNode['body']) { |
||||
nodeOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', (contractNode['body'] as any).id) |
||||
nodes = [...nodes, ...nodeOfScope] |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// blocks can have statements
|
||||
/* |
||||
if (node.statements){ |
||||
console.log('statements', node.statements) |
||||
for (const statement of node.statements) { |
||||
if(statement.expression){ |
||||
const declaration = await this.props.plugin.call('codeParser', 'declarationOf', statement.expression) |
||||
declaration.outSideBlock = true |
||||
nodes = [...nodes, declaration] |
||||
} |
||||
} |
||||
} |
||||
*/ |
||||
} |
||||
} |
||||
|
||||
// we are only interested in nodes that are in the same block as the cursor
|
||||
nodes = nodes.filter(node => { |
||||
if (node.src) { |
||||
const position = sourceMappingDecoder.decode(node.src) |
||||
if (position.start >= ANTLRBlock.range[0] && (position.start + position.length) <= ANTLRBlock.range[1]) { |
||||
return true |
||||
} |
||||
} |
||||
if(node.outSideBlock){ return true } |
||||
return false |
||||
}) |
||||
|
||||
return nodes; |
||||
} |
||||
|
||||
private getContractCompletions = async (position: Position) => { |
||||
let nodes: any[] = [] |
||||
const cursorPosition = this.props.editorAPI.getCursorPosition() |
||||
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition) |
||||
// if no nodes exits at position, try to get the block of which the position is in
|
||||
const block = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null) |
||||
if (!nodesAtPosition.length) { |
||||
if (block) { |
||||
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.range[0]) |
||||
} |
||||
} |
||||
// find the contract and get the nodes of the contract and the base contracts and imports
|
||||
if (isArray(nodesAtPosition) && nodesAtPosition.length) { |
||||
let contractNode: any = {} |
||||
for (const node of nodesAtPosition) { |
||||
if (node.nodeType === 'ContractDefinition') { |
||||
contractNode = node |
||||
const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes') |
||||
const contractNodes = fileNodes.contracts[node.name] |
||||
nodes = [...Object.values(contractNodes.contractScopeNodes), ...nodes] |
||||
nodes = [...Object.values(contractNodes.baseNodesWithBaseContractScope), ...nodes] |
||||
nodes = [...Object.values(fileNodes.imports), ...nodes] |
||||
// at the nodes at the block itself
|
||||
nodes = [...nodes, ...await this.getBlockNodesAtPosition(position)] |
||||
// filter private nodes, only allow them when contract ID is the same as the current contract
|
||||
nodes = nodes.filter(node => { |
||||
if (node.visibility) { |
||||
if (node.visibility === 'private') { |
||||
return (node.contractId ? node.contractId === contractNode.id : false) || false |
||||
} |
||||
} |
||||
return true |
||||
}) |
||||
break; |
||||
} |
||||
|
||||
} |
||||
} else { |
||||
// get all the nodes from a simple code parser which only parses the current file
|
||||
nodes = [...nodes, ...await this.props.plugin.call('codeParser', 'listAstNodes')] |
||||
} |
||||
return nodes |
||||
} |
||||
|
||||
private getThisCompletions = async (position: Position) => { |
||||
let nodes: any[] = [] |
||||
let thisCompletionNodes = await this.getContractCompletions(position) |
||||
const allowedTypesForThisCompletion = ['VariableDeclaration', 'FunctionDefinition'] |
||||
// with this. you can't have internal nodes and no contractDefinitions
|
||||
thisCompletionNodes = thisCompletionNodes.filter(node => { |
||||
if (node.visibility && (node.visibility === 'internal' || node.visibility === 'private')) { |
||||
return false |
||||
} |
||||
if (node.nodeType && !allowedTypesForThisCompletion.includes(node.nodeType)) { |
||||
return false |
||||
} |
||||
return true |
||||
}) |
||||
nodes = [...nodes, ...thisCompletionNodes] |
||||
setTimeout(() => { |
||||
// eslint-disable-next-line no-debugger
|
||||
// debugger
|
||||
}, 2000) |
||||
return nodes |
||||
} |
||||
|
||||
private getDotCompletions = async (position: Position, nameOfLastTypedExpression: string, range) => { |
||||
const contractCompletions = await this.getContractCompletions(position) |
||||
let nodes: any[] = [] |
||||
let suggestions: monaco.languages.CompletionItem[] = [] |
||||
|
||||
const filterNodes = (nodes: any[], parentNode: any, declarationOf: any = null) => { |
||||
return nodes && nodes.filter(node => { |
||||
if (node.visibility) { |
||||
if (declarationOf && declarationOf.nodeType && declarationOf.nodeType === 'StructDefinition') { |
||||
return true |
||||
} |
||||
if ((node.visibility === 'internal' && !parentNode.isBaseNode) || node.visibility === 'private') { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
}) |
||||
} |
||||
|
||||
|
||||
for (const nodeOfScope of contractCompletions) { |
||||
if (nodeOfScope.name === nameOfLastTypedExpression) { |
||||
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') { |
||||
const declarationOf: AstNode = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName) |
||||
nodes = [...nodes, |
||||
...filterNodes(declarationOf.nodes, nodeOfScope, declarationOf) |
||||
|| filterNodes(declarationOf.members, nodeOfScope, declarationOf)] |
||||
const baseContracts = await this.getlinearizedBaseContracts(declarationOf) |
||||
for (const baseContract of baseContracts) { |
||||
nodes = [...nodes, ...filterNodes(baseContract.nodes, nodeOfScope)] |
||||
} |
||||
} else if (nodeOfScope.members) { |
||||
nodes = [...nodes, ...filterNodes(nodeOfScope.members, nodeOfScope)] |
||||
} else if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ArrayTypeName') { |
||||
suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('ArrayTypeName', range, this.monaco)] |
||||
} else if(nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ElementaryTypeName' && nodeOfScope.typeName.name === 'bytes') { |
||||
suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('bytes', range, this.monaco)] |
||||
} else if(nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ElementaryTypeName' && nodeOfScope.typeName.name === 'address') { |
||||
suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('address', range, this.monaco)] |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
return { nodes, suggestions } |
||||
} |
||||
|
||||
private getlinearizedBaseContracts = async (node: any) => { |
||||
let params = [] |
||||
if (node.linearizedBaseContracts) { |
||||
for (const id of node.linearizedBaseContracts) { |
||||
if (id !== node.id) { |
||||
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id) |
||||
params = [...params, ...[baseContract]] |
||||
} |
||||
} |
||||
} |
||||
return params |
||||
} |
||||
|
||||
/** |
||||
*
|
||||
* @param lineTextBeforeCursor
|
||||
* @returns
|
||||
*/ |
||||
private async getLastNodeInExpression(lineTextBeforeCursor: string) { |
||||
|
||||
const wrapLineInFunction = async (text: string) => { |
||||
return `function() {
|
||||
${text} |
||||
}` |
||||
} |
||||
|
||||
let lastNodeInExpression |
||||
|
||||
const linesToCheck = |
||||
[ |
||||
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;", |
||||
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;}", |
||||
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode);", |
||||
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;"), |
||||
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;}"), |
||||
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;)"), |
||||
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode)"), |
||||
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode);"), |
||||
] |
||||
|
||||
for (const line of linesToCheck) { |
||||
try { |
||||
const lineAst = await this.props.plugin.call('codeParser', 'parseSolidity', line) |
||||
const lastNode = await this.props.plugin.call('codeParser', 'getLastNodeInLine', lineAst) |
||||
if (lastNode) { |
||||
lastNodeInExpression = lastNode |
||||
break |
||||
} |
||||
|
||||
} catch (e) { |
||||
|
||||
} |
||||
} |
||||
return lastNodeInExpression |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
import { Monaco } from "@monaco-editor/react" |
||||
import monaco from "../../types/monaco" |
||||
import { EditorUIProps } from "../remix-ui-editor" |
||||
|
||||
export class RemixDefinitionProvider implements monaco.languages.DefinitionProvider { |
||||
props: EditorUIProps |
||||
monaco: Monaco |
||||
constructor(props: any, monaco: any) { |
||||
this.props = props |
||||
this.monaco = monaco |
||||
} |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async provideDefinition(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise<monaco.languages.Definition | monaco.languages.LocationLink[]> { |
||||
const cursorPosition = this.props.editorAPI.getCursorPosition() |
||||
await this.jumpToDefinition(cursorPosition) |
||||
return null |
||||
} |
||||
|
||||
async jumpToDefinition(position: any) { |
||||
const node = await this.props.plugin.call('codeParser', 'definitionAtPosition', position) |
||||
const sourcePosition = await this.props.plugin.call('codeParser', 'positionOfDefinition', node) |
||||
if (sourcePosition) { |
||||
await this.jumpToPosition(sourcePosition) |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* onClick jump to position of ast node in the editor |
||||
*/ |
||||
async jumpToPosition(position: any) { |
||||
const jumpToLine = async (fileName: string, lineColumn: any) => { |
||||
if (fileName !== await this.props.plugin.call('fileManager', 'file')) { |
||||
await this.props.plugin.call('contentImport', 'resolveAndSave', fileName, null) |
||||
await this.props.plugin.call('fileManager', 'open', fileName) |
||||
} |
||||
if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) { |
||||
this.props.plugin.call('editor', 'gotoLine', lineColumn.start.line, lineColumn.end.column + 1) |
||||
} |
||||
} |
||||
const lastCompilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult') // await this.props.plugin.call('compilerArtefacts', 'getLastCompilationResult')
|
||||
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { |
||||
|
||||
const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position) |
||||
const filename = lastCompilationResult.getSourceName(position.file) |
||||
jumpToLine(filename, lineColumn) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
import { Monaco } from "@monaco-editor/react" |
||||
import { sourceMappingDecoder } from "@remix-project/remix-debug" |
||||
import monaco from "../../types/monaco" |
||||
import { EditorUIProps } from "../remix-ui-editor" |
||||
|
||||
export class RemixHighLightProvider implements monaco.languages.DocumentHighlightProvider { |
||||
props: EditorUIProps |
||||
monaco: Monaco |
||||
constructor(props: any, monaco: any) { |
||||
this.props = props |
||||
this.monaco = monaco |
||||
} |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async provideDocumentHighlights(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise<monaco.languages.DocumentHighlight[]> { |
||||
const cursorPosition = this.props.editorAPI.getCursorPosition() |
||||
const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition) |
||||
const highlights: monaco.languages.DocumentHighlight[] = [] |
||||
if (nodes && nodes.length) { |
||||
const compilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult') |
||||
const file = await this.props.plugin.call('fileManager', 'file') |
||||
if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) { |
||||
for (const node of nodes) { |
||||
const position = sourceMappingDecoder.decode(node.src) |
||||
const fileInNode = compilationResult.getSourceName(position.file) |
||||
if (fileInNode === file) { |
||||
const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position) |
||||
const range = new this.monaco.Range(lineColumn.start.line + 1, lineColumn.start.column + 1, lineColumn.end.line + 1, lineColumn.end.column + 1) |
||||
highlights.push({ |
||||
range, |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return highlights |
||||
} |
||||
} |
@ -0,0 +1,166 @@ |
||||
|
||||
import { Monaco } from '@monaco-editor/react' |
||||
import { editor, languages, Position } from 'monaco-editor' |
||||
import { EditorUIProps } from '../remix-ui-editor' |
||||
export class RemixHoverProvider implements languages.HoverProvider { |
||||
|
||||
props: EditorUIProps |
||||
monaco: Monaco |
||||
constructor(props: any, monaco: any) { |
||||
this.props = props |
||||
this.monaco = monaco |
||||
} |
||||
|
||||
provideHover = async function (model: editor.ITextModel, position: Position): Promise<languages.Hover> { |
||||
const cursorPosition = this.props.editorAPI.getHoverPosition(position) |
||||
const nodeAtPosition = await this.props.plugin.call('codeParser', 'definitionAtPosition', cursorPosition) |
||||
const contents = [] |
||||
|
||||
const getDocs = async (node: any) => { |
||||
contents.push({ |
||||
value: await this.props.plugin.call('codeParser', 'getNodeDocumentation', node) |
||||
}) |
||||
} |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const getScope = async (node: any) => { |
||||
if (node.id) { |
||||
contents.push({ |
||||
value: `id: ${node.id}` |
||||
}) |
||||
} |
||||
if (node.scope) { |
||||
contents.push({ |
||||
value: `scope: ${node.scope}` |
||||
}) |
||||
} |
||||
} |
||||
|
||||
const getLinks = async (node: any) => { |
||||
contents.push({ |
||||
value: await this.props.plugin.call('codeParser', 'getNodeLink', node) |
||||
}) |
||||
} |
||||
|
||||
const getVariableDeclaration = async (node: any) => { |
||||
return await this.props.plugin.call('codeParser', 'getVariableDeclaration', node) |
||||
} |
||||
|
||||
|
||||
const getParamaters = async (node: any) => { |
||||
return await this.props.plugin.call('codeParser', 'getFunctionParamaters', node) |
||||
} |
||||
|
||||
const getReturnParameters = async (node: any) => { |
||||
return await this.props.plugin.call('codeParser', 'getFunctionReturnParameters', node) |
||||
} |
||||
|
||||
|
||||
const getOverrides = async (node: any) => { |
||||
if (node.overrides) { |
||||
const overrides = [] |
||||
for (const override of node.overrides.overrides) { |
||||
overrides.push(override.name) |
||||
} |
||||
if (overrides.length) |
||||
return ` overrides (${overrides.join(', ')})` |
||||
} |
||||
return '' |
||||
} |
||||
|
||||
const getlinearizedBaseContracts = async (node: any) => { |
||||
const params = [] |
||||
if (node.linearizedBaseContracts) { |
||||
for (const id of node.linearizedBaseContracts) { |
||||
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id) |
||||
params.push( |
||||
baseContract.name |
||||
) |
||||
} |
||||
if (params.length) |
||||
return `is ${params.join(', ')}` |
||||
} |
||||
return '' |
||||
} |
||||
|
||||
if (nodeAtPosition) { |
||||
if (nodeAtPosition.absolutePath) { |
||||
const target = await this.props.plugin.call('fileManager', 'getPathFromUrl', nodeAtPosition.absolutePath) |
||||
if (target.file !== nodeAtPosition.absolutePath) { |
||||
contents.push({ |
||||
value: `${target.file}` |
||||
}) |
||||
} |
||||
contents.push({ |
||||
value: `${nodeAtPosition.absolutePath}` |
||||
}) |
||||
} |
||||
if (nodeAtPosition.nodeType === 'VariableDeclaration') { |
||||
contents.push({ |
||||
value: await getVariableDeclaration(nodeAtPosition) |
||||
}) |
||||
|
||||
} |
||||
else if (nodeAtPosition.nodeType === 'ElementaryTypeName') { |
||||
contents.push({ |
||||
value: `${nodeAtPosition.typeDescriptions.typeString}` |
||||
}) |
||||
|
||||
} else if (nodeAtPosition.nodeType === 'FunctionDefinition') { |
||||
if (!nodeAtPosition.name) return |
||||
const returns = await getReturnParameters(nodeAtPosition) |
||||
contents.push({ |
||||
value: `function ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)} ${nodeAtPosition.visibility} ${nodeAtPosition.stateMutability}${await getOverrides(nodeAtPosition)} ${returns ? `returns ${returns}` : ''}` |
||||
}) |
||||
|
||||
} else if (nodeAtPosition.nodeType === 'ModifierDefinition') { |
||||
contents.push({ |
||||
value: `modifier ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)}` |
||||
}) |
||||
} else if (nodeAtPosition.nodeType === 'EventDefinition') { |
||||
contents.push({ |
||||
value: `modifier ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)}` |
||||
}) |
||||
} else if (nodeAtPosition.nodeType === 'ContractDefinition') { |
||||
contents.push({ |
||||
value: `${nodeAtPosition.contractKind || nodeAtPosition.kind} ${nodeAtPosition.name} ${await getlinearizedBaseContracts(nodeAtPosition)}` |
||||
}) |
||||
|
||||
} else if (nodeAtPosition.nodeType === 'InvalidNode') { |
||||
contents.push({ |
||||
value: `There are errors in the code.` |
||||
}) |
||||
} else if (nodeAtPosition.nodeType === 'Block') { |
||||
|
||||
} else { |
||||
contents.push({ |
||||
value: `${nodeAtPosition.nodeType}` |
||||
}) |
||||
|
||||
} |
||||
|
||||
for (const key in contents) { |
||||
contents[key].value = '```remix-solidity\n' + contents[key].value + '\n```' |
||||
} |
||||
getLinks(nodeAtPosition) |
||||
getDocs(nodeAtPosition) |
||||
// getScope(nodeAtPosition)
|
||||
} |
||||
|
||||
setTimeout(() => { |
||||
// eslint-disable-next-line no-debugger
|
||||
// debugger
|
||||
},1000) |
||||
|
||||
return { |
||||
range: new this.monaco.Range( |
||||
position.lineNumber, |
||||
position.column, |
||||
position.lineNumber, |
||||
model.getLineMaxColumn(position.lineNumber) |
||||
), |
||||
contents: contents |
||||
}; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,47 @@ |
||||
import { Monaco } from "@monaco-editor/react" |
||||
import { sourceMappingDecoder } from "@remix-project/remix-debug" |
||||
import monaco from "../../types/monaco" |
||||
import { EditorUIProps } from "../remix-ui-editor" |
||||
|
||||
export class RemixReferenceProvider implements monaco.languages.ReferenceProvider { |
||||
props: EditorUIProps |
||||
monaco: Monaco |
||||
constructor(props: any, monaco: any) { |
||||
this.props = props |
||||
this.monaco = monaco |
||||
} |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async provideReferences(model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.ReferenceContext, token: monaco.CancellationToken) { |
||||
|
||||
const cursorPosition = this.props.editorAPI.getCursorPosition() |
||||
const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition) |
||||
const references = [] |
||||
if (nodes && nodes.length) { |
||||
const compilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult') |
||||
const file = await this.props.plugin.call('fileManager', 'file') |
||||
if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) { |
||||
for (const node of nodes) { |
||||
const position = sourceMappingDecoder.decode(node.src) |
||||
const fileInNode = compilationResult.getSourceName(position.file) |
||||
let fileTarget = await this.props.plugin.call('fileManager', 'getPathFromUrl', fileInNode) |
||||
fileTarget = fileTarget.file |
||||
const fileContent = await this.props.plugin.call('fileManager', 'readFile', fileInNode) |
||||
const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position) |
||||
console.log(position, fileTarget, lineColumn) |
||||
try { |
||||
this.props.plugin.call('editor', 'addModel', fileTarget, fileContent) |
||||
} catch (e) { |
||||
|
||||
} |
||||
const range = new this.monaco.Range(lineColumn.start.line + 1, lineColumn.start.column + 1, lineColumn.end.line + 1, lineColumn.end.column + 1) |
||||
references.push({ |
||||
range, |
||||
uri: this.monaco.Uri.parse(fileTarget) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
return references |
||||
} |
||||
} |
Loading…
Reference in new issue