Merge pull request #2774 from ethereum/editorcontext_merge

editor autocomplete/hover/references
pull/5370/head
Rob 2 years ago committed by GitHub
commit 5df7fb5b93
  1. 2
      apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts
  2. 173
      apps/remix-ide-e2e/src/examples/editor-test-contracts.ts
  3. 257
      apps/remix-ide-e2e/src/tests/editor.test.ts
  4. 517
      apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts
  5. 236
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  6. 196
      apps/remix-ide-e2e/src/tests/editorReferences.test.ts
  7. 13
      apps/remix-ide/src/app.js
  8. 13
      apps/remix-ide/src/app/editor/editor.js
  9. 643
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  10. 158
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  11. 235
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  12. 76
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  13. 732
      apps/remix-ide/src/app/plugins/parser/types/antlr-types.ts
  14. 1
      apps/remix-ide/src/app/plugins/parser/types/index.ts
  15. 251
      apps/remix-ide/src/assets/js/parser/Solidity-EZVQ6AE4.tokens
  16. 41374
      apps/remix-ide/src/assets/js/parser/antlr.js
  17. 7
      apps/remix-ide/src/assets/js/parser/antlr.js.map
  18. 1
      apps/remix-ide/src/index.html
  19. 1
      apps/solidity-compiler/src/app/compiler-api.ts
  20. 2
      apps/solidity-compiler/src/app/compiler.ts
  21. 8
      libs/remix-analyzer/src/types.ts
  22. 1
      libs/remix-core-plugin/src/index.ts
  23. 244
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  24. 8
      libs/remix-core-plugin/tsconfig.lib.json
  25. 1
      libs/remix-solidity/src/compiler/compiler-worker.ts
  26. 87
      libs/remix-solidity/src/compiler/compiler.ts
  27. 8
      libs/remix-solidity/src/compiler/types.ts
  28. 4
      libs/remix-ui/editor-context-view/.babelrc
  29. 19
      libs/remix-ui/editor-context-view/.eslintrc
  30. 7
      libs/remix-ui/editor-context-view/README.md
  31. 1
      libs/remix-ui/editor-context-view/src/index.ts
  32. 43
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.css
  33. 207
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx
  34. 16
      libs/remix-ui/editor-context-view/tsconfig.json
  35. 13
      libs/remix-ui/editor-context-view/tsconfig.lib.json
  36. 17
      libs/remix-ui/editor/src/lib/actions/editor.ts
  37. 662
      libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts
  38. 457
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  39. 49
      libs/remix-ui/editor/src/lib/providers/definitionProvider.ts
  40. 38
      libs/remix-ui/editor/src/lib/providers/highlightProvider.ts
  41. 166
      libs/remix-ui/editor/src/lib/providers/hoverProvider.ts
  42. 47
      libs/remix-ui/editor/src/lib/providers/referenceProvider.ts
  43. 102
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  44. 1
      libs/remix-ui/editor/src/lib/web-types.ts
  45. 2
      libs/remix-ui/editor/src/types/monaco.ts
  46. 2
      libs/remix-ui/editor/tsconfig.lib.json
  47. 2
      libs/remix-ui/search/src/lib/context/context.tsx
  48. 3
      libs/remix-ui/settings/src/lib/constants.ts
  49. 109
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  50. 15
      libs/remix-ui/settings/src/lib/settingsAction.ts
  51. 47
      libs/remix-ui/settings/src/lib/settingsReducer.ts
  52. 3
      nx.json
  53. 2
      package.json
  54. 8
      tsconfig.base.json
  55. 20
      workspace.json
  56. 2
      yarn.lock

@ -21,7 +21,7 @@ function checkFilter (browser: NightwatchBrowser, filter: string, test: string,
const filterClass = '[data-id="terminalInputSearch"]'
browser.setValue(filterClass, filter, function () {
browser.execute(function () {
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test || ''
}, [], function (result) {
browser.clearValue(filterClass).setValue(filterClass, '', function () {
if (!result.value) {

@ -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
}

@ -128,119 +128,6 @@ module.exports = {
.waitForElementNotPresent('.highlightLine51', 60000)
},
'Should display the context view #group2': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError)
.pause(2000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(17, 16)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'FunctionDefinition')
.waitForElementContainsText('.contextview .name', 'store')
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(18, 12)
}, [], () => {})
.waitForElementContainsText('.contextview .type', 'uint256')
.waitForElementContainsText('.contextview .name', 'number')
.click('.contextview [data-action="previous"]') // declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
.click('.contextview [data-action="next"]') // back to the initial state
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '323')
})
.click('.contextview [data-action="next"]') // next reference
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '489')
})
.click('.contextview [data-action="gotoref"]') // back to the declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
},
'Should display the context view, loop over "Owner" by switching file #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.click('[for="autoCompile"]') // disable auto compile
.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]') // compile
.pause(6000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(14, 6)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'ContractDefinition')
.waitForElementContainsText('.contextview .name', 'Owner')
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '1061')
})
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '153')
})
.currentSelectedFileIs('2_Owner.sol') // make sure the current file has been properly changed
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '211')
})
.click('.contextview [data-action="next"]')
.currentSelectedFileIs('3_Ballot.sol')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '1061')
})
.click('.contextview [data-action="gotoref"]') // go to the declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '153')
})
.end()
}
}
const aceThemes = {
@ -347,148 +234,4 @@ contract Storage {
}
}`
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 Ballot {
Owner c;
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) {
c = new Owner();
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,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;
}
}
`

