parent
17eca0f318
commit
583b4dc09d
@ -1,900 +0,0 @@ |
|||||||
'use strict' |
|
||||||
import { Plugin } from '@remixproject/engine' |
|
||||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
|
||||||
import { CompilerAbstract } from '@remix-project/remix-solidity' |
|
||||||
import { Compiler } from '@remix-project/remix-solidity' |
|
||||||
|
|
||||||
import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' |
|
||||||
import { helper } from '@remix-project/remix-solidity' |
|
||||||
|
|
||||||
import React from 'react' |
|
||||||
import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' |
|
||||||
import { lineText } from '@remix-ui/editor' |
|
||||||
// eslint-disable-next-line
|
|
||||||
|
|
||||||
|
|
||||||
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || [] |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'codeParser', |
|
||||||
methods: ['nodesAtPosition', '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'], |
|
||||||
events: [], |
|
||||||
version: '0.0.1' |
|
||||||
} |
|
||||||
|
|
||||||
export function isNodeDefinition(node: any) { |
|
||||||
return node.nodeType === 'ContractDefinition' || |
|
||||||
node.nodeType === 'FunctionDefinition' || |
|
||||||
node.nodeType === 'ModifierDefinition' || |
|
||||||
node.nodeType === 'VariableDeclaration' || |
|
||||||
node.nodeType === 'StructDefinition' || |
|
||||||
node.nodeType === 'EventDefinition' |
|
||||||
} |
|
||||||
|
|
||||||
export class CodeParser extends Plugin { |
|
||||||
|
|
||||||
currentFileAST: any // contains the simple parsed AST for the current file
|
|
||||||
compiler: any // used to compile the current file seperately from the main compiler
|
|
||||||
lastCompilationResult: any |
|
||||||
currentFile: any |
|
||||||
_index: any |
|
||||||
astWalker: any |
|
||||||
errorState: boolean = false |
|
||||||
onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise<void> |
|
||||||
gastEstimateTimeOut: any |
|
||||||
|
|
||||||
constructor(astWalker) { |
|
||||||
super(profile) |
|
||||||
this.astWalker = astWalker |
|
||||||
this._index = { |
|
||||||
Declarations: {}, |
|
||||||
FlatReferences: {} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async onActivation() { |
|
||||||
this.on('editor', 'didChangeFile', async (file) => { |
|
||||||
console.log('contentChanged', file) |
|
||||||
await this.call('editor', 'discardLineTexts') |
|
||||||
await this.getCurrentFileAST() |
|
||||||
await this.compile() |
|
||||||
}) |
|
||||||
|
|
||||||
this.on('filePanel', 'setWorkspace', async () => { |
|
||||||
await this.call('fileDecorator', 'setFileDecorators', []) |
|
||||||
}) |
|
||||||
|
|
||||||
|
|
||||||
this.on('fileManager', 'currentFileChanged', async () => { |
|
||||||
await this.call('editor', 'discardLineTexts') |
|
||||||
await this.getCurrentFileAST() |
|
||||||
await this.compile() |
|
||||||
}) |
|
||||||
|
|
||||||
this.on('solidity', 'loadingCompiler', async (url) => { |
|
||||||
console.log('loading compiler', url) |
|
||||||
this.compiler.loadVersion(true, url) |
|
||||||
this.compiler.event.register('compilerLoaded', async () => { |
|
||||||
console.log('compiler loaded') |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
/** |
|
||||||
* - processes compilation results |
|
||||||
* - calls the editor to add markers on the errors
|
|
||||||
* - builds the flat index of nodes |
|
||||||
*/ |
|
||||||
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => { |
|
||||||
console.log('compile success', success, data, this) |
|
||||||
this.call('editor', 'clearAnnotations') |
|
||||||
this.errorState = true |
|
||||||
const checkIfFatalError = (error: CompilationError) => { |
|
||||||
// Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround
|
|
||||||
const isValidError = (error.message && error.message.includes('Deferred import')) ? false : error.severity !== 'warning' |
|
||||||
if (isValidError) { |
|
||||||
console.log(error) |
|
||||||
} |
|
||||||
} |
|
||||||
const result = new CompilerAbstract('soljson', data, source, input) |
|
||||||
|
|
||||||
if (data.error) checkIfFatalError(data.error) |
|
||||||
if (data.errors) data.errors.forEach((err) => checkIfFatalError(err)) |
|
||||||
const allErrors = [] |
|
||||||
if (data.errors) { |
|
||||||
const sources = result.getSourceCode().sources |
|
||||||
for (const error of data.errors) { |
|
||||||
const pos = helper.getPositionDetails(error.formattedMessage) |
|
||||||
const filePosition = Object.keys(sources).findIndex((fileName) => fileName === error.sourceLocation.file) |
|
||||||
const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', |
|
||||||
{ |
|
||||||
start: error.sourceLocation.start, |
|
||||||
length: error.sourceLocation.end - error.sourceLocation.start |
|
||||||
}, |
|
||||||
filePosition, |
|
||||||
result.getSourceCode().sources, |
|
||||||
null) |
|
||||||
allErrors.push({ error, lineColumn }) |
|
||||||
} |
|
||||||
console.log('allErrors', allErrors) |
|
||||||
await this.call('editor', 'addErrorMarker', allErrors) |
|
||||||
|
|
||||||
const errorsPerFiles = {} |
|
||||||
for (const error of allErrors) { |
|
||||||
if (!errorsPerFiles[error.error.sourceLocation.file]) { |
|
||||||
errorsPerFiles[error.error.sourceLocation.file] = [] |
|
||||||
} |
|
||||||
errorsPerFiles[error.error.sourceLocation.file].push(error.error) |
|
||||||
} |
|
||||||
|
|
||||||
const errorPriority = { |
|
||||||
'error': 0, |
|
||||||
'warning': 1, |
|
||||||
} |
|
||||||
|
|
||||||
// sort errorPerFiles by error priority
|
|
||||||
const sortedErrorsPerFiles = {} |
|
||||||
for (const fileName in errorsPerFiles) { |
|
||||||
const errors = errorsPerFiles[fileName] |
|
||||||
errors.sort((a, b) => { |
|
||||||
return errorPriority[a.severity] - errorPriority[b.severity] |
|
||||||
} |
|
||||||
) |
|
||||||
sortedErrorsPerFiles[fileName] = errors |
|
||||||
} |
|
||||||
console.log('sortedErrorsPerFiles', sortedErrorsPerFiles) |
|
||||||
|
|
||||||
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName]) |
|
||||||
|
|
||||||
console.log('filesWithOutErrors', filesWithOutErrors) |
|
||||||
// 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 === 'error' ? fileDecorationType.Error : fileDecorationType.Warning, |
|
||||||
fileStateLabelClass: errors[0].severity === 'error' ? 'text-danger' : 'text-warning', |
|
||||||
fileStateIconClass: '', |
|
||||||
fileStateIcon: '', |
|
||||||
text: errors.length, |
|
||||||
owner: 'code-parser', |
|
||||||
bubble: true, |
|
||||||
commment: 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) |
|
||||||
} |
|
||||||
console.log(decorators) |
|
||||||
await this.call('fileDecorator', 'setFileDecorators', decorators) |
|
||||||
await this.call('editor', 'clearErrorMarkers', filesWithOutErrors) |
|
||||||
} else { |
|
||||||
await this.call('editor', 'clearErrorMarkers', result.getSourceCode().sources) |
|
||||||
const decorators: fileDecoration[] = [] |
|
||||||
for (const fileName of Object.keys(result.getSourceCode().sources)) { |
|
||||||
const decorator: fileDecoration = { |
|
||||||
path: fileName, |
|
||||||
isDirectory: false, |
|
||||||
fileStateType: fileDecorationType.None, |
|
||||||
fileStateLabelClass: '', |
|
||||||
fileStateIconClass: '', |
|
||||||
fileStateIcon: '', |
|
||||||
text: '', |
|
||||||
owner: 'code-parser', |
|
||||||
bubble: false |
|
||||||
} |
|
||||||
decorators.push(decorator) |
|
||||||
} |
|
||||||
console.log(decorators) |
|
||||||
|
|
||||||
await this.call('fileDecorator', 'setFileDecorators', decorators) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
if (!data.sources) return |
|
||||||
if (data.sources && Object.keys(data.sources).length === 0) return |
|
||||||
this.lastCompilationResult = new CompilerAbstract('soljson', data, source, input) |
|
||||||
|
|
||||||
this.errorState = false |
|
||||||
this._index = { |
|
||||||
Declarations: {}, |
|
||||||
FlatReferences: {}, |
|
||||||
NodesPerFile: {}, |
|
||||||
} |
|
||||||
this._buildIndex(data, source) |
|
||||||
|
|
||||||
if (this.gastEstimateTimeOut) { |
|
||||||
window.clearTimeout(this.gastEstimateTimeOut) |
|
||||||
} |
|
||||||
|
|
||||||
this.gastEstimateTimeOut = window.setTimeout(async () => { |
|
||||||
this.setGasEstimates() |
|
||||||
}, 500) |
|
||||||
|
|
||||||
console.log("INDEX", this._index) |
|
||||||
this.emit('astFinished') |
|
||||||
} |
|
||||||
|
|
||||||
this.compiler = new Compiler((url, cb) => this.call('contentImport', 'resolveAndSave', url, undefined, true).then((result) => cb(null, result)).catch((error) => cb(error.message))) |
|
||||||
this.compiler.event.register('compilationFinished', this.onAstFinished) |
|
||||||
} |
|
||||||
|
|
||||||
// COMPILER
|
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async compile() { |
|
||||||
try { |
|
||||||
const state = await this.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', state.useFileConfiguration) |
|
||||||
this.currentFile = await this.call('fileManager', 'file') |
|
||||||
console.log(this.currentFile) |
|
||||||
if (!this.currentFile) return |
|
||||||
const content = await this.call('fileManager', 'readFile', this.currentFile) |
|
||||||
const sources = { [this.currentFile]: { content } } |
|
||||||
this.compiler.compile(sources, this.currentFile) |
|
||||||
} catch (e) { |
|
||||||
console.log(e) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getLastCompilationResult() { |
|
||||||
return this.lastCompilationResult |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* 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 t0 = performance.now(); |
|
||||||
const ast = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true }) |
|
||||||
const t1 = performance.now(); |
|
||||||
console.log(`Call to doSomething took ${t1 - t0} milliseconds.`); |
|
||||||
console.log('AST PARSE SUCCESS', ast) |
|
||||||
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.currentFile = await this.call('fileManager', 'file') |
|
||||||
if (!this.currentFile) return |
|
||||||
const fileContent = text || await this.call('fileManager', 'readFile', this.currentFile) |
|
||||||
try { |
|
||||||
const ast = await this.parseSolidity(fileContent) |
|
||||||
this.currentFileAST = ast |
|
||||||
} catch (e) { |
|
||||||
console.log(e) |
|
||||||
} |
|
||||||
return this.currentFileAST |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Builds a flat index and declarations of all the nodes in the compilation result |
|
||||||
* @param compilationResult
|
|
||||||
* @param source
|
|
||||||
*/ |
|
||||||
_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) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NODE HELPERS
|
|
||||||
|
|
||||||
_getInputParams(node) { |
|
||||||
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(node: any, contractName: string, fileName: string, compilatioResult: any) { |
|
||||||
const index = {} |
|
||||||
const callback = (node) => { |
|
||||||
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) |
|
||||||
node.functionName = node.name + this._getInputParams(node) |
|
||||||
index[node.id] = node |
|
||||||
} |
|
||||||
this.astWalker.walkFull(node, callback) |
|
||||||
return index |
|
||||||
} |
|
||||||
|
|
||||||
_extractFileNodes(fileName: string, compilatioResult: any) { |
|
||||||
const source = compilatioResult.data.sources[fileName] |
|
||||||
const nodesByContract = [] |
|
||||||
this.astWalker.walkFull(source.ast, (node) => { |
|
||||||
if (node.nodeType === 'ContractDefinition') { |
|
||||||
const flatNodes = this._flatNodeList(node, node.name, fileName, compilatioResult) |
|
||||||
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilatioResult) |
|
||||||
nodesByContract[node.name] = { contractDefinition: node, contractNodes: flatNodes } |
|
||||||
} |
|
||||||
}) |
|
||||||
return nodesByContract |
|
||||||
} |
|
||||||
|
|
||||||
_getContractGasEstimate(node: any, contractName: string, fileName: string, compilationResult: any) { |
|
||||||
|
|
||||||
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 |
|
||||||
if (node.nodeType === 'ContractDefinition') { |
|
||||||
return { |
|
||||||
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, |
|
||||||
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, |
|
||||||
} |
|
||||||
} |
|
||||||
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 } |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* 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 getBlockAtPosition(position: any, text: string = null) { |
|
||||||
await this.getCurrentFileAST(text) |
|
||||||
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] |
|
||||||
|
|
||||||
const walkAst = (node) => { |
|
||||||
console.log(node) |
|
||||||
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.currentFileAST) return |
|
||||||
return walkAst(this.currentFileAST) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 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 = []; |
|
||||||
(SolidityParser as any).visit(this.currentFileAST, { |
|
||||||
StateVariableDeclaration: (node) => { |
|
||||||
if (node.variables) { |
|
||||||
for (const variable of node.variables) { |
|
||||||
nodes.push({ ...variable, nodeType: 'VariableDeclaration' }) |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
VariableDeclaration: (node) => { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
UserDefinedTypeName: (node) => { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
FunctionDefinition: (node) => { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
ContractDefinition: (node) => { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
MemberAccess: function (node) { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
Identifier: function (node) { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
EventDefinition: function (node) { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
ModifierDefinition: function (node) { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
}, |
|
||||||
InvalidNode: function (node) { |
|
||||||
nodes.push({ ...node, nodeType: node.type }) |
|
||||||
} |
|
||||||
}) |
|
||||||
console.log("LIST NODES", nodes) |
|
||||||
return nodes |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Nodes at position where position is a number, offset |
|
||||||
* @param position
|
|
||||||
* @param type
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async nodesAtPosition(position: number, type = '') { |
|
||||||
const lastCompilationResult = this.lastCompilationResult |
|
||||||
if (!lastCompilationResult) return false |
|
||||||
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) |
|
||||||
console.log('URL FROM PATH', urlFromPath) |
|
||||||
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { |
|
||||||
const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) |
|
||||||
return nodes |
|
||||||
} |
|
||||||
return [] |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getNodeById(id: any) { |
|
||||||
for (const key in this._index.FlatReferences) { |
|
||||||
if (this._index.FlatReferences[key].id === id) { |
|
||||||
return this._index.FlatReferences[key] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getDeclaration(id: any) { |
|
||||||
if (this._index.Declarations && this._index.Declarations[id]) return this._index.Declarations[id] |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param scope
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getNodesWithScope(scope: number) { |
|
||||||
const nodes = [] |
|
||||||
for (const node of Object.values(this._index.FlatReferences) as any[]) { |
|
||||||
if (node.scope === scope) nodes.push(node) |
|
||||||
} |
|
||||||
return nodes |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getNodesWithName(name: string) { |
|
||||||
const nodes = [] |
|
||||||
for (const node of Object.values(this._index.FlatReferences) as any[]) { |
|
||||||
if (node.name === name) nodes.push(node) |
|
||||||
} |
|
||||||
return nodes |
|
||||||
} |
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
declarationOf(node: AstNode) { |
|
||||||
if (node && node.referencedDeclaration) { |
|
||||||
return this._index.FlatReferences[node.referencedDeclaration] |
|
||||||
} else { |
|
||||||
// console.log(this._index.FlatReferences)
|
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param position
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async definitionAtPosition(position: number) { |
|
||||||
const nodes = await this.nodesAtPosition(position) |
|
||||||
console.log('nodes at position', nodes, position) |
|
||||||
console.log(this._index.FlatReferences) |
|
||||||
let nodeDefinition: any |
|
||||||
let node: any |
|
||||||
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._index.FlatReferences) { |
|
||||||
if (this._index.FlatReferences[key].id === node.sourceUnit) { |
|
||||||
nodeDefinition = this._index.FlatReferences[key] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return nodeDefinition |
|
||||||
} else { |
|
||||||
const astNodes = await this.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 |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param identifierNode
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async findIdentifier(identifierNode: any) { |
|
||||||
const astNodes = await this.listAstNodes() |
|
||||||
for (const node of astNodes) { |
|
||||||
if (node.name === identifierNode.name && node.nodeType !== 'Identifier') { |
|
||||||
return node |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async positionOfDefinition(node: any): Promise<any | null> { |
|
||||||
if (node) { |
|
||||||
if (node.src) { |
|
||||||
console.log('positionOfDefinition', node) |
|
||||||
const position = sourceMappingDecoder.decode(node.src) |
|
||||||
if (position) { |
|
||||||
return position |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
* @param imported
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async resolveImports(node, imported = {}) { |
|
||||||
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { |
|
||||||
console.log('IMPORTING', node) |
|
||||||
const importNode = await this.getNodeById(node.sourceUnit) |
|
||||||
imported[importNode.id] = importNode |
|
||||||
if (importNode.nodes) { |
|
||||||
for (const child of importNode.nodes) { |
|
||||||
imported = await this.resolveImports(child, imported) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
console.log(imported) |
|
||||||
return imported |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param ast
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getLastNodeInLine(ast: string) { |
|
||||||
let lastNode |
|
||||||
const checkLastNode = (node) => { |
|
||||||
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) { |
|
||||||
checkLastNode(node) |
|
||||||
}, |
|
||||||
Identifier: function (node) { |
|
||||||
checkLastNode(node) |
|
||||||
} |
|
||||||
}) |
|
||||||
if (lastNode && lastNode.expression && lastNode.expression.expression) { |
|
||||||
console.log('lastNode with expression', lastNode, lastNode.expression) |
|
||||||
return lastNode.expression.expression |
|
||||||
} |
|
||||||
if (lastNode && lastNode.expression) { |
|
||||||
console.log('lastNode with expression', lastNode, lastNode.expression) |
|
||||||
return lastNode.expression |
|
||||||
} |
|
||||||
console.log('lastNode', lastNode) |
|
||||||
return lastNode |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
referencesOf(node: any) { |
|
||||||
const results = [] |
|
||||||
const highlights = (id) => { |
|
||||||
if (this._index.Declarations && this._index.Declarations[id]) { |
|
||||||
const refs = this._index.Declarations[id] |
|
||||||
for (const ref in refs) { |
|
||||||
const node = refs[ref] |
|
||||||
results.push(node) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if (node && node.referencedDeclaration) { |
|
||||||
highlights(node.referencedDeclaration) |
|
||||||
const current = this._index.FlatReferences[node.referencedDeclaration] |
|
||||||
results.push(current) |
|
||||||
} else { |
|
||||||
highlights(node.id) |
|
||||||
} |
|
||||||
return results |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param position
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async referrencesAtPosition(position: any) { |
|
||||||
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() { |
|
||||||
return this._index.FlatReferences |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getNodeLink(node: any) { |
|
||||||
const lineColumn = await this.getNodeLineColumn(node) |
|
||||||
return lineColumn ? `${lineColumn.fileName} ${lineColumn.position.start.line}:${lineColumn.position.start.column}` : null |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* @param node
|
|
||||||
*/ |
|
||||||
async getNodeLineColumn(node: any) { |
|
||||||
const position = await this.positionOfDefinition(node) |
|
||||||
if (position) { |
|
||||||
const fileName = this.lastCompilationResult.getSourceName(position.file) |
|
||||||
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.lastCompilationResult.source.sources[fileName].content) |
|
||||||
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks) |
|
||||||
return { |
|
||||||
fileName, |
|
||||||
position: lineColumn |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getNodeDocumentation(node: any) { |
|
||||||
if (node.documentation && node.documentation.text) { |
|
||||||
let text = '' |
|
||||||
node.documentation.text.split('\n').forEach(line => { |
|
||||||
text += `${line.trim()}\n` |
|
||||||
}) |
|
||||||
return text |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
* @returns
|
|
||||||
*/ |
|
||||||
async getVariableDeclaration(node: any) { |
|
||||||
if (node.typeDescriptions && node.typeDescriptions.typeString) { |
|
||||||
return `${node.typeDescriptions.typeString} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
|
||||||
} else { |
|
||||||
if (node.typeName && node.typeName.name) { |
|
||||||
return `${node.typeName.name} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
|
||||||
} |
|
||||||
else if (node.typeName && node.typeName.namePath) { |
|
||||||
return `${node.typeName.namePath} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
|
||||||
} |
|
||||||
else { |
|
||||||
return `${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @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(', ')})` |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*
|
|
||||||
* @param fileName
|
|
||||||
*/ |
|
||||||
async getGasEstimates(fileName: string) { |
|
||||||
if (!fileName) { |
|
||||||
fileName = await this.currentFile |
|
||||||
} |
|
||||||
if (this._index.NodesPerFile && this._index.NodesPerFile[fileName]) { |
|
||||||
const estimates = [] |
|
||||||
for (const contract in this._index.NodesPerFile[fileName]) { |
|
||||||
console.log(contract) |
|
||||||
const nodes = this._index.NodesPerFile[fileName][contract].contractNodes |
|
||||||
for (const node of Object.values(nodes) as any[]) { |
|
||||||
if (node.gasEstimate) { |
|
||||||
estimates.push({ |
|
||||||
node, |
|
||||||
range: await this.getNodeLineColumn(node) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return estimates |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
async setGasEstimates() { |
|
||||||
this.currentFile = await this.call('fileManager', 'file') |
|
||||||
this._index.NodesPerFile[this.currentFile] = await this._extractFileNodes(this.currentFile, this.lastCompilationResult) |
|
||||||
|
|
||||||
const gasEstimates = await this.getGasEstimates(this.currentFile) |
|
||||||
console.log('all estimates', gasEstimates) |
|
||||||
|
|
||||||
const friendlyNames = { |
|
||||||
'executionCost': 'Estimated execution cost', |
|
||||||
'codeDepositCost': 'Estimated code deposit cost', |
|
||||||
'creationCost': 'Estimated creation cost', |
|
||||||
} |
|
||||||
await this.call('editor', 'discardLineTexts') |
|
||||||
if (gasEstimates) { |
|
||||||
for (const estimate of gasEstimates) { |
|
||||||
console.log(estimate) |
|
||||||
const linetext: lineText = { |
|
||||||
content: Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${value} gas`).join(' '), |
|
||||||
position: estimate.range.position, |
|
||||||
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.call('editor', 'addLineText', linetext, estimate.range.fileName) |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,564 @@ |
|||||||
|
'use strict' |
||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||||
|
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||||
|
import { Compiler } from '@remix-project/remix-solidity' |
||||||
|
|
||||||
|
import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' |
||||||
|
import { helper } 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 CodeParserNodeHelper from './services/code-parser-node-helper' |
||||||
|
import React from 'react' |
||||||
|
import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' |
||||||
|
|
||||||
|
import { Profile } from '@remixproject/plugin-utils' |
||||||
|
// eslint-disable-next-line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const profile: Profile = { |
||||||
|
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'], |
||||||
|
events: [], |
||||||
|
version: '0.0.1' |
||||||
|
} |
||||||
|
|
||||||
|
export function isNodeDefinition(node: any) { |
||||||
|
return node.nodeType === 'ContractDefinition' || |
||||||
|
node.nodeType === 'FunctionDefinition' || |
||||||
|
node.nodeType === 'ModifierDefinition' || |
||||||
|
node.nodeType === 'VariableDeclaration' || |
||||||
|
node.nodeType === 'StructDefinition' || |
||||||
|
node.nodeType === 'EventDefinition' |
||||||
|
} |
||||||
|
|
||||||
|
export class CodeParser extends Plugin { |
||||||
|
|
||||||
|
currentFileAST: any // contains the simple parsed AST for the current file
|
||||||
|
|
||||||
|
lastCompilationResult: any |
||||||
|
currentFile: any |
||||||
|
_index: any |
||||||
|
astWalker: any |
||||||
|
errorState: boolean = false |
||||||
|
|
||||||
|
gastEstimateTimeOut: any |
||||||
|
|
||||||
|
gasService: CodeParserGasService |
||||||
|
compilerService: CodeParserCompiler |
||||||
|
antlrService: CodeParserAntlrService |
||||||
|
nodeHelper: CodeParserNodeHelper |
||||||
|
|
||||||
|
parseSolidity: (text: string) => Promise<any> |
||||||
|
getLastNodeInLine: (ast: string) => Promise<any> |
||||||
|
listAstNodes: () => Promise<any> |
||||||
|
getBlockAtPosition: (position: any, text?: string) => Promise<any> |
||||||
|
|
||||||
|
constructor(astWalker) { |
||||||
|
super(profile) |
||||||
|
this.astWalker = astWalker |
||||||
|
this._index = { |
||||||
|
Declarations: {}, |
||||||
|
FlatReferences: {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async onActivation() { |
||||||
|
|
||||||
|
this.gasService = new CodeParserGasService(this) |
||||||
|
this.compilerService = new CodeParserCompiler(this) |
||||||
|
this.antlrService = new CodeParserAntlrService(this) |
||||||
|
this.nodeHelper = new CodeParserNodeHelper(this) |
||||||
|
|
||||||
|
this.parseSolidity = this.antlrService.parseSolidity |
||||||
|
this.getLastNodeInLine = this.antlrService.getLastNodeInLine |
||||||
|
this.listAstNodes = this.antlrService.listAstNodes |
||||||
|
this.getBlockAtPosition = this.antlrService.getBlockAtPosition |
||||||
|
|
||||||
|
this.on('editor', 'didChangeFile', async (file) => { |
||||||
|
console.log('contentChanged', file) |
||||||
|
await this.call('editor', 'discardLineTexts') |
||||||
|
await this.antlrService.getCurrentFileAST() |
||||||
|
await this.compilerService.compile() |
||||||
|
}) |
||||||
|
|
||||||
|
this.on('filePanel', 'setWorkspace', async () => { |
||||||
|
await this.call('fileDecorator', 'setFileDecorators', []) |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
this.on('fileManager', 'currentFileChanged', async () => { |
||||||
|
await this.call('editor', 'discardLineTexts') |
||||||
|
await this.antlrService.getCurrentFileAST() |
||||||
|
await this.compilerService.compile() |
||||||
|
}) |
||||||
|
|
||||||
|
this.on('solidity', 'loadingCompiler', async (url) => { |
||||||
|
console.log('loading compiler', url) |
||||||
|
this.compilerService.compiler.loadVersion(true, url) |
||||||
|
this.compilerService.compiler.event.register('compilerLoaded', async () => { |
||||||
|
console.log('compiler loaded') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
await this.compilerService.init() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getLastCompilationResult() { |
||||||
|
return this.lastCompilationResult |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a flat index and declarations of all the nodes in the compilation result |
||||||
|
* @param compilationResult
|
||||||
|
* @param source
|
||||||
|
*/ |
||||||
|
_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) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// NODE HELPERS
|
||||||
|
|
||||||
|
_getInputParams(node) { |
||||||
|
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(node: any, contractName: string, fileName: string, compilatioResult: any) { |
||||||
|
const index = {} |
||||||
|
const callback = (node) => { |
||||||
|
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) |
||||||
|
node.functionName = node.name + this._getInputParams(node) |
||||||
|
index[node.id] = node |
||||||
|
} |
||||||
|
this.astWalker.walkFull(node, callback) |
||||||
|
return index |
||||||
|
} |
||||||
|
|
||||||
|
_extractFileNodes(fileName: string, compilatioResult: any) { |
||||||
|
if (compilatioResult && compilatioResult.data.sources && compilatioResult.data.sources[fileName]) { |
||||||
|
const source = compilatioResult.data.sources[fileName] |
||||||
|
const nodesByContract = [] |
||||||
|
this.astWalker.walkFull(source.ast, (node) => { |
||||||
|
if (node.nodeType === 'ContractDefinition') { |
||||||
|
const flatNodes = this._flatNodeList(node, node.name, fileName, compilatioResult) |
||||||
|
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilatioResult) |
||||||
|
nodesByContract[node.name] = { contractDefinition: node, contractNodes: flatNodes } |
||||||
|
} |
||||||
|
}) |
||||||
|
return nodesByContract |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_getContractGasEstimate(node: any, contractName: string, fileName: string, compilationResult: any) { |
||||||
|
|
||||||
|
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 |
||||||
|
if (node.nodeType === 'ContractDefinition') { |
||||||
|
return { |
||||||
|
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, |
||||||
|
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, |
||||||
|
} |
||||||
|
} |
||||||
|
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 } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Nodes at position where position is a number, offset |
||||||
|
* @param position
|
||||||
|
* @param type
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async nodesAtPosition(position: number, type = '') { |
||||||
|
const lastCompilationResult = this.lastCompilationResult |
||||||
|
if (!lastCompilationResult) return false |
||||||
|
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) |
||||||
|
console.log('URL FROM PATH', urlFromPath) |
||||||
|
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { |
||||||
|
const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) |
||||||
|
return nodes |
||||||
|
} |
||||||
|
return [] |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getNodeById(id: any) { |
||||||
|
for (const key in this._index.FlatReferences) { |
||||||
|
if (this._index.FlatReferences[key].id === id) { |
||||||
|
return this._index.FlatReferences[key] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getDeclaration(id: any) { |
||||||
|
if (this._index.Declarations && this._index.Declarations[id]) return this._index.Declarations[id] |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param scope
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getNodesWithScope(scope: number) { |
||||||
|
const nodes = [] |
||||||
|
for (const node of Object.values(this._index.FlatReferences) as any[]) { |
||||||
|
if (node.scope === scope) nodes.push(node) |
||||||
|
} |
||||||
|
return nodes |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getNodesWithName(name: string) { |
||||||
|
const nodes = [] |
||||||
|
for (const node of Object.values(this._index.FlatReferences) as any[]) { |
||||||
|
if (node.name === name) nodes.push(node) |
||||||
|
} |
||||||
|
return nodes |
||||||
|
} |
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
declarationOf(node: AstNode) { |
||||||
|
if (node && node.referencedDeclaration) { |
||||||
|
return this._index.FlatReferences[node.referencedDeclaration] |
||||||
|
} else { |
||||||
|
// console.log(this._index.FlatReferences)
|
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param position
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async definitionAtPosition(position: number) { |
||||||
|
const nodes = await this.nodesAtPosition(position) |
||||||
|
console.log('nodes at position', nodes, position) |
||||||
|
console.log(this._index.FlatReferences) |
||||||
|
let nodeDefinition: any |
||||||
|
let node: any |
||||||
|
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._index.FlatReferences) { |
||||||
|
if (this._index.FlatReferences[key].id === node.sourceUnit) { |
||||||
|
nodeDefinition = this._index.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 |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @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: any): Promise<any | null> { |
||||||
|
if (node) { |
||||||
|
if (node.src) { |
||||||
|
console.log('positionOfDefinition', node) |
||||||
|
const position = sourceMappingDecoder.decode(node.src) |
||||||
|
if (position) { |
||||||
|
return position |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @param imported
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async resolveImports(node, imported = {}) { |
||||||
|
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { |
||||||
|
console.log('IMPORTING', node) |
||||||
|
const importNode = await this.getNodeById(node.sourceUnit) |
||||||
|
imported[importNode.id] = importNode |
||||||
|
if (importNode.nodes) { |
||||||
|
for (const child of importNode.nodes) { |
||||||
|
imported = await this.resolveImports(child, imported) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
console.log(imported) |
||||||
|
return imported |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
referencesOf(node: any) { |
||||||
|
const results = [] |
||||||
|
const highlights = (id) => { |
||||||
|
if (this._index.Declarations && this._index.Declarations[id]) { |
||||||
|
const refs = this._index.Declarations[id] |
||||||
|
for (const ref in refs) { |
||||||
|
const node = refs[ref] |
||||||
|
results.push(node) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (node && node.referencedDeclaration) { |
||||||
|
highlights(node.referencedDeclaration) |
||||||
|
const current = this._index.FlatReferences[node.referencedDeclaration] |
||||||
|
results.push(current) |
||||||
|
} else { |
||||||
|
highlights(node.id) |
||||||
|
} |
||||||
|
return results |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param position
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async referrencesAtPosition(position: any) { |
||||||
|
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() { |
||||||
|
return this._index.FlatReferences |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getNodeLink(node: any) { |
||||||
|
const lineColumn = await this.getLineColumnOfNode(node) |
||||||
|
const position = await this.positionOfDefinition(node) |
||||||
|
if (this.lastCompilationResult && this.lastCompilationResult.sources) { |
||||||
|
const fileName = this.lastCompilationResult.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.lastCompilationResult.getSourceName(position.file) |
||||||
|
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.lastCompilationResult.source.sources[fileName].content) |
||||||
|
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks) |
||||||
|
return lineColumn |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getNodeDocumentation(node: any) { |
||||||
|
if (node.documentation && node.documentation.text) { |
||||||
|
let text = '' |
||||||
|
node.documentation.text.split('\n').forEach(line => { |
||||||
|
text += `${line.trim()}\n` |
||||||
|
}) |
||||||
|
return text |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getVariableDeclaration(node: any) { |
||||||
|
if (node.typeDescriptions && node.typeDescriptions.typeString) { |
||||||
|
return `${node.typeDescriptions.typeString} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
||||||
|
} else { |
||||||
|
if (node.typeName && node.typeName.name) { |
||||||
|
return `${node.typeName.name} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
||||||
|
} |
||||||
|
else if (node.typeName && node.typeName.namePath) { |
||||||
|
return `${node.typeName.namePath} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
||||||
|
} |
||||||
|
else { |
||||||
|
return `${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @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,160 @@ |
|||||||
|
'use strict' |
||||||
|
|
||||||
|
import { CodeParser } from "../code-parser" |
||||||
|
|
||||||
|
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 t0 = performance.now(); |
||||||
|
const ast = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true }) |
||||||
|
const t1 = performance.now(); |
||||||
|
console.log(`Call to doSomething took ${t1 - t0} milliseconds.`); |
||||||
|
console.log('AST PARSE SUCCESS', ast) |
||||||
|
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) return |
||||||
|
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) |
||||||
|
try { |
||||||
|
const ast = await this.parseSolidity(fileContent) |
||||||
|
this.plugin.currentFileAST = ast |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
return this.plugin.currentFileAST |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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: any = []; |
||||||
|
(SolidityParser as any).visit(this.plugin.currentFileAST, { |
||||||
|
StateVariableDeclaration: (node) => { |
||||||
|
if (node.variables) { |
||||||
|
for (const variable of node.variables) { |
||||||
|
nodes.push({ ...variable, nodeType: 'VariableDeclaration' }) |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
VariableDeclaration: (node) => { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
UserDefinedTypeName: (node) => { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
FunctionDefinition: (node) => { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
ContractDefinition: (node) => { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
MemberAccess: function (node) { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
Identifier: function (node) { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
EventDefinition: function (node) { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
ModifierDefinition: function (node) { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
}, |
||||||
|
InvalidNode: function (node) { |
||||||
|
nodes.push({ ...node, nodeType: node.type }) |
||||||
|
} |
||||||
|
}) |
||||||
|
console.log("LIST NODES", nodes) |
||||||
|
return nodes |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param ast
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async getLastNodeInLine(ast: string) { |
||||||
|
let lastNode |
||||||
|
const checkLastNode = (node) => { |
||||||
|
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) { |
||||||
|
checkLastNode(node) |
||||||
|
}, |
||||||
|
Identifier: function (node) { |
||||||
|
checkLastNode(node) |
||||||
|
} |
||||||
|
}) |
||||||
|
if (lastNode && lastNode.expression && lastNode.expression.expression) { |
||||||
|
console.log('lastNode with expression', lastNode, lastNode.expression) |
||||||
|
return lastNode.expression.expression |
||||||
|
} |
||||||
|
if (lastNode && lastNode.expression) { |
||||||
|
console.log('lastNode with expression', lastNode, lastNode.expression) |
||||||
|
return lastNode.expression |
||||||
|
} |
||||||
|
console.log('lastNode', lastNode) |
||||||
|
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 getBlockAtPosition(position: any, text: string = null) { |
||||||
|
await this.getCurrentFileAST(text) |
||||||
|
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] |
||||||
|
|
||||||
|
const walkAst = (node) => { |
||||||
|
console.log(node) |
||||||
|
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.currentFileAST) return |
||||||
|
return walkAst(this.plugin.currentFileAST) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,198 @@ |
|||||||
|
'use strict' |
||||||
|
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||||
|
import { Compiler } from '@remix-project/remix-solidity' |
||||||
|
|
||||||
|
import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' |
||||||
|
import { helper } from '@remix-project/remix-solidity' |
||||||
|
import { CodeParser } from "../code-parser"; |
||||||
|
import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' |
||||||
|
|
||||||
|
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) => { |
||||||
|
console.log('compile success', success, data, this) |
||||||
|
this.plugin.call('editor', 'clearAnnotations') |
||||||
|
this.errorState = true |
||||||
|
const checkIfFatalError = (error: CompilationError) => { |
||||||
|
// Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround
|
||||||
|
const isValidError = (error.message && error.message.includes('Deferred import')) ? false : error.severity !== 'warning' |
||||||
|
if (isValidError) { |
||||||
|
console.log(error) |
||||||
|
} |
||||||
|
} |
||||||
|
const result = new CompilerAbstract('soljson', data, source, input) |
||||||
|
|
||||||
|
if (data.error) checkIfFatalError(data.error) |
||||||
|
if (data.errors) data.errors.forEach((err) => checkIfFatalError(err)) |
||||||
|
const allErrors: any = [] |
||||||
|
if (data.errors) { |
||||||
|
const sources = result.getSourceCode().sources |
||||||
|
for (const error of data.errors) { |
||||||
|
const pos = helper.getPositionDetails(error.formattedMessage) |
||||||
|
const filePosition = Object.keys(sources).findIndex((fileName) => fileName === error.sourceLocation.file) |
||||||
|
const lineColumn = await this.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', |
||||||
|
{ |
||||||
|
start: error.sourceLocation.start, |
||||||
|
length: error.sourceLocation.end - error.sourceLocation.start |
||||||
|
}, |
||||||
|
filePosition, |
||||||
|
result.getSourceCode().sources, |
||||||
|
null) |
||||||
|
allErrors.push({ error, lineColumn }) |
||||||
|
} |
||||||
|
console.log('allErrors', allErrors) |
||||||
|
await this.plugin.call('editor', 'addErrorMarker', allErrors) |
||||||
|
|
||||||
|
const errorsPerFiles = {} |
||||||
|
for (const error of allErrors) { |
||||||
|
if (!errorsPerFiles[error.error.sourceLocation.file]) { |
||||||
|
errorsPerFiles[error.error.sourceLocation.file] = [] |
||||||
|
} |
||||||
|
errorsPerFiles[error.error.sourceLocation.file].push(error.error) |
||||||
|
} |
||||||
|
|
||||||
|
const errorPriority = { |
||||||
|
'error': 0, |
||||||
|
'warning': 1, |
||||||
|
} |
||||||
|
|
||||||
|
// sort errorPerFiles by error priority
|
||||||
|
const sortedErrorsPerFiles = {} |
||||||
|
for (const fileName in errorsPerFiles) { |
||||||
|
const errors = errorsPerFiles[fileName] |
||||||
|
errors.sort((a, b) => { |
||||||
|
return errorPriority[a.severity] - errorPriority[b.severity] |
||||||
|
} |
||||||
|
) |
||||||
|
sortedErrorsPerFiles[fileName] = errors |
||||||
|
} |
||||||
|
console.log('sortedErrorsPerFiles', sortedErrorsPerFiles) |
||||||
|
|
||||||
|
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName]) |
||||||
|
|
||||||
|
console.log('filesWithOutErrors', filesWithOutErrors) |
||||||
|
// 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 === 'error' ? fileDecorationType.Error : fileDecorationType.Warning, |
||||||
|
fileStateLabelClass: errors[0].severity === 'error' ? 'text-danger' : 'text-warning', |
||||||
|
fileStateIconClass: '', |
||||||
|
fileStateIcon: '', |
||||||
|
text: errors.length, |
||||||
|
owner: 'code-parser', |
||||||
|
bubble: true, |
||||||
|
commment: 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) |
||||||
|
} |
||||||
|
console.log(decorators) |
||||||
|
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) |
||||||
|
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors) |
||||||
|
} else { |
||||||
|
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources) |
||||||
|
const decorators: fileDecoration[] = [] |
||||||
|
for (const fileName of Object.keys(result.getSourceCode().sources)) { |
||||||
|
const decorator: fileDecoration = { |
||||||
|
path: fileName, |
||||||
|
isDirectory: false, |
||||||
|
fileStateType: fileDecorationType.None, |
||||||
|
fileStateLabelClass: '', |
||||||
|
fileStateIconClass: '', |
||||||
|
fileStateIcon: '', |
||||||
|
text: '', |
||||||
|
owner: 'code-parser', |
||||||
|
bubble: false |
||||||
|
} |
||||||
|
decorators.push(decorator) |
||||||
|
} |
||||||
|
console.log(decorators) |
||||||
|
|
||||||
|
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if (!data.sources) return |
||||||
|
if (data.sources && Object.keys(data.sources).length === 0) return |
||||||
|
this.plugin.lastCompilationResult = new CompilerAbstract('soljson', data, source, input) |
||||||
|
|
||||||
|
this.errorState = false |
||||||
|
this.plugin._index = { |
||||||
|
Declarations: {}, |
||||||
|
FlatReferences: {}, |
||||||
|
NodesPerFile: {}, |
||||||
|
} |
||||||
|
this.plugin._buildIndex(data, source) |
||||||
|
|
||||||
|
if (this.gastEstimateTimeOut) { |
||||||
|
window.clearTimeout(this.gastEstimateTimeOut) |
||||||
|
} |
||||||
|
|
||||||
|
this.gastEstimateTimeOut = window.setTimeout(async () => { |
||||||
|
await this.plugin.gasService.showGasEstimates() |
||||||
|
}, 1000) |
||||||
|
|
||||||
|
console.log("INDEX", this.plugin._index) |
||||||
|
this.plugin.emit('astFinished') |
||||||
|
} |
||||||
|
|
||||||
|
this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined, true).then((result) => cb(null, result)).catch((error) => cb(error.message))) |
||||||
|
this.compiler.event.register('compilationFinished', this.onAstFinished) |
||||||
|
} |
||||||
|
|
||||||
|
// COMPILER
|
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
async compile() { |
||||||
|
try { |
||||||
|
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', state.useFileConfiguration) |
||||||
|
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') |
||||||
|
console.log(this.plugin.currentFile) |
||||||
|
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) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
import { CodeParser } 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._index.NodesPerFile && this.plugin._index.NodesPerFile[fileName]) { |
||||||
|
const estimates: any = [] |
||||||
|
for (const contract in this.plugin._index.NodesPerFile[fileName]) { |
||||||
|
console.log(contract) |
||||||
|
const nodes = this.plugin._index.NodesPerFile[fileName][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() { |
||||||
|
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') |
||||||
|
this.plugin._index.NodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.lastCompilationResult) |
||||||
|
|
||||||
|
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile) |
||||||
|
console.log('all estimates', gasEstimates) |
||||||
|
|
||||||
|
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) { |
||||||
|
console.log(estimate) |
||||||
|
const linetext: lineText = { |
||||||
|
content: Object.entries(estimate.node.gasEstimate).map(([key, 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,8 @@ |
|||||||
|
import { CodeParser } from "../code-parser" |
||||||
|
|
||||||
|
export default class CodeParserNodeHelper { |
||||||
|
plugin: CodeParser |
||||||
|
constructor(plugin: CodeParser) { |
||||||
|
this.plugin = plugin |
||||||
|
} |
||||||
|
} |
@ -1,46 +0,0 @@ |
|||||||
import { lineText } from "../remix-ui-editor" |
|
||||||
|
|
||||||
export class RemixCodeLensProvider { |
|
||||||
props: any |
|
||||||
monaco: any |
|
||||||
constructor(props: any, monaco: any) { |
|
||||||
this.props = props |
|
||||||
this.monaco = monaco |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
async provideCodeLenses(model: any, token: any) { |
|
||||||
|
|
||||||
const gasEstimates = await this.props.plugin.call('codeParser', 'getGasEstimates') |
|
||||||
const decorations = [] |
|
||||||
const friendlyNames = { |
|
||||||
'executionCost': 'Execution Cost', |
|
||||||
'codeDepositCost': 'Code Deposit Cost', |
|
||||||
'creationCost': 'Creation Cost', |
|
||||||
} |
|
||||||
|
|
||||||
/* if (gasEstimates) { |
|
||||||
for (const estimate of gasEstimates) { |
|
||||||
console.log(estimate) |
|
||||||
const linetext: lineText = { |
|
||||||
content: Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' '), |
|
||||||
position: estimate.range.position, |
|
||||||
hide: false, |
|
||||||
className: 'remix-code-lens', |
|
||||||
from: 'remix-code-lens', |
|
||||||
} |
|
||||||
|
|
||||||
this.props.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName) |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} */ |
|
||||||
|
|
||||||
return { |
|
||||||
lenses: [], |
|
||||||
dispose: () => { } |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,57 @@ |
|||||||
|
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) |
||||||
|
console.log("JUMP", sourcePosition) |
||||||
|
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')) { |
||||||
|
console.log('jump to file', fileName) |
||||||
|
await this.props.plugin.call('contentImport', 'resolveAndSave', fileName, null, true) |
||||||
|
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')
|
||||||
|
console.log(lastCompilationResult.getSourceCode().sources) |
||||||
|
console.log(position) |
||||||
|
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) |
||||||
|
// TODO: refactor with rendererAPI.errorClick
|
||||||
|
console.log(filename, lineColumn) |
||||||
|
jumpToLine(filename, lineColumn) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
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[]> { |
||||||
|
|
||||||
|
console.log(model.uri) |
||||||
|
const cursorPosition = this.props.editorAPI.getCursorPosition() |
||||||
|
const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition) |
||||||
|
//console.log('nodes for highlights', nodes)
|
||||||
|
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) |
||||||
|
console.log(fileInNode, 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, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
console.log('highlights', highlights) |
||||||
|
return highlights |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue