editorcontextDummy
filip mertens 2 years ago
parent 3e05e165d1
commit a9c762152c
  1. 109
      apps/remix-ide-e2e/src/examples/editor-test-contracts.ts
  2. 427
      apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts
  3. 114
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  4. 2
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  5. 7
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  6. 20
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  7. 2
      libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts
  8. 353
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts

@ -0,0 +1,109 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const testContract = {
name: 'test.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "contracts/base.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);
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: '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: '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 {
}
}`}
export default {
testContract,
baseContract,
baseOfBaseContract
}

@ -1,28 +1,79 @@
'use strict' 'use strict'
import { NightwatchBrowser } from 'nightwatch' import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
import examples from '../examples/editor-test-contracts'
const autoCompleteLineElement = (name: string) => { const autoCompleteLineElement = (name: string) => {
return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]`
} }
module.exports = { module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false) init(browser, done, 'http://127.0.0.1:8080', false)
}, },
'Should load the test file': function (browser: NightwatchBrowser) { 'Should add test and base files #group2': function (browser: NightwatchBrowser) {
browser.openFile('contracts') browser.addFile('contracts/test.sol', examples.testContract)
.openFile('contracts/3_Ballot.sol') .addFile('contracts/base.sol', examples.baseContract)
.waitForElementVisible('#editorView') .addFile('contracts/baseofbase.sol', examples.baseOfBaseContract)
.setEditorValue(BallotWithARefToOwner) .openFile('contracts/test.sol').pause(3000)
.pause(4000) // wait for the compiler to finish
.scrollToLine(37)
}, },
'Should put cursor at the end of a line': function (browser: NightwatchBrowser) { 'Should put cursor in the () of the function #group2': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'new') and contains(.,'owner')]//span//span[contains(.,';')]" browser.scrollToLine(18)
const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]"
browser.waitForElementVisible('#editorView') browser.waitForElementVisible('#editorView')
.useXpath() .useXpath()
.click(path).pause(1000) .click(path).pause(1000)
},
'Should complete variable declaration types in a function definition #group2': 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, 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(' t, 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(' b')
})
},
'Should put cursor at the end of function #group2': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'{')]"
browser
.useXpath()
.click(path).pause(1000)
.perform(function () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
@ -31,7 +82,42 @@ module.exports = {
sendKeys(this.Keys.ARROW_RIGHT) sendKeys(this.Keys.ARROW_RIGHT)
}) })
}, },
'Should type and get msg + sender': function (browser: NightwatchBrowser) {
'Should autocomplete derived and local event when not using this. #group2': 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 #group2': function (browser: NightwatchBrowser) {
browser. browser.
perform(function () { perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
@ -40,6 +126,10 @@ module.exports = {
sendKeys('msg.') sendKeys('msg.')
}) })
.waitForElementVisible(autoCompleteLineElement('sender')) .waitForElementVisible(autoCompleteLineElement('sender'))
.waitForElementVisible(autoCompleteLineElement('data'))
.waitForElementVisible(autoCompleteLineElement('value'))
.waitForElementVisible(autoCompleteLineElement('gas'))
.waitForElementVisible(autoCompleteLineElement('sig'))
.click(autoCompleteLineElement('sender')) .click(autoCompleteLineElement('sender'))
.perform(function () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
@ -48,217 +138,176 @@ module.exports = {
sendKeys(this.Keys.ENTER) sendKeys(this.Keys.ENTER)
}) })
}, },
'Should type and get completions in the context without this': function (browser: NightwatchBrowser) { 'Should bo and get book #group2': 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 #group2': function (browser: NightwatchBrowser) {
browser.perform(function () { browser.perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
sendKeys(this.Keys.ENTER). sendKeys('.')
sendKeys('co')
}) })
.waitForElementVisible(autoCompleteLineElement('chairperson')) .waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('cowner')) .waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('constructor')) .waitForElementVisible(autoCompleteLineElement('title'))
.waitForElementVisible(autoCompleteLineElement('continue')) .click(autoCompleteLineElement('title'))
.waitForElementVisible(autoCompleteLineElement('contract')) .perform(function () {
.waitForElementVisible(autoCompleteLineElement('constant')) const actions = this.actions({ async: true });
.click(autoCompleteLineElement('cowner')) return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should bo and get basebook #group2': 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'))
}, },
'Perform dot completion on cowner': function (browser: NightwatchBrowser) { 'Should autcomplete derived struct from base class #group2': function (browser: NightwatchBrowser) {
browser.perform(function () { browser.perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
sendKeys('.') sendKeys('.')
}) })
// publicly available functions .waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('changeOwner')) .waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('getOwner')) .waitForElementVisible(autoCompleteLineElement('title'))
// do not show private vars, functions & modifiers & events .click(autoCompleteLineElement('title'))
.waitForElementNotPresent(autoCompleteLineElement('private'))
.waitForElementNotPresent(autoCompleteLineElement('isOwner'))
.waitForElementNotPresent(autoCompleteLineElement('ownerSet'))
.perform(function () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
sendKeys(this.Keys.ENTER). sendKeys(';')
sendKeys('msg.') .sendKeys(this.Keys.ENTER)
}) })
.waitForElementVisible(autoCompleteLineElement('sender')) },
.click(autoCompleteLineElement('sender')) 'Should find private and internal local functions #group2': 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 () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
// right arrow key
sendKeys(this.Keys.ARROW_RIGHT).
sendKeys(this.Keys.ARROW_RIGHT).
sendKeys(this.Keys.ENTER) sendKeys(this.Keys.ENTER)
}) })
}, },
'Dot complete struct': function (browser: NightwatchBrowser) { 'Should find internal functions and var from base and owner #group2': function (browser: NightwatchBrowser) {
browser.perform(function () { browser.perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
sendKeys(this.Keys.ENTER). sendKeys('intern')
sendKeys('Proposal m')
}) })
.waitForElementVisible(autoCompleteLineElement('memory')) .waitForElementVisible(autoCompleteLineElement('internalbasefunction'))
.click(autoCompleteLineElement('memory')) .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. #group2': 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. #group2': 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 #group2': 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 #group2': 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 () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
sendKeys(' p;'). sendKeys(';')
sendKeys(this.Keys.ENTER).pause(5000). .sendKeys(this.Keys.ENTER)
sendKeys('p.') .sendKeys('MyEnum.')
}) })
.waitForElementVisible(autoCompleteLineElement('name')) .waitForElementVisible(autoCompleteLineElement('SMALL'))
.waitForElementVisible(autoCompleteLineElement('voteCount')) .waitForElementVisible(autoCompleteLineElement('MEDIUM'))
.waitForElementVisible(autoCompleteLineElement('LARGE'))
.click(autoCompleteLineElement('SMALL'))
.perform(function () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions. return actions.
sendKeys(' =1;') sendKeys(';')
.sendKeys(this.Keys.ENTER)
}) })
} }
} }
// 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();
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;
}
}
`

@ -23,7 +23,7 @@ import { ConfigPlugin } from '../config'
const profile: Profile = { const profile: Profile = {
name: 'codeParser', name: 'codeParser',
methods: ['nodesAtPosition', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates'], 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: [], events: [],
version: '0.0.1' version: '0.0.1'
} }
@ -38,16 +38,16 @@ export function isNodeDefinition(node: genericASTNode) {
} }
export type genericASTNode = export type genericASTNode =
ContractDefinitionAstNode ContractDefinitionAstNode
| FunctionDefinitionAstNode | FunctionDefinitionAstNode
| ModifierDefinitionAstNode | ModifierDefinitionAstNode
| VariableDeclarationAstNode | VariableDeclarationAstNode
| StructDefinitionAstNode | StructDefinitionAstNode
| EventDefinitionAstNode | EventDefinitionAstNode
| IdentifierAstNode | IdentifierAstNode
| FunctionCallAstNode | FunctionCallAstNode
| ImportDirectiveAstNode | ImportDirectiveAstNode
| SourceUnitAstNode | SourceUnitAstNode
interface flatReferenceIndexNode { interface flatReferenceIndexNode {
[id: number]: genericASTNode [id: number]: genericASTNode
@ -83,7 +83,7 @@ export class CodeParser extends Plugin {
parseSolidity: (text: string) => Promise<antlr.ParseResult> parseSolidity: (text: string) => Promise<antlr.ParseResult>
getLastNodeInLine: (ast: string) => Promise<any> getLastNodeInLine: (ast: string) => Promise<any>
listAstNodes: () => Promise<any> listAstNodes: () => Promise<any>
getBlockAtPosition: (position: any, text?: string) => Promise<any> getANTLRBlockAtPosition: (position: any, text?: string) => Promise<any>
getCurrentFileAST: (text?: string) => Promise<ParseResult> getCurrentFileAST: (text?: string) => Promise<ParseResult>
constructor(astWalker: any) { constructor(astWalker: any) {
@ -92,7 +92,8 @@ export class CodeParser extends Plugin {
this.nodeIndex = { this.nodeIndex = {
declarations: [[]], declarations: [[]],
flatReferences: [], flatReferences: [],
nodesPerFile: {} } nodesPerFile: {}
}
} }
async onActivation() { async onActivation() {
@ -105,7 +106,7 @@ export class CodeParser extends Plugin {
this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService) this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService)
this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(this.antlrService) this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(this.antlrService)
this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService) this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService)
this.getBlockAtPosition = this.antlrService.getBlockAtPosition.bind(this.antlrService) this.getANTLRBlockAtPosition = this.antlrService.getANTLRBlockAtPosition.bind(this.antlrService)
this.getCurrentFileAST = this.antlrService.getCurrentFileAST.bind(this.antlrService) this.getCurrentFileAST = this.antlrService.getCurrentFileAST.bind(this.antlrService)
@ -154,7 +155,7 @@ export class CodeParser extends Plugin {
getSubNodes<T extends genericASTNode>(node: T): number[] { getSubNodes<T extends genericASTNode>(node: T): number[] {
return node.nodeType=="ContractDefinition" && node.contractDependencies; return node.nodeType == "ContractDefinition" && node.contractDependencies;
} }
/** /**
@ -198,26 +199,77 @@ export class CodeParser extends Plugin {
} }
_flatNodeList(node: ContractDefinitionAstNode, contractName: string, fileName: string, compilatioResult: any) { _flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) {
const index = {} const index = {}
const contractName: string = contractNode.name
const callback = (node) => { const callback = (node) => {
if(inScope && node.scope !== contractNode.id) return
if(inScope) node.isClassNode = true;
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult)
node.functionName = node.name + this._getInputParams(node) node.functionName = node.name + this._getInputParams(node)
node.contractName = contractName
node.contractId = contractNode.id
index[node.id] = node index[node.id] = node
} }
this.astWalker.walkFull(node, callback) this.astWalker.walkFull(contractNode, callback)
return index return index
} }
_extractFileNodes(fileName: string, compilationResult: lastCompilationResult) { _extractFileNodes(fileName: string, compilationResult: lastCompilationResult) {
if (compilationResult && compilationResult.data.sources && compilationResult.data.sources[fileName]) { if (compilationResult && compilationResult.data.sources && compilationResult.data.sources[fileName]) {
const source = compilationResult.data.sources[fileName] const source = compilationResult.data.sources[fileName]
const nodesByContract = [] const nodesByContract: any = {}
this.astWalker.walkFull(source.ast, (node) => { nodesByContract.imports = {}
nodesByContract.contracts = {}
this.astWalker.walkFull(source.ast, async (node) => {
if (node.nodeType === 'ContractDefinition') { if (node.nodeType === 'ContractDefinition') {
const flatNodes = this._flatNodeList(node, node.name, fileName, compilationResult) const flatNodes = this._flatNodeList(node, fileName, false, compilationResult)
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult) node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult)
nodesByContract[node.name] = { contractDefinition: node, contractNodes: flatNodes } 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)
console.log('baseContract', baseContract)
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, {})
console.log('import resolve', node, (Object.values(imported) as any))
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 return nodesByContract
@ -385,6 +437,24 @@ export class CodeParser extends Plugin {
} }
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 * @param identifierNode
@ -426,7 +496,7 @@ export class CodeParser extends Plugin {
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) {
const importNode: any = await this.getNodeById(node.sourceUnit) const importNode: any = await this.getNodeById(node.sourceUnit)
imported[importNode.id] = importNode imported[importNode.id] = importNode
if (importNode.nodeType=='ImportDirective' && importNode.nodes) { if (importNode.nodes) {
for (const child of importNode.nodes) { for (const child of importNode.nodes) {
imported = await this.resolveImports(child, imported) imported = await this.resolveImports(child, imported)
} }

@ -143,7 +143,7 @@ export default class CodeParserAntlrService {
* @param {string} text // optional * @param {string} text // optional
* @return {any} * @return {any}
* */ * */
async getBlockAtPosition(position: any, text: string = null) { async getANTLRBlockAtPosition(position: any, text: string = null) {
await this.getCurrentFileAST(text) await this.getCurrentFileAST(text)
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition']
const walkAst = (node: any) => { const walkAst = (node: any) => {

@ -73,14 +73,9 @@ export default class CodeParserCompiler {
this.plugin._buildIndex(data, source) this.plugin._buildIndex(data, source)
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract)
if (this.gastEstimateTimeOut) {
window.clearTimeout(this.gastEstimateTimeOut)
}
await this.plugin.gasService.showGasEstimates() await this.plugin.gasService.showGasEstimates()
console.log("INDEX", this.plugin.nodeIndex) console.log("INDEX", this.plugin.nodeIndex)
this.plugin.emit('astFinished') this.plugin.emit('astFinished')
} }

@ -12,17 +12,19 @@ export default class CodeParserGasService {
if (!fileName) { if (!fileName) {
fileName = await this.plugin.currentFile fileName = await this.plugin.currentFile
} }
if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName]) { if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
const estimates: any = [] const estimates: any = []
for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName]) { for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
console.log(contract) console.log(contract)
const nodes = this.plugin.nodeIndex.nodesPerFile[fileName][contract].contractNodes if (this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes) {
for (const node of Object.values(nodes) as any[]) { const nodes = this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes
if (node.gasEstimate) { for (const node of Object.values(nodes) as any[]) {
estimates.push({ if (node.gasEstimate) {
node, estimates.push({
range: await this.plugin.getLineColumnOfNode(node) node,
}) range: await this.plugin.getLineColumnOfNode(node)
})
}
} }
} }
} }

@ -260,7 +260,7 @@ function CreateCompletionItem(label: string, kind: monaco.languages.CompletionIt
export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.CompletionItem[] { export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.CompletionItem[] {
const completionItems = []; const completionItems = [];
const keywords = ['modifier', 'mapping', 'break', 'continue', 'delete', 'else', 'for', const keywords = ['modifier', 'mapping', 'break', 'continue', 'delete', 'else', 'for',
'if', 'new', 'return', 'returns', 'while', 'using', 'if', 'new', 'return', 'returns', 'while', 'using', 'emit',
'private', 'public', 'external', 'internal', 'payable', 'nonpayable', 'view', 'pure', 'case', 'do', 'else', 'finally', 'private', 'public', 'external', 'internal', 'payable', 'nonpayable', 'view', 'pure', 'case', 'do', 'else', 'finally',
'in', 'instanceof', 'return', 'throw', 'try', 'catch', 'typeof', 'yield', 'void', 'virtual', 'override']; 'in', 'instanceof', 'return', 'throw', 'try', 'catch', 'typeof', 'yield', 'void', 'virtual', 'override'];
keywords.forEach(unit => { keywords.forEach(unit => {

@ -1,3 +1,4 @@
import { sourceMappingDecoder } from "@remix-project/remix-debug"
import { AstNode } from "@remix-project/remix-solidity-ts" import { AstNode } from "@remix-project/remix-solidity-ts"
import { isArray } from "lodash" import { isArray } from "lodash"
import { editor, languages, Position } from "monaco-editor" import { editor, languages, Position } from "monaco-editor"
@ -13,17 +14,13 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
constructor(props: any, monaco: any) { constructor(props: any, monaco: any) {
this.props = props this.props = props
this.monaco = monaco this.monaco = monaco
} }
triggerCharacters = ['.', ''] triggerCharacters = ['.', '']
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> { async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> {
console.log('AUTOCOMPLETE', context) console.log('AUTOCOMPLETE', context)
console.log(position)
const word = model.getWordUntilPosition(position); const word = model.getWordUntilPosition(position);
const wordAt = model.getWordAtPosition(position);
const range = { const range = {
startLineNumber: position.lineNumber, startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber, endLineNumber: position.lineNumber,
@ -35,20 +32,18 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
let nodes: AstNode[] = [] let nodes: AstNode[] = []
let suggestions: monaco.languages.CompletionItem[] = [] let suggestions: monaco.languages.CompletionItem[] = []
const cursorPosition: number = this.props.editorAPI.getCursorPosition()
if (context.triggerCharacter === '.') { if (context.triggerCharacter === '.') {
console.clear() console.clear()
console.log('TEXT', line)
const lineTextBeforeCursor: string = line.substring(0, position.column - 1) const lineTextBeforeCursor: string = line.substring(0, position.column - 1)
const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor)
console.log('lastNode found', lastNodeInExpression) console.log('lastNode found', lastNodeInExpression)
console.log(lineTextBeforeCursor)
const expressionElements = lineTextBeforeCursor.split('.') const expressionElements = lineTextBeforeCursor.split('.')
console.log('expression elements', expressionElements)
let dotCompleted = false let dotCompleted = false
//if (expressionElements.length === 2) {
// handles completion from for builtin types
const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco)
if (globalCompletion) { if (globalCompletion) {
dotCompleted = true dotCompleted = true
@ -58,115 +53,21 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
// debugger // debugger
}, 2000) }, 2000)
} }
// handle completion for global THIS.
if (lastNodeInExpression.name === 'this') { if (lastNodeInExpression.name === 'this') {
dotCompleted = true dotCompleted = true
let thisCompletionNodes = await this.getContractCompletions(nodes, position) nodes = [...nodes, ...await this.getThisCompletions(position)]
// with this. you can't have internal nodes and no contractDefinitions
thisCompletionNodes = thisCompletionNodes.filter(node => {
if (node.visibility && node.visibility === 'internal') {
return false
}
if (node.nodeType && node.nodeType === 'ContractDefinition') {
return false
}
return true
})
nodes = [...nodes, ...thisCompletionNodes]
setTimeout(() => {
// eslint-disable-next-line no-debugger
// debugger
}, 2000)
} }
//} // handle completion for other dot completions
if (expressionElements.length > 1 && !dotCompleted) { if (expressionElements.length > 1 && !dotCompleted) {
const last = lastNodeInExpression.name || lastNodeInExpression.memberName const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName
console.log('last', last) nodes = [...nodes, ...await this.getDotCompletions(position, nameOfLastTypedExpression)]
let nodesAtPosition;
const block = await this.props.plugin.call('codeParser', 'getBlockAtPosition', position, null)
console.log('BLOCK', block)
if (block) {
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.body ? block.body.range[0] : block.range[0])
console.log('NODES AT POSITION WITH BLOCK', nodesAtPosition)
} else {
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
console.log('NODES AT POSITION', nodesAtPosition)
}
// explore nodes at the BLOCK
if (nodesAtPosition) {
for (const node of nodesAtPosition) {
const nodesOfScope: AstNode[] = await this.props.plugin.call('codeParser', 'getNodesWithScope', node.id)
console.log('NODES OF SCOPE ', node.name, node.id, nodesOfScope)
for (const nodeOfScope of nodesOfScope) {
if (nodeOfScope.name === last) {
console.log('FOUND NODE', nodeOfScope)
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') {
const declarationOf: AstNode = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName)
console.log('METHOD 1 HAS DECLARATION OF', declarationOf)
nodes = [...nodes, ...declarationOf.nodes || declarationOf.members]
const baseContracts = await this.getlinearizedBaseContracts(declarationOf)
for (const baseContract of baseContracts) {
nodes = [...nodes, ...baseContract.nodes]
}
}
}
}
}
// anything within the block statements might provide a clue to what it is
// if (!nodes.length) {
for (const node of nodesAtPosition) {
if (node.statements) {
for (const statement of node.statements) {
if (statement.expression && statement.expression.memberName === last) {
const declarationOf = await this.props.plugin.call('codeParser', 'declarationOf', statement.expression)
if (declarationOf.typeName && declarationOf.typeName.nodeType === 'UserDefinedTypeName') {
const baseDeclaration = await this.props.plugin.call('codeParser', 'declarationOf', declarationOf.typeName)
console.log('METHOD 2 HAS BASE DECLARATION OF', baseDeclaration)
nodes = [...nodes, ...baseDeclaration.nodes || baseDeclaration.members]
}
}
}
}
}
}
// }
// brute force search in all nodes with the name
const nodesOfScope = await this.props.plugin.call('codeParser', 'getNodesWithName', last)
console.log('NODES WITHE NAME ', last, nodesOfScope)
for (const nodeOfScope of nodesOfScope) {
if (nodeOfScope.name === last) {
console.log('FOUND NODE', nodeOfScope)
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') {
const declarationOf = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName)
console.log('METHOD 3 HAS DECLARATION OF', declarationOf)
nodes = [...nodes, ...declarationOf.nodes || declarationOf.members]
//const baseContracts = await this.getlinearizedBaseContracts(declarationOf)
//for (const baseContract of baseContracts) {
//nodes = [...nodes, ...baseContract.nodes]
//}
}
}
}
const filterNodeTypes = ['ContractDefinition', 'ModifierDefinition', 'EventDefinition']
nodes = nodes.filter(node => {
if (node.visibility && node.visibility === 'private') {
return false
}
if (node.nodeType && filterNodeTypes.indexOf(node.nodeType) !== -1) {
return false
}
return true
})
} }
} else { } else {
// handles contract completions and other suggestions
suggestions = [...suggestions, suggestions = [...suggestions,
...GetGlobalVariable(range, this.monaco), ...GetGlobalVariable(range, this.monaco),
...getCompletionSnippets(range, this.monaco), ...getCompletionSnippets(range, this.monaco),
@ -175,19 +76,18 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
...GetGlobalFunctions(range, this.monaco), ...GetGlobalFunctions(range, this.monaco),
...GeCompletionUnits(range, this.monaco), ...GeCompletionUnits(range, this.monaco),
] ]
let thisCompletionNodes = await this.getContractCompletions(nodes, position) let contractCompletions = await this.getContractCompletions(position)
// we can't have external nodes without using this. // we can't have external nodes without using this.
thisCompletionNodes = thisCompletionNodes.filter(node => { contractCompletions = contractCompletions.filter(node => {
if (node.visibility && node.visibility === 'external') { if (node.visibility && node.visibility === 'external') {
return false return false
} }
return true return true
}) })
nodes = [...nodes, ...thisCompletionNodes] nodes = [...nodes, ...contractCompletions]
} }
console.log('WORD', word, wordAt)
console.log('NODES', nodes) console.log('NODES', nodes)
// remove duplicates // remove duplicates
@ -225,7 +125,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} }
} }
const getVariableDeclaration = async (node: any) => { const getVariableDeclaration = async (node: any) => {
let variableDeclaration = await this.props.plugin.call('codeParser', 'getVariableDeclaration', node) let variableDeclaration = await this.props.plugin.call('codeParser', 'getVariableDeclaration', node)
if (node.scope) { if (node.scope) {
@ -250,7 +149,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} }
suggestions.push(completion) suggestions.push(completion)
} else if (node.nodeType === 'FunctionDefinition') { } else if (node.nodeType === 'FunctionDefinition') {
const completion = { const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` }, label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` },
kind: this.monaco.languages.CompletionItemKind.Function, kind: this.monaco.languages.CompletionItemKind.Function,
@ -293,9 +191,10 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} else if } else if
(node.nodeType === 'EventDefinition') { (node.nodeType === 'EventDefinition') {
const completion = { const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` }, label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` },
kind: this.monaco.languages.CompletionItemKind.Event, kind: this.monaco.languages.CompletionItemKind.Event,
insertText: node.name, insertText: `${node.name}${await completeParameters(node.parameters)};`,
insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range: range, range: range,
documentation: await getDocs(node) documentation: await getDocs(node)
} }
@ -310,6 +209,17 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
documentation: await getDocs(node) documentation: await getDocs(node)
} }
suggestions.push(completion) 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)
} else { } else {
console.log('UNKNOWN NODE', node) console.log('UNKNOWN NODE', node)
} }
@ -321,77 +231,204 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} }
} }
private getlinearizedBaseContracts = async (node: any) => { private getBlockNodesAtPosition = async (position: Position) => {
let params = [] let nodes: any[] = []
if (node.linearizedBaseContracts) { const cursorPosition = this.props.editorAPI.getCursorPosition()
for (const id of node.linearizedBaseContracts) { const nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
if (id !== node.id) { // try to get the block from ANTLR of which the position is in
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id) const ANTLRBlock = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null)
params = [...params, ...[baseContract]] // 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')
console.log('file nodes', fileNodes);
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) {
console.log('found real block', contractNode)
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]
}
}
}
} }
//const nodesOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', node.id)
// nodes = [...nodes, ...nodesOfScope]
} }
} }
return params console.log('NODES AT BLOCK SCOPE', nodes)
// 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
}
}
return false
})
return nodes;
} }
private getContractCompletions = async (nodes: any[], position: Position) => { private getContractAtPosition = async (position: Position) => {
const cursorPosition = this.props.editorAPI.getCursorPosition() const cursorPosition = this.props.editorAPI.getCursorPosition()
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition) let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
console.log('NODES AT POSITION', nodesAtPosition) console.log('NODES AT POSITION', nodesAtPosition)
// if no nodes exits at position, try to get the block of which the position is in // if no nodes exits at position, try to get the block of which the position is in
const ANTLRBlock = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null)
if (!nodesAtPosition.length) { if (!nodesAtPosition.length) {
const block = await this.props.plugin.call('codeParser', 'getBlockAtPosition', position, null) if (ANTLRBlock) {
if (block) { nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', ANTLRBlock.range[0])
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.range[0])
} }
} }
// get all children of all nodes at position
if (isArray(nodesAtPosition) && nodesAtPosition.length) { if (isArray(nodesAtPosition) && nodesAtPosition.length) {
for (const node of nodesAtPosition) { for (const node of nodesAtPosition) {
const nodesOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', node.id)
for (const nodeOfScope of nodesOfScope) {
const imports = await this.props.plugin.call('codeParser', 'resolveImports', nodeOfScope)
if (imports) {
for (const key in imports) {
if (imports[key].nodes)
nodes = [...nodes, ...imports[key].nodes]
}
}
}
// if the node is a contract at this positio mark the nodes as the contractNodes
if (node.nodeType === 'ContractDefinition') { if (node.nodeType === 'ContractDefinition') {
for (const node of nodesOfScope) { return node
node.isInContract = true
}
} }
nodes = [...nodes, ...nodesOfScope]
} }
// get the linearized base contracts }
return null
}
private getContractCompletions = async (position: Position) => {
let nodes: any[] = []
const cursorPosition = this.props.editorAPI.getCursorPosition()
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
console.log('NODES AT POSITION', nodesAtPosition)
// 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)
console.log('BLOCK AT POSITION', block)
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) { for (const node of nodesAtPosition) {
const baseContracts = await this.getlinearizedBaseContracts(node) if (node.nodeType === 'ContractDefinition') {
for (const baseContract of baseContracts) { contractNode = node
nodes = [...nodes, ...baseContract.nodes] const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes')
console.log('FILE NODES', fileNodes)
const contractNodes = fileNodes.contracts[node.name]
nodes = [...Object.values(contractNodes.contractScopeNodes), ...nodes]
nodes = [...Object.values(contractNodes.baseNodesWithBaseContractScope), ...nodes]
nodes = [...Object.values(fileNodes.imports), ...nodes]
// some types like Enum and Events are not scoped so we need to add them manually
nodes = [...contractNodes.contractDefinition.nodes, ...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 { } else {
// get all the nodes from a simple code parser which only parses the current file // get all the nodes from a simple code parser which only parses the current file
nodes = [...nodes, ...await this.props.plugin.call('codeParser', 'listAstNodes')] nodes = [...nodes, ...await this.props.plugin.call('codeParser', 'listAstNodes')]
} }
return nodes
}
// filter private nodes, only allow them when isContract is true private getThisCompletions = async (position: Position) => {
nodes = nodes.filter(node => { let nodes: any[] = []
if (node.visibility) { let thisCompletionNodes = await this.getContractCompletions(position)
if (!node.isInContract) { const allowedTypesForThisCompletion = ['VariableDeclaration', 'FunctionDefinition']
return node.visibility !== 'private' // with this. you can't have internal nodes and no contractDefinitions
} thisCompletionNodes = thisCompletionNodes.filter(node => {
console.log('node filter', node)
if (node.visibility && (node.visibility === 'internal' || node.visibility === 'private')) {
return false
}
if (node.nodeType && !allowedTypesForThisCompletion.includes(node.nodeType)) {
return false
} }
return true return true
}) })
nodes = [...nodes, ...thisCompletionNodes]
setTimeout(() => {
// eslint-disable-next-line no-debugger
// debugger
}, 2000)
return nodes return nodes
} }
private getDotCompletions = async (position: Position, nameOfLastTypedExpression: string) => {
const contractCompletions = await this.getContractCompletions(position)
let nodes: any[] = []
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) {
// console.log('nodeOfScope', nodeOfScope.name, nodeOfScope)
if (nodeOfScope.name === nameOfLastTypedExpression) {
console.log('FOUND NODE', nodeOfScope)
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') {
const declarationOf: AstNode = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName)
console.log('METHOD 1 HAS DECLARATION OF', declarationOf)
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) {
console.log('METHOD 1 HAS MEMBERS OF')
nodes = [...nodes, ...filterNodes(nodeOfScope.members, nodeOfScope)]
}
}
}
return nodes
}
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 * @param lineTextBeforeCursor

Loading…
Cancel
Save