parent
2356bd8e0d
commit
12d0c6a25e
@ -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