commit
70f4cb16f6
@ -1,46 +0,0 @@ |
||||
'use strict' |
||||
var remixLib = require('@remix-project/remix-lib') |
||||
var txHelper = remixLib.execution.txHelper |
||||
|
||||
module.exports = class CompilerAbstract { |
||||
constructor (languageversion, data, source) { |
||||
this.languageversion = languageversion |
||||
this.data = data |
||||
this.source = source // source code
|
||||
} |
||||
|
||||
getContracts () { |
||||
return this.data.contracts |
||||
} |
||||
|
||||
getContract (name) { |
||||
return txHelper.getContract(name, this.data.contracts) |
||||
} |
||||
|
||||
visitContracts (calllback) { |
||||
return txHelper.visitContracts(this.data.contracts, calllback) |
||||
} |
||||
|
||||
getData () { |
||||
return this.data |
||||
} |
||||
|
||||
getAsts () { |
||||
return this.data.sources // ast
|
||||
} |
||||
|
||||
getSourceName (fileIndex) { |
||||
if (this.data && this.data.sources) { |
||||
return Object.keys(this.data.sources)[fileIndex] |
||||
} else if (Object.keys(this.source.sources).length === 1) { |
||||
// if we don't have ast, we return the only one filename present.
|
||||
const sourcesArray = Object.keys(this.source.sources) |
||||
return sourcesArray[0] |
||||
} |
||||
return null |
||||
} |
||||
|
||||
getSourceCode () { |
||||
return this.source |
||||
} |
||||
} |
@ -1,169 +0,0 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import * as packageJson from '../../../../../package.json' |
||||
import { RemixURLResolver } from '@remix-project/remix-url-resolver' |
||||
const remixTests = require('@remix-project/remix-tests') |
||||
const globalRegistry = require('../../global/registry') |
||||
const addTooltip = require('../ui/tooltip') |
||||
const async = require('async') |
||||
|
||||
const profile = { |
||||
name: 'contentImport', |
||||
displayName: 'content import', |
||||
version: packageJson.version, |
||||
methods: ['resolve', 'resolveAndSave', 'isExternalUrl'] |
||||
} |
||||
|
||||
module.exports = class CompilerImports extends Plugin { |
||||
constructor (fileManager) { |
||||
super(profile) |
||||
this.fileManager = fileManager |
||||
// const token = await this.call('settings', 'getGithubAccessToken')
|
||||
const token = globalRegistry.get('config').api.get('settings/gist-access-token') // TODO replace with the plugin call above https://github.com/ethereum/remix-ide/issues/2288
|
||||
const protocol = window.location.protocol |
||||
this.urlResolver = new RemixURLResolver(token, protocol) |
||||
this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
|
||||
} |
||||
|
||||
isRelativeImport (url) { |
||||
return /^([^/]+)/.exec(url) |
||||
} |
||||
|
||||
isExternalUrl (url) { |
||||
const handlers = this.urlResolver.getHandlers() |
||||
return handlers.some(handler => handler.match(url)) |
||||
} |
||||
|
||||
/** |
||||
* resolve the content of @arg url. This only resolves external URLs. |
||||
* |
||||
* @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc... |
||||
* @returns {Promise} - { content, cleanUrl, type, url } |
||||
*/ |
||||
resolve (url) { |
||||
return new Promise((resolve, reject) => { |
||||
this.import(url, null, (error, content, cleanUrl, type, url) => { |
||||
if (error) return reject(error) |
||||
resolve({ content, cleanUrl, type, url }) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
async import (url, force, loadingCb, cb) { |
||||
if (typeof force !== 'boolean') { |
||||
const temp = loadingCb |
||||
loadingCb = force |
||||
cb = temp |
||||
force = false |
||||
} |
||||
if (!loadingCb) loadingCb = () => {} |
||||
if (!cb) cb = () => {} |
||||
|
||||
var self = this |
||||
if (force) delete this.previouslyHandled[url] |
||||
var imported = this.previouslyHandled[url] |
||||
if (imported) { |
||||
return cb(null, imported.content, imported.cleanUrl, imported.type, url) |
||||
} |
||||
|
||||
let resolved |
||||
try { |
||||
resolved = await this.urlResolver.resolve(url) |
||||
const { content, cleanUrl, type } = resolved |
||||
self.previouslyHandled[url] = { |
||||
content, |
||||
cleanUrl, |
||||
type |
||||
} |
||||
cb(null, content, cleanUrl, type, url) |
||||
} catch (e) { |
||||
return cb(new Error('not found ' + url)) |
||||
} |
||||
} |
||||
|
||||
importExternal (url, targetPath, cb) { |
||||
this.import(url, |
||||
// TODO: move to an event that is generated, the UI shouldn't be here
|
||||
(loadingMsg) => { addTooltip(loadingMsg) }, |
||||
(error, content, cleanUrl, type, url) => { |
||||
if (error) return cb(error) |
||||
if (this.fileManager) { |
||||
const provider = this.fileManager.currentFileProvider() |
||||
const path = targetPath || type + '/' + cleanUrl |
||||
if (provider) provider.addExternal('.deps/' + path, content, url) |
||||
} |
||||
cb(null, content) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* import the content of @arg url. |
||||
* first look in the browser localstorage (browser explorer) or locahost explorer. if the url start with `browser/*` or `localhost/*` |
||||
* then check if the @arg url is located in the localhost, in the node_modules or installed_contracts folder |
||||
* then check if the @arg url match any external url |
||||
* |
||||
* @param {String} url - URL of the content. can be basically anything like file located in the browser explorer, in the localhost explorer, raw HTTP, github address etc... |
||||
* @param {String} targetPath - (optional) internal path where the content should be saved to |
||||
* @returns {Promise} - string content |
||||
*/ |
||||
resolveAndSave (url, targetPath) { |
||||
return new Promise((resolve, reject) => { |
||||
if (url.indexOf('remix_tests.sol') !== -1) resolve(remixTests.assertLibCode) |
||||
if (!this.fileManager) { |
||||
// fallback to just resolving the file, it won't be saved in file manager
|
||||
return this.importExternal(url, targetPath, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
} |
||||
var provider = this.fileManager.fileProviderOf(url) |
||||
if (provider) { |
||||
if (provider.type === 'localhost' && !provider.isConnected()) { |
||||
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`)) |
||||
} |
||||
provider.exists(url).then(exist => { |
||||
/* |
||||
if the path is absolute and the file does not exist, we can stop here |
||||
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop. |
||||
*/ |
||||
if (!exist && url.startsWith('browser/')) return reject(new Error(`not found ${url}`)) |
||||
if (!exist && url.startsWith('localhost/')) return reject(new Error(`not found ${url}`)) |
||||
|
||||
if (exist) { |
||||
return provider.get(url, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
} |
||||
|
||||
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
|
||||
const localhostProvider = this.fileManager.getProvider('localhost') |
||||
if (localhostProvider.isConnected()) { |
||||
var splitted = /([^/]+)\/(.*)$/g.exec(url) |
||||
return async.tryEach([ |
||||
(cb) => { this.resolveAndSave('localhost/installed_contracts/' + url).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2]).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }, |
||||
(cb) => { this.resolveAndSave('localhost/node_modules/' + url).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2]).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }], |
||||
(error, result) => { |
||||
if (error) { |
||||
return this.importExternal(url, targetPath, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
} |
||||
resolve(result) |
||||
}) |
||||
} |
||||
|
||||
this.importExternal(url, targetPath, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
}).catch(error => { |
||||
return reject(error) |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -1,21 +0,0 @@ |
||||
'use strict' |
||||
|
||||
module.exports = (sources, opts) => { |
||||
return JSON.stringify({ |
||||
language: 'Solidity', |
||||
sources: sources, |
||||
settings: { |
||||
optimizer: { |
||||
enabled: opts.optimize === true || opts.optimize === 1, |
||||
runs: 200 |
||||
}, |
||||
libraries: opts.libraries, |
||||
outputSelection: { |
||||
'*': { |
||||
'': ['ast'], |
||||
'*': ['abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates'] |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
@ -1,148 +0,0 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import * as packageJson from '../../../../../package.json' |
||||
import { joinPath } from '../../lib/helper' |
||||
var CompilerAbstract = require('../compiler/compiler-abstract') |
||||
|
||||
const profile = { |
||||
name: 'compilerMetadata', |
||||
methods: ['deployMetadataOf'], |
||||
events: [], |
||||
version: packageJson.version |
||||
} |
||||
|
||||
class CompilerMetadata extends Plugin { |
||||
constructor (blockchain, fileManager, config) { |
||||
super(profile) |
||||
this.blockchain = blockchain |
||||
this.fileManager = fileManager |
||||
this.config = config |
||||
this.networks = ['VM:-', 'main:1', 'ropsten:3', 'rinkeby:4', 'kovan:42', 'görli:5', 'Custom'] |
||||
this.innerPath = 'artifacts' |
||||
} |
||||
|
||||
_JSONFileName (path, contractName) { |
||||
return joinPath(path, this.innerPath, contractName + '.json') |
||||
} |
||||
|
||||
_MetadataFileName (path, contractName) { |
||||
return joinPath(path, this.innerPath, contractName + '_metadata.json') |
||||
} |
||||
|
||||
onActivation () { |
||||
var self = this |
||||
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => { |
||||
if (!self.config.get('settings/generate-contract-metadata')) return |
||||
const compiler = new CompilerAbstract(languageVersion, data, source) |
||||
var provider = self.fileManager.fileProviderOf(source.target) |
||||
var path = self.fileManager.extractPathOf(source.target) |
||||
if (provider) { |
||||
compiler.visitContracts((contract) => { |
||||
if (contract.file !== source.target) return |
||||
|
||||
var fileName = self._JSONFileName(path, contract.name) |
||||
var metadataFileName = self._MetadataFileName(path, contract.name) |
||||
provider.get(fileName, (error, content) => { |
||||
if (!error) { |
||||
content = content || '{}' |
||||
var metadata |
||||
try { |
||||
metadata = JSON.parse(content) |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
|
||||
var deploy = metadata.deploy || {} |
||||
self.networks.forEach((network) => { |
||||
deploy[network] = self._syncContext(contract, deploy[network] || {}) |
||||
}) |
||||
|
||||
let parsedMetadata |
||||
try { |
||||
parsedMetadata = JSON.parse(contract.object.metadata) |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
if (parsedMetadata) provider.set(metadataFileName, JSON.stringify(parsedMetadata, null, '\t')) |
||||
|
||||
var data = { |
||||
deploy, |
||||
data: { |
||||
bytecode: contract.object.evm.bytecode, |
||||
deployedBytecode: contract.object.evm.deployedBytecode, |
||||
gasEstimates: contract.object.evm.gasEstimates, |
||||
methodIdentifiers: contract.object.evm.methodIdentifiers |
||||
}, |
||||
abi: contract.object.abi |
||||
} |
||||
|
||||
provider.set(fileName, JSON.stringify(data, null, '\t')) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
_syncContext (contract, metadata) { |
||||
var linkReferences = metadata.linkReferences |
||||
var autoDeployLib = metadata.autoDeployLib |
||||
if (!linkReferences) linkReferences = {} |
||||
if (autoDeployLib === undefined) autoDeployLib = true |
||||
|
||||
for (var libFile in contract.object.evm.bytecode.linkReferences) { |
||||
if (!linkReferences[libFile]) linkReferences[libFile] = {} |
||||
for (var lib in contract.object.evm.bytecode.linkReferences[libFile]) { |
||||
if (!linkReferences[libFile][lib]) { |
||||
linkReferences[libFile][lib] = '<address>' |
||||
} |
||||
} |
||||
} |
||||
metadata.linkReferences = linkReferences |
||||
metadata.autoDeployLib = autoDeployLib |
||||
return metadata |
||||
} |
||||
|
||||
// TODO: is only called by dropdownLogic and can be moved there
|
||||
deployMetadataOf (contractName, fileLocation) { |
||||
return new Promise((resolve, reject) => { |
||||
var provider |
||||
let path |
||||
if (fileLocation) { |
||||
provider = this.fileManager.fileProviderOf(fileLocation) |
||||
path = fileLocation.split('/') |
||||
path.pop() |
||||
path = path.join('/') |
||||
} else { |
||||
provider = this.fileManager.currentFileProvider() |
||||
path = this.fileManager.currentPath() |
||||
} |
||||
|
||||
if (provider) { |
||||
this.blockchain.detectNetwork((err, { id, name } = {}) => { |
||||
if (err) { |
||||
console.log(err) |
||||
reject(err) |
||||
} else { |
||||
var fileName = this._JSONFileName(path, contractName) |
||||
provider.get(fileName, (error, content) => { |
||||
if (error) return reject(error) |
||||
if (!content) return resolve() |
||||
try { |
||||
var metadata = JSON.parse(content) |
||||
metadata = metadata.deploy || {} |
||||
return resolve(metadata[name + ':' + id] || metadata[name] || metadata[id] || metadata[name.toLowerCase() + ':' + id] || metadata[name.toLowerCase()]) |
||||
} catch (e) { |
||||
reject(e.message) |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
} else { |
||||
reject(new Error(`Please select the folder in the file explorer where the metadata of ${contractName} can be found`)) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
module.exports = CompilerMetadata |
@ -0,0 +1 @@ |
||||
{ "extends": "../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] } |
@ -0,0 +1,3 @@ |
||||
# remix-core-plugin-core-plugin |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"name": "@remix-project/core-plugin", |
||||
"version": "0.0.1", |
||||
"description": "This library was generated with [Nx](https://nx.dev).", |
||||
"main": "index.js", |
||||
"scripts": { |
||||
"test": "echo \"Error: no test specified\" && exit 1" |
||||
}, |
||||
"author": "Remix Team", |
||||
"license": "ISC" |
||||
} |
||||
|
@ -0,0 +1,5 @@ |
||||
export { OffsetToLineColumnConverter } from './lib/offset-line-to-column-converter' |
||||
export { CompilerMetadata } from './lib/compiler-metadata' |
||||
export { FetchAndCompile } from './lib/compiler-fetch-and-compile' |
||||
export { CompilerImports } from './lib/compiler-content-imports' |
||||
export { CompilerArtefacts } from './lib/compiler-artefacts' |
@ -1,16 +1,17 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import * as packageJson from '../../../../../package.json' |
||||
import CompilerAbstract from './compiler-abstract' |
||||
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||
|
||||
const profile = { |
||||
name: 'compilerArtefacts', |
||||
methods: [], |
||||
methods: ['get', 'addResolvedContract'], |
||||
events: [], |
||||
version: packageJson.version |
||||
version: '0.0.1' |
||||
} |
||||
|
||||
module.exports = class CompilerArtefacts extends Plugin { |
||||
export class CompilerArtefacts extends Plugin { |
||||
compilersArtefactsPerFile: any |
||||
compilersArtefacts: any |
||||
constructor () { |
||||
super(profile) |
||||
this.compilersArtefacts = {} |
@ -0,0 +1,175 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { RemixURLResolver } from '@remix-project/remix-url-resolver' |
||||
const remixTests = require('@remix-project/remix-tests') |
||||
const async = require('async') |
||||
|
||||
const profile = { |
||||
name: 'contentImport', |
||||
displayName: 'content import', |
||||
version: '0.0.1', |
||||
methods: ['resolve', 'resolveAndSave', 'isExternalUrl'] |
||||
} |
||||
|
||||
export class CompilerImports extends Plugin { |
||||
previouslyHandled: {} |
||||
urlResolver: any |
||||
constructor () { |
||||
super(profile) |
||||
this.urlResolver = new RemixURLResolver() |
||||
this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
|
||||
} |
||||
|
||||
async setToken () { |
||||
const protocol = typeof window !== 'undefined' && window.location.protocol |
||||
const token = await this.call('settings', 'get', 'settings/gist-access-token') |
||||
this.urlResolver.setGistToken(token, protocol) |
||||
} |
||||
|
||||
isRelativeImport (url) { |
||||
return /^([^/]+)/.exec(url) |
||||
} |
||||
|
||||
isExternalUrl (url) { |
||||
const handlers = this.urlResolver.getHandlers() |
||||
return handlers.some(handler => handler.match(url)) |
||||
} |
||||
|
||||
/** |
||||
* resolve the content of @arg url. This only resolves external URLs. |
||||
* |
||||
* @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc... |
||||
* @returns {Promise} - { content, cleanUrl, type, url } |
||||
*/ |
||||
resolve (url) { |
||||
return new Promise((resolve, reject) => { |
||||
this.import(url, null, (error, content, cleanUrl, type, url) => { |
||||
if (error) return reject(error) |
||||
resolve({ content, cleanUrl, type, url }) |
||||
}, null) |
||||
}) |
||||
} |
||||
|
||||
async import (url, force, loadingCb, cb) { |
||||
if (typeof force !== 'boolean') { |
||||
const temp = loadingCb |
||||
loadingCb = force |
||||
cb = temp |
||||
force = false |
||||
} |
||||
if (!loadingCb) loadingCb = () => {} |
||||
if (!cb) cb = () => {} |
||||
|
||||
var self = this |
||||
if (force) delete this.previouslyHandled[url] |
||||
var imported = this.previouslyHandled[url] |
||||
if (imported) { |
||||
return cb(null, imported.content, imported.cleanUrl, imported.type, url) |
||||
} |
||||
|
||||
let resolved |
||||
try { |
||||
await this.setToken() |
||||
resolved = await this.urlResolver.resolve(url) |
||||
const { content, cleanUrl, type } = resolved |
||||
self.previouslyHandled[url] = { |
||||
content, |
||||
cleanUrl, |
||||
type |
||||
} |
||||
cb(null, content, cleanUrl, type, url) |
||||
} catch (e) { |
||||
return cb(new Error('not found ' + url)) |
||||
} |
||||
} |
||||
|
||||
importExternal (url, targetPath, cb) { |
||||
this.import(url, |
||||
// TODO: handle this event
|
||||
(loadingMsg) => { this.emit('message', loadingMsg) }, |
||||
async (error, content, cleanUrl, type, url) => { |
||||
if (error) return cb(error) |
||||
try { |
||||
const provider = await this.call('fileManager', 'getProviderOf', null) |
||||
const path = targetPath || type + '/' + cleanUrl |
||||
if (provider) provider.addExternal('.deps/' + path, content, url) |
||||
} catch (err) { |
||||
|
||||
} |
||||
cb(null, content) |
||||
}, null) |
||||
} |
||||
|
||||
/** |
||||
* import the content of @arg url. |
||||
* first look in the browser localstorage (browser explorer) or locahost explorer. if the url start with `browser/*` or `localhost/*` |
||||
* then check if the @arg url is located in the localhost, in the node_modules or installed_contracts folder |
||||
* then check if the @arg url match any external url |
||||
* |
||||
* @param {String} url - URL of the content. can be basically anything like file located in the browser explorer, in the localhost explorer, raw HTTP, github address etc... |
||||
* @param {String} targetPath - (optional) internal path where the content should be saved to |
||||
* @returns {Promise} - string content |
||||
*/ |
||||
resolveAndSave (url, targetPath) { |
||||
return new Promise((resolve, reject) => { |
||||
if (url.indexOf('remix_tests.sol') !== -1) resolve(remixTests.assertLibCode) |
||||
this.call('fileManager', 'getProviderOf', url).then((provider) => { |
||||
if (provider) { |
||||
if (provider.type === 'localhost' && !provider.isConnected()) { |
||||
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`)) |
||||
} |
||||
provider.exists(url).then(exist => { |
||||
/* |
||||
if the path is absolute and the file does not exist, we can stop here |
||||
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop. |
||||
*/ |
||||
if (!exist && url.startsWith('browser/')) return reject(new Error(`not found ${url}`)) |
||||
if (!exist && url.startsWith('localhost/')) return reject(new Error(`not found ${url}`)) |
||||
|
||||
if (exist) { |
||||
return provider.get(url, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
} |
||||
|
||||
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
|
||||
this.call('fileManager', 'getProviderByName', 'localhost').then((localhostProvider) => { |
||||
if (localhostProvider.isConnected()) { |
||||
var splitted = /([^/]+)\/(.*)$/g.exec(url) |
||||
return async.tryEach([ |
||||
(cb) => { this.resolveAndSave('localhost/installed_contracts/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }, |
||||
(cb) => { this.resolveAndSave('localhost/node_modules/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }], |
||||
(error, result) => { |
||||
if (error) { |
||||
return this.importExternal(url, targetPath, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
} |
||||
resolve(result) |
||||
}) |
||||
} |
||||
this.importExternal(url, targetPath, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
}) |
||||
}).catch(error => { |
||||
return reject(error) |
||||
}) |
||||
} |
||||
}).catch(() => { |
||||
// fallback to just resolving the file, it won't be saved in file manager
|
||||
return this.importExternal(url, targetPath, (error, content) => { |
||||
if (error) return reject(error) |
||||
resolve(content) |
||||
}) |
||||
}) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,145 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||
|
||||
const profile = { |
||||
name: 'compilerMetadata', |
||||
methods: ['deployMetadataOf'], |
||||
events: [], |
||||
version: '0.0.1' |
||||
} |
||||
|
||||
export class CompilerMetadata extends Plugin { |
||||
networks: string[] |
||||
innerPath: string |
||||
constructor () { |
||||
super(profile) |
||||
this.networks = ['VM:-', 'main:1', 'ropsten:3', 'rinkeby:4', 'kovan:42', 'görli:5', 'Custom'] |
||||
this.innerPath = 'artifacts' |
||||
} |
||||
|
||||
_JSONFileName (path, contractName) { |
||||
return this.joinPath(path, this.innerPath, contractName + '.json') |
||||
} |
||||
|
||||
_MetadataFileName (path, contractName) { |
||||
return this.joinPath(path, this.innerPath, contractName + '_metadata.json') |
||||
} |
||||
|
||||
onActivation () { |
||||
var self = this |
||||
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data) => { |
||||
if (!await this.call('settings', 'get', 'settings/generate-contract-metadata')) return |
||||
const compiler = new CompilerAbstract(languageVersion, data, source) |
||||
var path = self._extractPathOf(source.target) |
||||
compiler.visitContracts((contract) => { |
||||
if (contract.file !== source.target) return |
||||
(async () => { |
||||
const fileName = self._JSONFileName(path, contract.name) |
||||
const content = await this.call('fileManager', 'exists', fileName) ? await this.call('fileManager', 'readFile', fileName) : null |
||||
await this._setArtefacts(content, contract, path) |
||||
})() |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
_extractPathOf (file) { |
||||
var reg = /(.*)(\/).*/ |
||||
var path = reg.exec(file) |
||||
return path ? path[1] : '/' |
||||
} |
||||
|
||||
async _setArtefacts (content, contract, path) { |
||||
content = content || '{}' |
||||
var metadata |
||||
try { |
||||
metadata = JSON.parse(content) |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
var fileName = this._JSONFileName(path, contract.name) |
||||
var metadataFileName = this._MetadataFileName(path, contract.name) |
||||
|
||||
var deploy = metadata.deploy || {} |
||||
this.networks.forEach((network) => { |
||||
deploy[network] = this._syncContext(contract, deploy[network] || {}) |
||||
}) |
||||
|
||||
let parsedMetadata |
||||
try { |
||||
parsedMetadata = JSON.parse(contract.object.metadata) |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
if (parsedMetadata) await this.call('fileManager', 'writeFile', metadataFileName, JSON.stringify(parsedMetadata, null, '\t')) |
||||
|
||||
var data = { |
||||
deploy, |
||||
data: { |
||||
bytecode: contract.object.evm.bytecode, |
||||
deployedBytecode: contract.object.evm.deployedBytecode, |
||||
gasEstimates: contract.object.evm.gasEstimates, |
||||
methodIdentifiers: contract.object.evm.methodIdentifiers |
||||
}, |
||||
abi: contract.object.abi |
||||
} |
||||
await this.call('fileManager', 'writeFile', fileName, JSON.stringify(data, null, '\t')) |
||||
} |
||||
|
||||
_syncContext (contract, metadata) { |
||||
var linkReferences = metadata.linkReferences |
||||
var autoDeployLib = metadata.autoDeployLib |
||||
if (!linkReferences) linkReferences = {} |
||||
if (autoDeployLib === undefined) autoDeployLib = true |
||||
|
||||
for (var libFile in contract.object.evm.bytecode.linkReferences) { |
||||
if (!linkReferences[libFile]) linkReferences[libFile] = {} |
||||
for (var lib in contract.object.evm.bytecode.linkReferences[libFile]) { |
||||
if (!linkReferences[libFile][lib]) { |
||||
linkReferences[libFile][lib] = '<address>' |
||||
} |
||||
} |
||||
} |
||||
metadata.linkReferences = linkReferences |
||||
metadata.autoDeployLib = autoDeployLib |
||||
return metadata |
||||
} |
||||
|
||||
async deployMetadataOf (contractName, fileLocation) { |
||||
let path |
||||
if (fileLocation) { |
||||
path = fileLocation.split('/') |
||||
path.pop() |
||||
path = path.join('/') |
||||
} else { |
||||
try { |
||||
path = this._extractPathOf(await this.call('fileManager', 'getCurrentFile')) |
||||
} catch (err) { |
||||
console.log(err) |
||||
throw new Error(err) |
||||
} |
||||
} |
||||
try { |
||||
const { id, name } = await this.call('network', 'detectNetwork') |
||||
const fileName = this._JSONFileName(path, contractName) |
||||
try { |
||||
const content = await this.call('fileManager', 'readFile', fileName) |
||||
if (!content) return null |
||||
let metadata = JSON.parse(content) |
||||
metadata = metadata.deploy || {} |
||||
return metadata[name + ':' + id] || metadata[name] || metadata[id] || metadata[name.toLowerCase() + ':' + id] || metadata[name.toLowerCase()] |
||||
} catch (err) { |
||||
return null |
||||
} |
||||
} catch (err) { |
||||
console.log(err) |
||||
throw new Error(err) |
||||
} |
||||
} |
||||
|
||||
joinPath (...paths) { |
||||
paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
|
||||
if (paths.length === 1) return paths[0] |
||||
return paths.join('/') |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
|
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
|
||||
const profile = { |
||||
name: 'offsetToLineColumnConverter', |
||||
methods: ['offsetToLineColumn'], |
||||
events: [], |
||||
version: '0.0.1' |
||||
} |
||||
|
||||
export class OffsetToLineColumnConverter extends Plugin { |
||||
lineBreakPositionsByContent: {} |
||||
sourceMappingDecoder: any |
||||
constructor () { |
||||
super(profile) |
||||
this.lineBreakPositionsByContent = {} |
||||
this.sourceMappingDecoder = sourceMappingDecoder |
||||
} |
||||
|
||||
/** |
||||
* Convert offset representation with line/column representation. |
||||
* This is also used to resolve the content: |
||||
* @arg file is the index of the file in the content sources array and content sources array does have filename as key and not index. |
||||
* So we use the asts (which references both index and filename) to look up the actual content targeted by the @arg file index. |
||||
* @param {{start, length}} rawLocation - offset location |
||||
* @param {number} file - The index where to find the source in the sources parameters |
||||
* @param {Object.<string, {content}>} sources - Map of content sources |
||||
* @param {Object.<string, {ast, id}>} asts - Map of content sources |
||||
*/ |
||||
offsetToLineColumn (rawLocation, file, sources, asts) { |
||||
if (!this.lineBreakPositionsByContent[file]) { |
||||
const sourcesArray = Object.keys(sources) |
||||
if (!asts || (file === 0 && sourcesArray.length === 1)) { |
||||
// if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12)
|
||||
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content) |
||||
} else { |
||||
for (var filename in asts) { |
||||
const source = asts[filename] |
||||
if (source.id === file) { |
||||
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) |
||||
} |
||||
|
||||
/** |
||||
* Convert offset representation with line/column representation. |
||||
* @param {{start, length}} rawLocation - offset location |
||||
* @param {number} file - The index where to find the source in the sources parameters |
||||
* @param {string} content - source |
||||
*/ |
||||
offsetToLineColumnWithContent (rawLocation, file, content) { |
||||
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(content) |
||||
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) |
||||
} |
||||
|
||||
/** |
||||
* Clear the cache |
||||
*/ |
||||
clear () { |
||||
this.lineBreakPositionsByContent = {} |
||||
} |
||||
|
||||
/** |
||||
* called by plugin API |
||||
*/ |
||||
activate () { |
||||
this.on('solidity', 'compilationFinished', () => { |
||||
this.clear() |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
{ |
||||
"extends": "../../tsconfig.json", |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"module": "commonjs", |
||||
"outDir": "../../dist/out-tsc", |
||||
"declaration": true, |
||||
"rootDir": "./src", |
||||
"types": ["node"] |
||||
}, |
||||
"exclude": ["**/*.spec.ts"], |
||||
"include": ["**/*.ts"] |
||||
} |
@ -0,0 +1,48 @@ |
||||
'use strict' |
||||
import txHelper from './txHelper' |
||||
|
||||
export class CompilerAbstract { |
||||
languageversion: any |
||||
data: any |
||||
source: any |
||||
constructor (languageversion, data, source) { |
||||
this.languageversion = languageversion |
||||
this.data = data |
||||
this.source = source // source code
|
||||
} |
||||
|
||||
getContracts () { |
||||
return this.data.contracts |
||||
} |
||||
|
||||
getContract (name) { |
||||
return txHelper.getContract(name, this.data.contracts) |
||||
} |
||||
|
||||
visitContracts (calllback) { |
||||
return txHelper.visitContracts(this.data.contracts, calllback) |
||||
} |
||||
|
||||
getData () { |
||||
return this.data |
||||
} |
||||
|
||||
getAsts () { |
||||
return this.data.sources // ast
|
||||
} |
||||
|
||||
getSourceName (fileIndex) { |
||||
if (this.data && this.data.sources) { |
||||
return Object.keys(this.data.sources)[fileIndex] |
||||
} else if (Object.keys(this.source.sources).length === 1) { |
||||
// if we don't have ast, we return the only one filename present.
|
||||
const sourcesArray = Object.keys(this.source.sources) |
||||
return sourcesArray[0] |
||||
} |
||||
return null |
||||
} |
||||
|
||||
getSourceCode () { |
||||
return this.source |
||||
} |
||||
} |
@ -1,7 +1,7 @@ |
||||
'use strict' |
||||
import { canUseWorker, urlFromVersion } from './compiler-utils' |
||||
import { Compiler } from '@remix-project/remix-solidity' |
||||
import CompilerAbstract from './compiler-abstract' |
||||
import { CompilerAbstract } from './compiler-abstract' |
||||
import { Compiler } from './compiler' |
||||
|
||||
export const compile = async (compilationTargets, settings, contentResolverCallback) => { |
||||
const res = await (() => { |
@ -1,3 +1,6 @@ |
||||
export { Compiler } from './compiler/compiler' |
||||
export { compile } from './compiler/compiler-helpers' |
||||
export { default as CompilerInput } from './compiler/compiler-input' |
||||
export { CompilerAbstract } from './compiler/compiler-abstract' |
||||
export * from './compiler/types' |
||||
export * from './compiler/compiler-utils' |
||||
|
Loading…
Reference in new issue