higlight provider

editorcontextDummy
filip mertens 2 years ago
parent 2356bd8e0d
commit 12d0c6a25e
  1. 2
      apps/remix-ide/src/app.js
  2. 1
      apps/remix-ide/src/app/panels/tab-proxy.js
  3. 900
      apps/remix-ide/src/app/plugins/code-parser.tsx
  4. 1
      apps/remix-ide/src/app/plugins/file-decorator.ts
  5. 564
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  6. 160
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  7. 198
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  8. 72
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  9. 8
      apps/remix-ide/src/app/plugins/parser/services/code-parser-node-helper.ts
  10. 1
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  11. 39
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  12. 1
      libs/remix-debug/src/source/sourceMappingDecoder.ts
  13. 46
      libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts
  14. 2
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  15. 57
      libs/remix-ui/editor/src/lib/providers/definitionProvider.ts
  16. 45
      libs/remix-ui/editor/src/lib/providers/highlightProvider.ts
  17. 6
      libs/remix-ui/editor/src/lib/providers/hoverProvider.ts
  18. 18
      libs/remix-ui/editor/src/lib/providers/referenceProvider.ts
  19. 38
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  20. 2
      libs/remix-ui/editor/src/types/monaco.ts
  21. 2
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx

@ -14,7 +14,7 @@ import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/code-parser'
import { CodeParser } from './app/plugins/parser/code-parser'
import { FileDecorator } from './app/plugins/file-decorator'
import { WalkthroughService } from './walkthroughService'

