diff --git a/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts b/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts index db86224b11..7e162530e1 100644 --- a/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts +++ b/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts @@ -17,7 +17,6 @@ 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') @@ -86,7 +85,47 @@ module.exports = { const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]" const expectedContent = 'StructDefinition' checkEditorHoverContent(browser, path, expectedContent) - } + }, + 'Add token file': function (browser: NightwatchBrowser) { + browser.addFile('contracts/mytoken.sol', { + content: myToken + }).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") + }, + // here we change quickly between files to test the files being parsed correctly when switching between them + 'Should show ERC20 hover over contract in editor #group1': function (browser: NightwatchBrowser) { + browser.scrollToLine(10) + const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]" + const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Metadata, IERC20, Context' + checkEditorHoverContent(browser, path, expectedContent, 25) + }, + 'Go back to ballot file': function (browser: NightwatchBrowser) { + browser.openFile('contracts/3_Ballot.sol') + .useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") + }, + 'Should show hover over function in editor again #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) + }, + 'Open token file': function (browser: NightwatchBrowser) { + browser.openFile('contracts/mytoken.sol') + .useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") + }, + 'Should show ERC20 hover over contract in editor again #group1': function (browser: NightwatchBrowser) { + browser.scrollToLine(10) + const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]" + const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Metadata, IERC20, Context' + checkEditorHoverContent(browser, path, expectedContent, 25) + }, + + + } @@ -233,4 +272,38 @@ contract BallotHoverTest { winnerName_ = proposals[winningProposal()].name; } } +` + +const myToken = ` +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import "@openzeppelin/contracts/security/Pausable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyToken is ERC20, ERC20Burnable, Pausable, Ownable { + constructor() ERC20("MyToken", "MTK") {} + + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } + + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal + whenNotPaused + override + { + super._beforeTokenTransfer(from, to, amount); + } +} ` \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts index b3a1a9c322..ecd8278ca3 100644 --- a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts @@ -99,21 +99,24 @@ export default class CodeParserCompiler { 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) // cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository - this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + const extractedFiledNodes = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + if(extractedFiledNodes) { + this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = extractedFiledNodes + } await this.plugin.gasService.showGasEstimates() this.plugin.emit('astFinished') } diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts index 274466fe9c..34e86290c1 100644 --- a/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts @@ -42,7 +42,10 @@ export default class CodeParserGasService { } this.plugin.currentFile = await this.plugin.call('fileManager', 'file') // cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository - this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + const extractedFiledNodes = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + if(extractedFiledNodes) { + this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = extractedFiledNodes + } const gasEstimates = await this.getGasEstimates(this.plugin.currentFile) diff --git a/libs/remix-solidity/src/compiler/compiler.ts b/libs/remix-solidity/src/compiler/compiler.ts index 77e50a5845..2e20b0c857 100644 --- a/libs/remix-solidity/src/compiler/compiler.ts +++ b/libs/remix-solidity/src/compiler/compiler.ts @@ -81,12 +81,15 @@ export class Compiler { * @param missingInputs missing import file path list */ - internalCompile(files: Source, missingInputs?: string[]): void { + internalCompile(files: Source, missingInputs?: string[], timeStamp?: number): void { + if(timeStamp != this.state.compilationStartTime && this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger ) { + return + } this.gatherImports(files, missingInputs, (error, input) => { if (error) { this.state.lastCompilationResult = null this.event.trigger('compilationFinished', [false, { error: { formattedMessage: error, severity: 'error' } }, files, input, this.state.currentVersion]) - } else if (this.state.compileJSON && input) { this.state.compileJSON(input) } + } else if (this.state.compileJSON && input) { this.state.compileJSON(input, timeStamp) } }) } @@ -100,7 +103,7 @@ export class Compiler { this.state.target = target this.state.compilationStartTime = new Date().getTime() this.event.trigger('compilationStarted', []) - this.internalCompile(files) + this.internalCompile(files, null, this.state.compilationStartTime) } /** @@ -157,7 +160,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, timeStamp?: number): void { let noFatalErrors = true // ie warnings are ok const checkIfFatalError = (error: CompilationError) => { @@ -173,7 +176,7 @@ export class Compiler { this.event.trigger('compilationFinished', [false, data, source, input, version]) } else if (missingInputs !== undefined && missingInputs.length > 0 && source && source.sources) { // try compiling again with the new set of inputs - this.internalCompile(source.sources, missingInputs) + this.internalCompile(source.sources, missingInputs, timeStamp) } else { data = this.updateInterface(data) if (source) { @@ -292,6 +295,7 @@ export class Compiler { 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) { + // drop message from previous compilation return } switch (data.cmd) { @@ -312,7 +316,7 @@ export class Compiler { 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, data.timestamp) } break } @@ -325,7 +329,7 @@ export class Compiler { this.onCompilationFinished({ error: { formattedMessage } }) }) - this.state.compileJSON = (source: SourceWithTarget) => { + this.state.compileJSON = (source: SourceWithTarget, timeStamp: number) => { if (source && source.sources) { const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state jobs.push({ sources: source }) @@ -342,12 +346,11 @@ export class Compiler { return } - this.state.worker.postMessage({ cmd: 'compile', job: jobs.length - 1, input: input, - timestamp: this.state.compilationStartTime + timestamp: timeStamp }) } } diff --git a/libs/remix-solidity/src/compiler/types.ts b/libs/remix-solidity/src/compiler/types.ts index fd1158e7f0..d86e7b0b74 100644 --- a/libs/remix-solidity/src/compiler/types.ts +++ b/libs/remix-solidity/src/compiler/types.ts @@ -160,7 +160,7 @@ export enum CompilerRetriggerMode { } export interface CompilerState { - compileJSON: ((input: SourceWithTarget) => void) | null, + compileJSON: ((input: SourceWithTarget, timeStamp?: number) => void) | null, worker: any, currentVersion: string| null| undefined, compilerLicense: string| null