@ -14,10 +14,11 @@ import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/parser/code-parser'
import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener, GistHandler } from '@remix-project/core-plugin'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler } from '@remix-project/core-plugin'
import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config'
@ -212,7 +213,9 @@ class AppComponent {
}
}
)
const contextualListener = new EditorContextListener(new AstWalker())
const codeParser = new CodeParser(new AstWalker())
this.notification = new NotificationPlugin()
@ -236,14 +239,14 @@ class AppComponent {
compilersArtefacts,
networkModule,
offsetToLineColumnConverter,
contextualListener,
codeParser,
fileDecorator,
terminal,
web3Provider,
compileAndRun,
fetchAndCompile,
dGitProvider,
storagePlugin,
fileDecorator,
hardhatProvider,
ganacheProvider,
foundryProvider,
@ -368,7 +371,7 @@ class AppComponent {
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'fileDecorator', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder'])

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'addErrorMarker', 'clearErrorMarkers']
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers'],
}
class Editor extends Plugin {
@ -211,6 +211,7 @@ class Editor extends Plugin {
}
async _onChange (file) {
this.triggerEvent('didChangeFile', [file])
const currentFile = await this.call('fileManager', 'file')
if (!currentFile) {
return
@ -292,6 +293,10 @@ class Editor extends Plugin {
return this.api.findMatches(this.currentFile, string)
}
addModel(path, content) {
this.emit('addModel', content, this._getMode(path), path, false)
}
/**
* Display an Empty read-only session
*/
@ -506,11 +511,13 @@ class Editor extends Plugin {
// error markers
async addErrorMarker (error){
this.api.addErrorMarker(error)
const { from } = this.currentRequest
this.api.addErrorMarker(error, from)
}
async clearErrorMarkers(sources){
this.api.clearErrorMarkers(sources)
const { from } = this.currentRequest
this.api.clearErrorMarkers(sources, from)
}
/**

@ -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

@ -41,6 +41,7 @@
<script type="text/javascript" src="assets/js/loader.js"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/intro.min.js"></script>
<script type="text/javascript" src="assets/js/parser/antlr.js"></script>
</body>
</html>

@ -219,6 +219,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
this.data.loading = true
this.data.loadingUrl = url
this.statusChanged({ key: 'loading', title: 'loading compiler...', type: 'info' })
this.emit('loadingCompiler', url)
}
this.compiler.event.register('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)

@ -22,7 +22,7 @@ export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements
this.compileTabLogic = new CompileTabLogic(this, this.contentImport)
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
this.initCompilerApi()
this.initCompilerApi()
}
getCompilerParameters () {

@ -168,7 +168,7 @@ export interface ContractDefinitionAstNode {
nodeType: 'ContractDefinition'
src: string
name: string
documentation: string | null
documentation: string | null | StructuredDocumentationAstNode
contractKind: 'interface' | 'contract' | 'library'
abstract: boolean
fullyImplemented: boolean
@ -241,7 +241,7 @@ export interface FunctionDefinitionAstNode {
nodeType: 'FunctionDefinition'
src: string
name: string
documentation: string | null
documentation: string | null | StructuredDocumentationAstNode
kind: string
stateMutability: 'pure' | 'view' | 'nonpayable' | 'payable'
visibility: string
@ -281,7 +281,7 @@ export interface ModifierDefinitionAstNode {
nodeType: 'ModifierDefinition'
src: string
name: string
documentation: Record<string, unknown> | null
documentation: Record<string, unknown> | null | StructuredDocumentationAstNode
visibility: string
parameters: ParameterListAstNode
virtual: boolean
@ -303,7 +303,7 @@ export interface EventDefinitionAstNode {
nodeType: 'EventDefinition'
src: string
name: string
documentation: Record<string, unknown> | null
documentation: Record<string, unknown> | null | StructuredDocumentationAstNode
parameters: ParameterListAstNode
anonymous: boolean
}

@ -3,7 +3,6 @@ export { CompilerMetadata } from './lib/compiler-metadata'
export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
export { CompilerImports } from './lib/compiler-content-imports'
export { CompilerArtefacts } from './lib/compiler-artefacts'
export { EditorContextListener } from './lib/editor-context-listener'
export { GistHandler } from './lib/gist-handler'
export * from './types/contract'
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'

@ -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() + ')'
}
}

@ -3,10 +3,14 @@
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"declaration": true,
"rootDir": "./src",
"types": ["node"]
},
"exclude": ["**/*.spec.ts"],
"exclude": [
"**/*.spec.ts",
"tests/"
],
"include": ["**/*.ts"]
}

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

@ -9,7 +9,7 @@ import {
Source, SourceWithTarget, MessageFromWorker, CompilerState, CompilationResult,
visitContractsCallbackParam, visitContractsCallbackInterface, CompilationError,
gatherImportsCallbackInterface,
isFunctionDescription
isFunctionDescription, CompilerRetriggerMode
} from './types'
/*
@ -19,7 +19,7 @@ export class Compiler {
event
state: CompilerState
constructor (public handleImportCall?: (fileurl: string, cb) => void) {
constructor(public handleImportCall?: (fileurl: string, cb) => void) {
this.event = new EventManager()
this.state = {
compileJSON: null,
@ -34,6 +34,7 @@ export class Compiler {
target: null,
useFileConfiguration: false,
configFileContent: '',
compilerRetriggerMode: CompilerRetriggerMode.none,
lastCompilationResult: {
data: null,
source: null
@ -48,7 +49,6 @@ export class Compiler {
})
this.event.register('compilationStarted', () => {
this.state.compilationStartTime = new Date().getTime()
})
}
@ -58,7 +58,7 @@ export class Compiler {
* @param value value of key in CompilerState
*/
set <K extends keyof CompilerState> (key: K, value: CompilerState[K]): void {
set<K extends keyof CompilerState>(key: K, value: CompilerState[K]): void {
this.state[key] = value
if (key === 'runs') this.state['runs'] = parseInt(value)
}
@ -69,7 +69,7 @@ export class Compiler {
* @param missingInputs missing import file path list
*/
internalCompile (files: Source, missingInputs?: string[]): void {
internalCompile(files: Source, missingInputs?: string[]): void {
this.gatherImports(files, missingInputs, (error, input) => {
if (error) {
this.state.lastCompilationResult = null
@ -84,8 +84,9 @@ export class Compiler {
* @param target target file name (This is passed as it is to IDE)
*/
compile (files: Source, target: string): void {
compile(files: Source, target: string): void {
this.state.target = target
this.state.compilationStartTime = new Date().getTime()
this.event.trigger('compilationStarted', [])
this.internalCompile(files)
}
@ -105,7 +106,7 @@ export class Compiler {
* @dev Called when compiler is loaded internally (without worker)
*/
onInternalCompilerLoaded (): void {
onInternalCompilerLoaded(): void {
if (this.state.worker === null) {
const compiler: any = typeof (window) !== 'undefined' && window['Module'] ? require('solc/wrapper')(window['Module']) : require('solc') // eslint-disable-line
this.state.compileJSON = (source: SourceWithTarget) => {
@ -144,7 +145,7 @@ export class Compiler {
* @param source Source
*/
onCompilationFinished (data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget, input?: string, version?: string): void {
onCompilationFinished(data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget, input?: string, version?: string): void {
let noFatalErrors = true // ie warnings are ok
const checkIfFatalError = (error: CompilationError) => {
@ -179,7 +180,7 @@ export class Compiler {
* @param version compiler version
*/
loadRemoteVersion (version: string): void {
loadRemoteVersion(version: string): void {
console.log(`Loading remote solc version ${version} ...`)
const compiler: any = require('solc') // eslint-disable-line
compiler.loadRemoteVersion(version, (err, remoteCompiler) => {
@ -224,7 +225,7 @@ export class Compiler {
* @param url URL to load compiler from
*/
loadVersion (usingWorker: boolean, url: string): void {
loadVersion(usingWorker: boolean, url: string): void {
console.log('Loading ' + url + ' ' + (usingWorker ? 'with worker' : 'without worker'))
this.event.trigger('loadingCompiler', [url, usingWorker])
if (this.state.worker) {
@ -243,7 +244,7 @@ export class Compiler {
* @param url URL to load compiler from
*/
loadInternal (url: string): void {
loadInternal(url: string): void {
delete window['Module']
// NOTE: workaround some browsers?
window['Module'] = undefined
@ -269,38 +270,41 @@ export class Compiler {
* @param url URL to load compiler from
*/
loadWorker (url: string): void {
loadWorker(url: string): void {
this.state.worker = webworkify(require.resolve('./compiler-worker'))
const jobs: Record<'sources', SourceWithTarget> [] = []
const jobs: Record<'sources', SourceWithTarget>[] = []
this.state.worker.addEventListener('message', (msg: Record <'data', MessageFromWorker>) => {
this.state.worker.addEventListener('message', (msg: Record<'data', MessageFromWorker>) => {
const data: MessageFromWorker = msg.data
if (this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger && data.timestamp !== this.state.compilationStartTime) {
return
}
switch (data.cmd) {
case 'versionLoaded':
if (data.data && data.license) this.onCompilerLoaded(data.data, data.license)
break
case 'compiled':
{
let result: CompilationResult
if (data.data && data.job !== undefined && data.job >= 0) {
try {
result = JSON.parse(data.data)
} catch (exception) {
result = { error: { formattedMessage: 'Invalid JSON output from the compiler: ' + exception } }
}
let sources: SourceWithTarget = {}
if (data.job in jobs !== undefined) {
sources = jobs[data.job].sources
delete jobs[data.job]
{
let result: CompilationResult
if (data.data && data.job !== undefined && data.job >= 0) {
try {
result = JSON.parse(data.data)
} catch (exception) {
result = { error: { formattedMessage: 'Invalid JSON output from the compiler: ' + exception } }
}
let sources: SourceWithTarget = {}
if (data.job in jobs !== undefined) {
sources = jobs[data.job].sources
delete jobs[data.job]
}
this.onCompilationFinished(result, data.missingInputs, sources, data.input, this.state.currentVersion)
}
this.onCompilationFinished(result, data.missingInputs, sources, data.input, this.state.currentVersion)
break
}
break
}
}
})
this.state.worker.addEventListener('error', (msg: Record <'data', MessageFromWorker>) => {
this.state.worker.addEventListener('error', (msg: Record<'data', MessageFromWorker>) => {
const formattedMessage = `Worker error: ${msg.data && msg.data !== undefined ? msg.data : msg['message']}`
this.onCompilationFinished({ error: { formattedMessage } })
})
@ -326,7 +330,8 @@ export class Compiler {
this.state.worker.postMessage({
cmd: 'compile',
job: jobs.length - 1,
input: input
input: input,
timestamp: this.state.compilationStartTime
})
}
}
@ -344,7 +349,7 @@ export class Compiler {
* @param cb callback
*/
gatherImports (files: Source, importHints?: string[], cb?: gatherImportsCallbackInterface): void {
gatherImports(files: Source, importHints?: string[], cb?: gatherImportsCallbackInterface): void {
importHints = importHints || []
// FIXME: This will only match imports if the file begins with one '.'
// It should tokenize by lines and check each.
@ -383,7 +388,7 @@ export class Compiler {
* @param version version
*/
truncateVersion (version: string): string {
truncateVersion(version: string): string {
const tmp: RegExpExecArray | null = /^(\d+.\d+.\d+)/.exec(version)
return tmp ? tmp[1] : version
}
@ -393,8 +398,8 @@ export class Compiler {
* @param data Compilation result
*/
updateInterface (data: CompilationResult) : CompilationResult {
txHelper.visitContracts(data.contracts, (contract : visitContractsCallbackParam) => {
updateInterface(data: CompilationResult): CompilationResult {
txHelper.visitContracts(data.contracts, (contract: visitContractsCallbackParam) => {
if (!contract.object.abi) contract.object.abi = []
if (this.state.language === 'Yul' && contract.object.abi.length === 0) {
// yul compiler does not return any abi,
@ -426,7 +431,7 @@ export class Compiler {
* @param name contract name
*/
getContract (name: string): Record<string, any> | null {
getContract(name: string): Record<string, any> | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return txHelper.getContract(name, this.state.lastCompilationResult.data.contracts)
}
@ -438,7 +443,7 @@ export class Compiler {
* @param cb callback
*/
visitContracts (cb: visitContractsCallbackInterface) : void | null {
visitContracts(cb: visitContractsCallbackInterface): void | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return txHelper.visitContracts(this.state.lastCompilationResult.data.contracts, cb)
}
@ -449,7 +454,7 @@ export class Compiler {
* @dev Get the compiled contracts data from last compilation result
*/
getContracts () : CompilationResult['contracts'] | null {
getContracts(): CompilationResult['contracts'] | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return this.state.lastCompilationResult.data.contracts
}
@ -460,7 +465,7 @@ export class Compiler {
* @dev Get sources from last compilation result
*/
getSources () : Source | null | undefined {
getSources(): Source | null | undefined {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.source) {
return this.state.lastCompilationResult.source.sources
}
@ -472,7 +477,7 @@ export class Compiler {
* @param fileName file name
*/
getSource (fileName: string) : Source['filename'] | null {
getSource(fileName: string): Source['filename'] | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.source && this.state.lastCompilationResult.source.sources) {
return this.state.lastCompilationResult.source.sources[fileName]
}
@ -484,7 +489,7 @@ export class Compiler {
* @param index - index of the source
*/
getSourceName (index: number): string | null {
getSourceName(index: number): string | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.sources) {
return Object.keys(this.state.lastCompilationResult.data.sources)[index]
}

@ -154,6 +154,11 @@ export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | '
export type Language = 'Solidity' | 'Yul'
export enum CompilerRetriggerMode {
'none' ,
'retrigger'
}
export interface CompilerState {
compileJSON: ((input: SourceWithTarget) => void) | null,
worker: any,
@ -167,6 +172,7 @@ export interface CompilerState {
target: string | null,
useFileConfiguration: boolean,
configFileContent: string,
compilerRetriggerMode: CompilerRetriggerMode,
lastCompilationResult: {
data: CompilationResult | null,
source: SourceWithTarget | null | undefined
@ -183,6 +189,7 @@ export interface MessageToWorker {
job?: number,
input?: CompilerInput,
data?: string
timestamp?: number
}
export interface MessageFromWorker {
@ -192,6 +199,7 @@ export interface MessageFromWorker {
missingInputs?: string[],
input?: any,
data?: string
timestamp?: number
}
export interface visitContractsCallbackParam {

@ -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"]
}

@ -21,7 +21,12 @@ export const reducerActions = (models = initialState, action: Action) => {
const readOnly = action.payload.readOnly
if (models[uri]) return models // already existing
models[uri] = { language, uri, readOnly }
const model = monaco.editor.createModel(value, language, monaco.Uri.parse(uri))
let model
try {
model = monaco.editor.createModel(value, language, monaco.Uri.parse(uri))
} catch (e) {
}
models[uri].model = model
model.onDidChangeContent(() => action.payload.events.onDidChangeContent(uri))
return models
@ -54,20 +59,20 @@ export const reducerActions = (models = initialState, action: Action) => {
case 'REVEAL_RANGE': {
if (!editor) return models
const range: IRange = {
startLineNumber: action.payload.startLineNumber +1,
startLineNumber: action.payload.startLineNumber + 1,
startColumn: action.payload.startColumn,
endLineNumber: action.payload.endLineNumber + 1,
endColumn: action.payload.endColumn
}
// reset to start of line
if(action.payload.startColumn < 100){
if (action.payload.startColumn < 100) {
editor.revealRange({
startLineNumber: range.startLineNumber,
startColumn: 1,
endLineNumber: range.endLineNumber,
endColumn: 1
})
}else{
} else {
editor.revealRangeInCenter(range)
}
return models
@ -132,12 +137,12 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
plugin.on('editor', 'revealRange', (startLineNumber, startColumn, endLineNumber, endColumn) => {
dispatch({
type: 'REVEAL_RANGE',
payload: {
payload: {
startLineNumber,
startColumn,
endLineNumber,
endColumn
},
},
monaco,
editor
})

@ -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
}
}

@ -1,23 +1,21 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { RemixUiEditorContextView, astNode } from '@remix-ui/editor-context-view'
import Editor, { loader } from '@monaco-editor/react'
import Editor, { loader, Monaco } from '@monaco-editor/react'
import { reducerActions, reducerListener, initialState } from './actions/editor'
import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import { IMarkdownString } from 'monaco-editor'
import './remix-ui-editor.css'
import { loadTypes } from './web-types'
import monaco from '../types/monaco'
import { MarkerSeverity } from 'monaco-editor'
import { IMarkdownString, IPosition, MarkerSeverity } from 'monaco-editor'
type cursorPosition = {
startLineNumber: number,
startColumn: number,
endLineNumber: number,
endColumn: number
}
import { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider'
import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixHighLightProvider } from './providers/highlightProvider'
import { RemixDefinitionProvider } from './providers/definitionProvider'
type sourceAnnotation = {
row: number,
@ -64,7 +62,7 @@ export type lineText = {
type errorMarker = {
message: string
severity: MarkerSeverity
severity: MarkerSeverity | 'warning' | 'info' | 'error' | 'hint'
position: {
start: {
line: number
@ -105,15 +103,15 @@ export interface EditorUIProps {
findMatches: (uri: string, value: string) => any
getFontSize: () => number,
getValue: (uri: string) => string
getCursorPosition: () => cursorPosition
getCursorPosition: () => number
getHoverPosition: (position: IPosition) => number
addDecoration: (marker: sourceMarker, filePath: string, typeOfDecoration: string) => DecorationsReturn
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
addErrorMarker: (errors: []) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}) => void
addErrorMarker: (errors: errorMarker[], from: string) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}, from: string) => void
}
}
export const EditorUI = (props: EditorUIProps) => {
const [, setCurrentBreakpoints] = useState({})
const defaultEditorValue = `
@ -136,7 +134,7 @@ export const EditorUI = (props: EditorUIProps) => {
\t\t\t\t\t\t\t\tTwitter: https://twitter.com/ethereumremix\n
`
const editorRef = useRef(null)
const monacoRef = useRef(null)
const monacoRef = useRef<Monaco>(null)
const currentFileRef = useRef('')
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
// const registeredDecorations = useRef({}) // registered decorations
@ -344,6 +342,18 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
if (typeOfDecoration === 'lineTextPerFile') {
const lineTextDecoration = decoration as lineText
return {
type: typeOfDecoration,
range: new monacoRef.current.Range(lineTextDecoration.position.start.line + 1, lineTextDecoration.position.start.column + 1, lineTextDecoration.position.start.line + 1, 1024),
options: {
after: { content: ` ${lineTextDecoration.content}`, inlineClassName: `${lineTextDecoration.className}` },
afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage : lineTextDecoration.hoverMessage
},
}
}
}
props.editorAPI.clearDecorationsByPlugin = (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => {
@ -390,7 +400,6 @@ export const EditorUI = (props: EditorUIProps) => {
const model = editorModelsState[filePath]?.model
if (!model) return { currentDecorations: [] }
const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration)
return {
currentDecorations: model.deltaDecorations([], [monacoDecoration]),
registeredDecorations: [{ value: decoration, type: typeOfDecoration }]
@ -401,7 +410,7 @@ export const EditorUI = (props: EditorUIProps) => {
return addDecoration(marker, filePath, typeOfDecoration)
}
props.editorAPI.addErrorMarker = async (errors: errorMarker[]) => {
props.editorAPI.addErrorMarker = async (errors: errorMarker[], from: string) => {
const allMarkersPerfile: Record<string, Array<monaco.editor.IMarkerData>> = {}
@ -419,7 +428,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
if (model) {
const markerData: monaco.editor.IMarkerData = {
severity: errorServerityMap[error.severity],
severity: (typeof error.severity === 'string') ? errorServerityMap[error.severity] : error.severity,
startLineNumber: ((error.position.start && error.position.start.line) || 0),
startColumn: ((error.position.start && error.position.start.column) || 0),
endLineNumber: ((error.position.end && error.position.end.line) || 0),
@ -435,18 +444,18 @@ export const EditorUI = (props: EditorUIProps) => {
for (const filePath in allMarkersPerfile) {
const model = editorModelsState[filePath]?.model
if (model) {
monacoRef.current.editor.setModelMarkers(model, 'remix-solidity', allMarkersPerfile[filePath])
monacoRef.current.editor.setModelMarkers(model, from, allMarkersPerfile[filePath])
}
}
}
props.editorAPI.clearErrorMarkers = async (sources: string[] | {[fileName: string]: any}) => {
props.editorAPI.clearErrorMarkers = async (sources: string[] | {[fileName: string]: any}, from: string) => {
if (sources) {
for (const source of (Array.isArray(sources) ? sources : Object.keys(sources))) {
const filePath = source
const model = editorModelsState[filePath]?.model
if (model) {
monacoRef.current.editor.setModelMarkers(model, 'remix-solidity', [])
monacoRef.current.editor.setModelMarkers(model, from, [])
}
}
}
@ -474,6 +483,16 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
props.editorAPI.getHoverPosition = (position: monaco.Position) => {
if (!monacoRef.current) return
const model = editorModelsState[currentFileRef.current]?.model
if (model) {
return model.getOffsetAt(position)
} else {
return 0
}
}
props.editorAPI.getFontSize = () => {
if (!editorRef.current) return
return editorRef.current.getOption(43).fontSize
@ -518,10 +537,10 @@ export const EditorUI = (props: EditorUIProps) => {
})
// zoomin zoomout
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_EQUAL, () => {
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_EQUAL, () => {
editor.updateOptions({ fontSize: editor.getOption(43).fontSize + 1 })
})
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_MINUS, () => {
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_MINUS, () => {
editor.updateOptions({ fontSize: editor.getOption(43).fontSize - 1 })
})
@ -574,14 +593,20 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.register({ id: 'remix-zokrates' })
// Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider)
monacoRef.current.languages.setLanguageConfiguration('remix-solidity', solidityLanguageConfig)
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-solidity', solidityLanguageConfig as any )
monacoRef.current.languages.setMonarchTokensProvider('remix-cairo', cairoTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-cairo', cairoLanguageConfig as any)
monacoRef.current.languages.setMonarchTokensProvider('remix-cairo', cairoTokensProvider)
monacoRef.current.languages.setLanguageConfiguration('remix-cairo', cairoLanguageConfig)
monacoRef.current.languages.setMonarchTokensProvider('remix-zokrates', zokratesTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-zokrates', zokratesLanguageConfig as any)
monacoRef.current.languages.setMonarchTokensProvider('remix-zokrates', zokratesTokensProvider)
monacoRef.current.languages.setLanguageConfiguration('remix-zokrates', zokratesLanguageConfig)
monacoRef.current.languages.registerDefinitionProvider('remix-solidity', new RemixDefinitionProvider(props, monaco))
monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', new RemixHighLightProvider(props, monaco))
monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco))
monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco))
monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco))
loadTypes(monacoRef.current)
}
@ -597,22 +622,7 @@ export const EditorUI = (props: EditorUIProps) => {
options={{ glyphMargin: true, readOnly: true }}
defaultValue={defaultEditorValue}
/>
<div className="contextview">
<RemixUiEditorContextView
hide={false}
gotoLine={(line, column) => props.plugin.call('editor', 'gotoLine', line, column)}
openFile={(file) => props.plugin.call('fileManager', 'switchFile', file)}
getLastCompilationResult={() => { return props.plugin.call('compilerArtefacts', 'getLastCompilationResult') }}
offsetToLineColumn={(position, file, sources, asts) => { return props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, file, sources, asts) }}
getCurrentFileName={() => { return props.plugin.call('fileManager', 'file') }}
onContextListenerChanged={(listener) => { props.plugin.on('contextualListener', 'contextChanged', listener) }}
onCurrentFileChanged={(listener) => { props.plugin.on('fileManager', 'currentFileChanged', listener) }}
referencesOf={(node: astNode) => { return props.plugin.call('contextualListener', 'referencesOf', node) }}
getActiveHighlights={() => { return props.plugin.call('contextualListener', 'getActiveHighlights') }}
gasEstimation={(node: astNode) => { return props.plugin.call('contextualListener', 'gasEstimation', node) }}
declarationOf={(node: astNode) => { return props.plugin.call('contextualListener', 'declarationOf', node) }}
/>
</div>
</div>
)
}

@ -222,5 +222,4 @@ export const loadTypes = async (monaco) => {
}
`
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexRemixApi)
console.log('loaded monaco types')
}

@ -1866,7 +1866,7 @@ declare namespace monaco.editor {
/**
* Get the language associated with this model.
*/
getModeId(): string;
getModeId?(): string;
/**
* Get the word under or besides `position`.
* @param position The position to look for a word.

@ -9,5 +9,5 @@
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.d.ts"]
}

@ -338,7 +338,7 @@ export const SearchProvider = ({
async function fetchWorkspace() {
try {
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
if (workspace) {
if (workspace && workspace.name) {
value.setCurrentWorkspace(workspace.name)
setFiles(await getDirectory('/', plugin))
}

@ -14,6 +14,9 @@ export const etherscanAccessTokenText2 = 'Go to Etherscan api key page (link bel
export const ethereunVMText = 'Always use Remix VM at load'
export const wordWrapText = 'Word wrap in editor'
export const enablePersonalModeText = ' Enable Personal Mode for Remix Provider. Transaction sent over Web3 will use the web3.personal API.\n'
export const useAutoCompleteText = 'Enable code completion in editor.'
export const useShowGasInEditorText = 'Display gas estimates in editor.'
export const displayErrorsText = 'Display errors in editor while typing.'
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '
export const swarmSettingsTitle = 'Swarm Settings'
export const swarmSettingsText = 'Swarm Settings'

@ -1,10 +1,10 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle, ipfsSettingsText } from './constants'
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle, ipfsSettingsText, useAutoCompleteText, useShowGasInEditorText, displayErrorsText } from './constants'
import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast } from './settingsAction'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast, useAutoCompletion, useShowGasInEditor, useDisplayErrors } from './settingsAction'
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer'
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module'
@ -14,16 +14,16 @@ import { GithubSettings } from './github-settings'
export interface RemixUiSettingsProps {
config: any,
editor: any,
_deps: any,
useMatomoAnalytics: boolean
themeModule: ThemeModule
_deps: any,
useMatomoAnalytics: boolean
themeModule: ThemeModule
}
export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [, dispatch] = useReducer(settingReducer, initialState)
const [state, dispatchToast] = useReducer(toastReducer, toastInitialState)
const [tokenValue, setTokenValue] = useState({})
const [themeName, ] = useState('')
const [themeName,] = useState('')
const [privateBeeAddress, setPrivateBeeAddress] = useState('')
const [postageStampId, setPostageStampId] = useState('')
const [resetState, refresh] = useState(0)
@ -33,13 +33,21 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [ipfsProjectId, setipfsProjectId] = useState('')
const [ipfsProjectSecret, setipfsProjectSecret] = useState('')
const initValue = () => {
const metadataConfig = props.config.get('settings/generate-contract-metadata')
const metadataConfig = props.config.get('settings/generate-contract-metadata')
if (metadataConfig === undefined || metadataConfig === null) generateContractMetadat(props.config, true, dispatch)
const javascriptVM = props.config.get('settings/always-use-vm')
if (javascriptVM === null || javascriptVM === undefined) ethereumVM(props.config, true, dispatch)
const useAutoComplete = props.config.get('settings/use-auto-complete')
if (useAutoComplete === null || useAutoComplete === undefined) useAutoCompletion(props.config, true, dispatch)
const displayErrors = props.config.get('settings/display-errors')
if (displayErrors === null || displayErrors === undefined) useDisplayErrors(props.config, true, dispatch)
const useShowGas = props.config.get('settings/show-gas')
if (useShowGas === null || useShowGas === undefined) useShowGasInEditor(props.config, true, dispatch)
}
useEffect(() => initValue(), [resetState, props.config])
useEffect(() => initValue(), [])
@ -88,7 +96,6 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
setipfsProjectSecret(configipfsProjectSecret)
}
}, [themeName, state.message])
useEffect(() => {
@ -115,6 +122,18 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
useMatomoAnalytics(props.config, event.target.checked, dispatch)
}
const onchangeUseAutoComplete = event => {
useAutoCompletion(props.config, event.target.checked, dispatch)
}
const onchangeShowGasInEditor = event => {
useShowGasInEditor(props.config, event.target.checked, dispatch)
}
const onchangeDisplayErrors = event => {
useDisplayErrors(props.config, event.target.checked, dispatch)
}
const getTextClass = (key) => {
if (props.config.get(key)) {
return textDark
@ -129,51 +148,71 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const isEditorWrapChecked = props.config.get('settings/text-wrap') || false
const isPersonalChecked = props.config.get('settings/personal-mode') || false
const isMatomoChecked = props.config.get('settings/matomo-analytics') || false
const isAutoCompleteChecked = props.config.get('settings/auto-completion') === null ? true:props.config.get('settings/auto-completion')
const isShowGasInEditorChecked = props.config.get('settings/show-gas') === null ? true:props.config.get('settings/show-gas')
const displayErrorsChecked = props.config.get('settings/display-errors') === null ? true:props.config.get('settings/display-errors')
return (
<div className="$border-top">
<div title="Reset to Default settings." className='d-flex justify-content-end pr-4'>
<button className="btn btn-sm btn-secondary ml-2" onClick={() => {
try {
if ((window as any).remixFileSystem.name === 'indexedDB') {
props.config.clear()
try {
if ((window as any).remixFileSystem.name === 'indexedDB') {
props.config.clear()
try {
localStorage.clear() // remove the whole storage
} catch (e) {
console.log(e)
}
} else {
props.config.clear() // remove only the remix settings
}
refresh(resetState + 1)
localStorage.clear() // remove the whole storage
} catch (e) {
console.log(e)
}
}}>Reset to Default settings</button>
} else {
props.config.clear() // remove only the remix settings
}
refresh(resetState + 1)
} catch (e) {
console.log(e)
}
}}>Reset to Default settings</button>
</div>
<div className="card-body pt-3 pb-2">
<h6 className="card-title">General settings</h6>
<div className="mt-2 custom-control custom-checkbox mb-1">
<input onChange={onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" className="custom-control-input" name="contractMetadata" checked = { isMetadataChecked }/>
<input onChange={onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" className="custom-control-input" name="contractMetadata" checked={isMetadataChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/generate-contract-metadata')}`} data-id="settingsTabGenerateContractMetadataLabel" htmlFor="generatecontractmetadata">{generateContractMetadataText}</label>
</div>
<div className="fmt-2 custom-control custom-checkbox mb-1">
<input onChange={onchangeOption} className="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox" name="ethereumVM" checked={ isEthereumVMChecked }/>
<input onChange={onchangeOption} className="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox" name="ethereumVM" checked={isEthereumVMChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/always-use-vm')}`} htmlFor="alwaysUseVM">{ethereunVMText}</label>
</div>
<div className="mt-2 custom-control custom-checkbox mb-1">
<input id="editorWrap" className="custom-control-input" type="checkbox" onChange={textWrapEvent} checked = { isEditorWrapChecked }/>
<input id="editorWrap" className="custom-control-input" type="checkbox" onChange={textWrapEvent} checked={isEditorWrapChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/text-wrap')}`} htmlFor="editorWrap">{wordWrapText}</label>
</div>
<div className='custom-control custom-checkbox mb-1'>
<input onChange={onchangeUseAutoComplete} id="settingsUseAutoComplete" type="checkbox" className="custom-control-input" checked={isAutoCompleteChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/auto-completion')}`} htmlFor="settingsUseAutoComplete">
<span>{useAutoCompleteText}</span>
</label>
</div>
<div className='custom-control custom-checkbox mb-1'>
<input onChange={onchangeShowGasInEditor} id="settingsUseShowGas" type="checkbox" className="custom-control-input" checked={isShowGasInEditorChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/show-gas')}`} htmlFor="settingsUseShowGas">
<span>{useShowGasInEditorText}</span>
</label>
</div>
<div className='custom-control custom-checkbox mb-1'>
<input onChange={onchangeDisplayErrors} id="settingsDisplayErrors" type="checkbox" className="custom-control-input" checked={displayErrorsChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/display-errors')}`} htmlFor="settingsDisplayErrors">
<span>{displayErrorsText}</span>
</label>
</div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangePersonal} id="personal" type="checkbox" className="custom-control-input" checked = { isPersonalChecked }/>
<input onChange={onchangePersonal} id="personal" type="checkbox" className="custom-control-input" checked={isPersonalChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/personal-mode')}`} htmlFor="personal">
<i className="fas fa-exclamation-triangle text-warning" aria-hidden="true"></i> <span> </span>
<span> </span>{enablePersonalModeText} {warnText}
</label>
</div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={ isMatomoChecked }/>
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={isMatomoChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/matomo-analytics')}`} htmlFor="settingsMatomoAnalytics">
<span>{matomoAnalytics}</span>
<a href="https://medium.com/p/66ef69e14931/" target="_blank"> Analytics in Remix IDE</a> <span>&</span> <a target="_blank" href="https://matomo.org/free-software">Matomo</a>
@ -191,7 +230,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const removeToken = (type: string) => {
setTokenValue(prevState => {
return { ...prevState, [type]: ''}
return { ...prevState, [type]: '' }
})
removeTokenToast(props.config, dispatchToast, labels[type].key)
}
@ -199,7 +238,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const handleSaveTokenState = useCallback(
(event, type) => {
setTokenValue(prevState => {
return { ...prevState, [type]: event.target.value}
return { ...prevState, [type]: event.target.value }
})
},
[tokenValue]
@ -208,10 +247,10 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const token = (type: string) => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ labels[type].title }</h6>
<p className="mb-1">{ labels[type].message1 }</p>
<p className="">{ labels[type].message2 }</p>
<p className="mb-1"><a className="text-primary" target="_blank" href={labels[type].link}>{ labels[type].link }</a></p>
<h6 className="card-title">{labels[type].title}</h6>
<p className="mb-1">{labels[type].message1}</p>
<p className="">{labels[type].message2}</p>
<p className="mb-1"><a className="text-primary" target="_blank" href={labels[type].link}>{labels[type].link}</a></p>
<div className=""><label>TOKEN:</label>
<div className="text-secondary mb-0 h6">
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={(e) => handleSaveTokenState(e, type)} value={ tokenValue[type] || '' } />
@ -250,12 +289,12 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
<h6 className="card-title">{ swarmSettingsTitle }</h6>
<div className="pt-2 pt-2 mb-0 pb-0"><label>PRIVATE BEE ADDRESS:</label>
<div className="text-secondary mb-0 h6">
<input id="swarmprivatebeeaddress" data-id="settingsPrivateBeeAddress" className="form-control" onChange={handleSavePrivateBeeAddress} value={ privateBeeAddress } />
<input id="swarmprivatebeeaddress" data-id="settingsPrivateBeeAddress" className="form-control" onChange={handleSavePrivateBeeAddress} value={privateBeeAddress} />
</div>
</div>
<div className="pt-2 mb-0 pb-0"><label>POSTAGE STAMP ID:</label>
<div className="text-secondary mb-0 h6">
<input id="swarmpostagestamp" data-id="settingsPostageStampId" className="form-control" onChange={handleSavePostageStampId} value={ postageStampId } />
<input id="swarmpostagestamp" data-id="settingsPostageStampId" className="form-control" onChange={handleSavePostageStampId} value={postageStampId} />
<div className="d-flex justify-content-end pt-2">
</div>
</div>

@ -41,6 +41,21 @@ export const useMatomoAnalytics = (config, checked, dispatch) => {
}
}
export const useAutoCompletion = (config, checked, dispatch) => {
config.set('settings/auto-completion', checked)
dispatch({ type: 'useAutoCompletion', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const useShowGasInEditor = (config, checked, dispatch) => {
config.set('settings/show-gas', checked)
dispatch({ type: 'useShowGasInEditor', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const useDisplayErrors = (config, checked, dispatch) => {
config.set('settings/display-errors', checked)
dispatch({ type: 'displayErrors', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const saveTokenToast = (config, dispatch, tokenValue, key) => {
config.set('settings/' + key, tokenValue)
dispatch({ type: 'save', payload: { message: 'GitHub credentials updated' } })

@ -26,6 +26,21 @@ export const initialState = {
name: 'useMatomoAnalytics',
isChecked: false,
textClass: textSecondary
},
{
name: 'useAutoCompletion',
isChecked: true,
textClass: textSecondary
},
{
name: 'useShowGasInEditor',
isChecked: true,
textClass: textSecondary
},
{
name: 'displayErrors',
isChecked: true,
textClass: textSecondary
}
]
}
@ -82,9 +97,41 @@ export const settingReducer = (state, action) => {
return {
...state
}
case 'useAutoCompletion':
state.elementState.map(element => {
if (element.name === 'useAutoCompletion') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'displayErrors':
state.elementState.map(element => {
if (element.name === 'displayErrors') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'useShowGasInEditor':
state.elementState.map(element => {
if (element.name === 'useShowGasInEditor') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
default:
return initialState
}
}
export const toastInitialState = {

@ -184,9 +184,6 @@
"solidity-unit-testing": {
"tags": []
},
"remix-ui-editor-context-view": {
"tags": []
},
"remix-ui-run-tab": {
"tags": []
},

@ -45,7 +45,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search,remix-ui-tooltip-popup",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search,remix-ui-file-decorators,remix-ui-tooltip-popup",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "yarn run build:libs && lerna publish --skip-git && yarn run bumpVersion:libs",

@ -10,7 +10,7 @@
"importHelpers": true,
"target": "es2015",
"module": "commonjs",
"typeRoots": ["node_modules/@types"],
"typeRoots": ["node_modules/@types", "dist/libs/**/*.d.ts"],
"lib": ["es2017", "es2019", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
@ -28,7 +28,7 @@
"dist/libs/remix-simulator/src/index.js"
],
"@remix-project/remix-solidity": [
"dist/libs/remix-solidity/src/index.js"
"dist/libs/remix-solidity/src/index"
],
"@remix-project/remix-tests": ["dist/libs/remix-tests/src/index.js"],
"@remix-project/remix-url-resolver": [
@ -52,6 +52,7 @@
"@remix-ui/modal-dialog": ["libs/remix-ui/modal-dialog/src/index.ts"],
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"],
"@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/src/index.ts"],
"@remix-ui/file-decorators": ["libs/remix-ui/file-decorators/src/index.ts"],
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"],
"@remix-ui/static-analyser": [
"libs/remix-ui/static-analyser/src/index.ts"
@ -78,9 +79,6 @@
],
"@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"],
"@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"],
"@remix-ui/editor-context-view": [
"libs/remix-ui/editor-context-view/src/index.ts"
],
"@remix-ui/solidity-unit-testing": [
"libs/remix-ui/solidity-unit-testing/src/index.ts"
],

@ -1370,26 +1370,6 @@
}
}
},
"remix-ui-editor-context-view": {
"root": "libs/remix-ui/editor-context-view",
"sourceRoot": "libs/remix-ui/editor-context-view/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": [
"libs/remix-ui/editor-context-view/tsconfig.lib.json"
],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/editor-context-view/**/*"
]
}
}
}
},
"remix-ui-run-tab": {
"root": "libs/remix-ui/run-tab",
"sourceRoot": "libs/remix-ui/run-tab/src",

@ -4644,7 +4644,7 @@
integrity sha512-NxxZZek50ylIACiXebKQYHD3D4One3WXOasEXWazL6aTfYbZob7ClNKxUpg8I4/oWArX87oPWvj1cHKqfel3Hg==
dependencies:
"@types/ws" "*"
"@types/semver@^7.3.10":
version "7.3.10"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.10.tgz#5f19ee40cbeff87d916eedc8c2bfe2305d957f73"

Loading…
Cancel
Save