@ -322,7 +322,6 @@ export class TabProxy extends Plugin {
}
renderComponent () {
console.log('rendering tabs')
const onSelect = (index) => {
if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name

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

@ -49,7 +49,6 @@ export class FileDecorator extends Plugin {
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
console.log('fileStates', this._fileStates)
this.emit('fileDecoratorsChanged', this._fileStates)
}
}

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

@ -125,7 +125,6 @@ export class CompilerImports extends Plugin {
* @returns {Promise} - string content
*/
async resolveAndSave(url: string, targetPath: string, save: boolean = true) {
console.log(url, targetPath, save)
try {
if (targetPath && this.currentRequest) {
const canCall = await this.askUserPermission('resolveAndSave', 'This action will update the path ' + targetPath)

@ -69,47 +69,8 @@ export class EditorContextListener extends Plugin {
return [...this._activeHighlights]
}
async jumpToDefinition(position: any) {
const node = await this.call('codeParser', 'definitionAtPosition', position)
const sourcePosition = await this.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.call('fileManager', 'file')) {
console.log('jump to file', fileName)
await this.call('contentImport', 'resolveAndSave', fileName, null, true)
await this.call('fileManager', 'open', fileName)
}
if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) {
this.call('editor', 'gotoLine', lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = await this.call('codeParser', 'getLastCompilationResult') // await this.call('compilerArtefacts', 'getLastCompilationResult')
console.log(lastCompilationResult.getSourceCode().sources)
console.log(position)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn',
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick
console.log(filename, lineColumn)
jumpToLine(filename, lineColumn)
}
}
async _highlightItems(cursorPosition, compilationResult, file) {
if (this.currentPosition === cursorPosition) return
this._stopHighlighting()

@ -68,7 +68,6 @@ export function getLinebreakPositions (source) {
* @return {Object} returns an object {start: {line, column}, end: {line, column}} (line/column count start at 0)
*/
export function convertOffsetToLineColumn (sourceLocation, lineBreakPositions) {
console.log(sourceLocation, lineBreakPositions)
if (sourceLocation.start >= 0 && sourceLocation.length >= 0) {
return {
start: convertFromCharPosition(sourceLocation.start, lineBreakPositions),

@ -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: () => { }
};
}
}

@ -15,7 +15,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
}
triggerCharacters = ['.', '']
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext) {
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> {
console.log('AUTOCOMPLETE', context)
console.log(position)

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

@ -1,16 +1,17 @@
import { Monaco } from '@monaco-editor/react'
import { editor, languages, Position } from 'monaco-editor'
import { EditorUIProps } from '../remix-ui-editor'
export class RemixHoverProvider implements languages.HoverProvider {
props: EditorUIProps
monaco: any
monaco: Monaco
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
provideHover = async function (model: editor.ITextModel, position: Position) {
provideHover = async function (model: editor.ITextModel, position: Position): Promise<languages.Hover> {
console.log('HOVERING')
const cursorPosition = this.props.editorAPI.getHoverPosition(position)
@ -25,6 +26,7 @@ export class RemixHoverProvider implements languages.HoverProvider {
})
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const getScope = async (node: any) => {
if (node.id) {
contents.push({

@ -1,14 +1,18 @@
import { Monaco } from "@monaco-editor/react"
import { sourceMappingDecoder } from "@remix-project/remix-debug"
import monaco from "../../types/monaco"
import { EditorUIProps } from "../remix-ui-editor"
export class RemixReferenceProvider {
props: any
monaco: any
export class RemixReferenceProvider implements monaco.languages.ReferenceProvider {
props: EditorUIProps
monaco: Monaco
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
async provideReferences(model: any, position: any, context: any, token: any) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async provideReferences(model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.ReferenceContext, token: monaco.CancellationToken) {
const cursorPosition = this.props.editorAPI.getCursorPosition()
const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition)
@ -23,11 +27,7 @@ export class RemixReferenceProvider {
let fileTarget = await this.props.plugin.call('fileManager', 'getPathFromUrl', fileInNode)
fileTarget = fileTarget.file
const fileContent = await this.props.plugin.call('fileManager', 'readFile', fileInNode)
const lineColumn = await this.props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn',
position,
position.file,
compilationResult.getSourceCode().sources,
compilationResult.getAsts())
const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position)
console.log(position, fileTarget, lineColumn)
try {
this.props.plugin.call('editor', 'addModel', fileTarget, fileContent)

@ -13,7 +13,8 @@ import { IMarkdownString, IPosition, MarkerSeverity } from 'monaco-editor'
import { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider'
import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixCodeLensProvider } from './providers/codeLensProvider'
import { RemixHighLightProvider } from './providers/highlightProvider'
import { RemixDefinitionProvider } from './providers/definitionProvider'
type sourceAnnotation = {
row: number,
@ -524,6 +525,7 @@ export const EditorUI = (props: EditorUIProps) => {
function handleEditorWillMount(monaco: Monaco) {
// MonacoEditorTextDecorationPatch.augmentEditor(monaco.editor)
console.log('editor will mount', monaco, typeof monaco)
monacoRef.current = monaco
// Register a new language
monacoRef.current.languages.register({ id: 'remix-solidity' })
@ -535,47 +537,21 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.setMonarchTokensProvider('remix-cairo', cairoLang as any)
monacoRef.current.languages.setLanguageConfiguration('remix-cairo', cairoConf as any)
// register Definition Provider
monacoRef.current.languages.registerDefinitionProvider('remix-solidity', {
provideDefinition(model: any, position: any, token: any) {
const cursorPosition = props.editorAPI.getCursorPosition()
props.plugin.call('contextualListener', 'jumpToDefinition', cursorPosition)
return null
}
})
monacoRef.current.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
});
monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', {
provideDocumentHighlights(model: any, position: any, token: any) {
console.log('HIghlight', position)
const hightlights = [
{
range: new monacoRef.current.Range(position.lineNumber, position.column, position.lineNumber, position.column + 5),
kind: monacoRef.current.languages.DocumentHighlightKind.Write
}
]
return hightlights
}
})
// monacoRef.current.languages.registerCodeLensProvider('remix-solidity', new RemixCodeLensProvider(props, monaco))
monacoRef.current.languages.registerDefinitionProvider('remix-solidity', new RemixDefinitionProvider(props, monaco))
monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', new RemixHighLightProvider(props, monaco))
monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco))
monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco))
monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco))
// monacoRef.current.languages.registerSignatureHelpProvider('remix-solidity', new RemixSignatureProvider(props, monaco))
loadTypes(monacoRef.current)
monacoRef.current.languages.registerDefinitionProvider('typescript', {
provideDefinition(model: any, position: any, token: any) {
console.log(token)
return null
}
})
}
return (

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

@ -74,7 +74,6 @@ export const TabsUI = (props: TabsUIProps) => {
const getFileDecorationClasses = (tab: any) => {
console.log('TAB', tab, tabsState.fileDecorations)
const fileDecoration = tabsState.fileDecorations.find((fileDecoration: fileDecoration) => {
if(`${fileDecoration.workspace.name}/${fileDecoration.path}` === tab.name) return true
})
@ -87,7 +86,6 @@ export const TabsUI = (props: TabsUIProps) => {
const renderTab = (tab, index) => {
console.log('rendertab')
const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass
const classNameTab = 'nav-item nav-link d-flex justify-content-center align-items-center px-2 py-1 tab' + (index === currentIndexRef.current ? ' active' : '')
const invert = props.themeQuality === 'dark' ? 'invert(1)' : 'invert(0)'

Loading…
Cancel
Save