Merge pull request #1100 from ethereum/remix-resolve

Remix resolve
pull/7/head
yann300 6 years ago committed by GitHub
commit 6938ff83d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      .circleci/config.yml
  2. 3
      lerna.json
  3. 1
      remix-url-resolver/.gitignore
  4. 32
      remix-url-resolver/README.md
  5. 49
      remix-url-resolver/package.json
  6. 1
      remix-url-resolver/src/index.ts
  7. 133
      remix-url-resolver/src/resolve.ts
  8. 17
      remix-url-resolver/tests/example_1/greeter.sol
  9. 12
      remix-url-resolver/tests/example_1/mortal.sol
  10. 146
      remix-url-resolver/tests/test.ts
  11. 21
      remix-url-resolver/tsconfig.json
  12. 113
      remix-url-resolver/tslint.json

@ -23,7 +23,7 @@ jobs:
- checkout
- run: npm install && npm run bootstrap
- run: cd remix-debug && npm test
remix-analyzer:
docker:
- image: circleci/node:10
@ -33,7 +33,7 @@ jobs:
- checkout
- run: npm install && npm run bootstrap
- run: cd remix-analyzer && npm test
remix-tests:
docker:
- image: circleci/node:10
@ -43,7 +43,7 @@ jobs:
- checkout
- run: npm install && npm run bootstrap
- run: cd remix-tests && npm test
remix-simulator:
docker:
- image: circleci/node:10
@ -53,7 +53,17 @@ jobs:
- checkout
- run: npm install && npm run bootstrap
- run: cd remix-simulator && npm test
remix-url-resolver:
docker:
- image: circleci/node:10
environment:
working_directory: ~/repo
steps:
- checkout
- run: npm install && npm run bootstrap
- run: cd remix-url-resolver && npm run build && npm test
workflows:
version: 2
@ -64,3 +74,4 @@ workflows:
- remix-analyzer
- remix-tests
- remix-simulator
- remix-url-resolver

@ -6,7 +6,8 @@
"remix-solidity",
"remix-analyzer",
"remix-tests",
"remix-simulator"
"remix-simulator",
"remix-url-resolver"
],
"command": {
"init": {

@ -0,0 +1,32 @@
## Remix URL resolver engine
`resolve(url, urlHandler)`
Returns `json` object with exact same path as `import` statement.
**Output**
```json
{
content: 'pragma solidity ^0.5.0;\nimport "./mortal.sol";\n\ncontract Greeter is Mortal {\n /* Define variable greeting of the type string */\n string greeting;\n\n /* This runs when the contract is executed */\n constructor(string memory _greeting) public {\n greeting = _greeting;\n }\n\n /* Main function */\n function greet() public view returns (string memory) {\n return greeting;\n }\n}\n',
cleanURL: '../greeter.sol',
type: 'local'
}
```
#### Usage
`resolve(url, urlHandler)` function should be called from within `handleImportCb` function of `solc.compile(input, handleImportCb)`.
```ts
import { RemixResolve } from 'remix-url-resolver'
const remixResolve = new RemixResolve()
const fileName: string = '../greeter.sol'
remixResolve.resolve(fileName, urlHandler)
.then((sources: object) => {
console.log(sources)
})
.catch((e: Error) => {
throw e
})
```

@ -0,0 +1,49 @@
{
"name": "remix-url-resolver",
"version": "0.0.1",
"description": "Solidity import url resolver engine",
"main": "./src/index.js",
"bin": {
"remix-url-resolver": "./bin/remix-url-resolver"
},
"scripts": {
"build": "tsc",
"lint": "standard",
"test": "standard && mocha --require ts-node/register tests/*.ts -t 300000"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ethereum/remix.git"
},
"keywords": [
"solidity",
"remix",
"resolve",
"import"
],
"author": "Remix Team",
"license": "MIT",
"standard": {
"ignore": [
"tests/"
]
},
"dependencies": {
"axios": "^0.18.0",
"solc": "^0.5.0",
"url": "^0.11.0",
"valid-url": "^1.0.9"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"chai": "^4.2.0",
"mocha": "^5.1.0",
"remix-plugin": "0.0.1-alpha.2",
"standard": "^12.0.1",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"typescript": "^3.1.6"
}
}

@ -0,0 +1 @@
export * from './resolve'

@ -0,0 +1,133 @@
import axios, { AxiosResponse } from 'axios'
export interface Imported {
content: string;
cleanURL: string;
type: string;
}
interface PreviouslyHandledImports {
[filePath: string]: Imported
}
interface Handler {
type: string;
match(url: string): any;
handle(match: any): any;
}
export class RemixURLResolver {
private previouslyHandled: PreviouslyHandledImports
constructor() {
this.previouslyHandled = {}
}
/**
* Handle an import statement based on github
* @params root The root of the github import statement
* @params filePath path of the file in github
*/
async handleGithubCall(root: string, filePath: string) {
try {
let req: string = 'https://api.github.com/repos/' + root + '/contents/' + filePath
const response: AxiosResponse = await axios.get(req)
return Buffer.from(response.data.content, 'base64').toString()
} catch(e) {
throw e
}
}
/**
* Handle an import statement based on http
* @params url The url of the import statement
* @params cleanURL
*/
async handleHttp(url: string, _: string) {
try {
const response: AxiosResponse = await axios.get(url)
return response.data
} catch(e) {
throw e
}
}
/**
* Handle an import statement based on https
* @params url The url of the import statement
* @params cleanURL
*/
async handleHttps(url: string, _: string) {
try {
const response: AxiosResponse = await axios.get(url)
return response.data
} catch(e) {
throw e
}
}
handleSwarm(url: string, cleanURL: string) {
return
}
/**
* Handle an import statement based on IPFS
* @params url The url of the IPFS import statement
*/
async handleIPFS(url: string) {
// replace ipfs:// with /ipfs/
url = url.replace(/^ipfs:\/\/?/, 'ipfs/')
try {
const req = 'https://gateway.ipfs.io/' + url
// If you don't find greeter.sol on ipfs gateway use local
// const req = 'http://localhost:8080/' + url
const response: AxiosResponse = await axios.get(req)
return response.data
} catch (e) {
throw e
}
}
getHandlers(): Handler[] {
return [
{
type: 'github',
match: (url) => { return /^(https?:\/\/)?(www.)?github.com\/([^/]*\/[^/]*)\/(.*)/.exec(url) },
handle: (match) => this.handleGithubCall(match[3], match[4])
},
{
type: 'http',
match: (url) => { return /^(http?:\/\/?(.*))$/.exec(url) },
handle: (match) => this.handleHttp(match[1], match[2])
},
{
type: 'https',
match: (url) => { return /^(https?:\/\/?(.*))$/.exec(url) },
handle: (match) => this.handleHttps(match[1], match[2])
},
{
type: 'swarm',
match: (url) => { return /^(bzz-raw?:\/\/?(.*))$/.exec(url) },
handle: (match) => this.handleSwarm(match[1], match[2])
},
{
type: 'ipfs',
match: (url) => { return /^(ipfs:\/\/?.+)/.exec(url) },
handle: (match) => this.handleIPFS(match[1])
}
]
}
public async resolve(filePath: string, customHandlers?: Handler[]): Promise<Imported> {
var imported: Imported = this.previouslyHandled[filePath]
if(imported) {
return imported
}
const builtinHandlers: Handler[] = this.getHandlers()
const handlers: Handler[] = customHandlers ? [...builtinHandlers, ...customHandlers] : [...builtinHandlers]
const matchedHandler = handlers.filter(handler => handler.match(filePath))
const handler: Handler = matchedHandler[0]
const match = handler.match(filePath)
const content: string = await handler.handle(match)
imported = {
content,
cleanURL: filePath,
type: handler.type
}
this.previouslyHandled[filePath] = imported
return imported
}
}

@ -0,0 +1,17 @@
pragma solidity ^0.5.0;
import "./mortal.sol";
contract Greeter is Mortal {
/* Define variable greeting of the type string */
string greeting;
/* This runs when the contract is executed */
constructor(string memory _greeting) public {
greeting = _greeting;
}
/* Main function */
function greet() public view returns (string memory) {
return greeting;
}
}

@ -0,0 +1,12 @@
pragma solidity ^0.5.0;
contract Mortal {
/* Define variable owner of the type address */
address payable owner;
/* This function is executed at initialization and sets the owner of the contract */
function mortal() public { owner = msg.sender; }
/* Function to recover the funds on the contract */
function kill() public { if (msg.sender == owner) selfdestruct(owner); }
}

@ -0,0 +1,146 @@
import { RemixURLResolver } from '../src'
import * as fs from 'fs'
import * as path from 'path'
import * as assert from 'assert'
describe('testRunner', () => {
describe('# RemixResolve.resolve()', () => {
describe('* test without AppManager', () => {
describe('test example_1 [local imports]', () => {
const remixURLResolve = new RemixURLResolver()
const fileName: string = '../remix-url-resolver/tests/example_1/greeter.sol'
let results: object = {}
before(done => {
function handleLocal(pathString: string, filePath: string) {
// if no relative/absolute path given then search in node_modules folder
if (pathString && pathString.indexOf('.') !== 0 && pathString.indexOf('/') !== 0) {
// return handleNodeModulesImport(pathString, filePath, pathString)
return
} else {
const o = { encoding: 'UTF-8' }
const p = pathString ? path.resolve(pathString, filePath) : path.resolve(pathString, filePath)
const content = fs.readFileSync(p, o)
return content
}
}
const localFSHandler = [
{
type: 'local',
match: (url: string) => { return /(^(?!(?:http:\/\/)|(?:https:\/\/)?(?:www.)?(?:github.com)))(^\/*[\w+-_/]*\/)*?(\w+\.sol)/g.exec(url) },
handle: (match: Array<string>) => { return handleLocal(match[2], match[3]) }
}
]
remixURLResolve.resolve(fileName, localFSHandler)
.then((sources: object) => {
results = sources
done()
})
.catch((e: Error) => {
throw e
})
})
it('should have 3 items', () => {
assert.equal(Object.keys(results).length, 3)
})
it('should return contract content of given local path', () => {
const expt = {
content: 'pragma solidity ^0.5.0;\nimport "./mortal.sol";\n\ncontract Greeter is Mortal {\n /* Define variable greeting of the type string */\n string greeting;\n\n /* This runs when the contract is executed */\n constructor(string memory _greeting) public {\n greeting = _greeting;\n }\n\n /* Main function */\n function greet() public view returns (string memory) {\n return greeting;\n }\n}\n',
cleanURL: '../remix-url-resolver/tests/example_1/greeter.sol',
type: 'local'
}
assert.deepEqual(results, expt)
})
})
// Test github import
describe('test getting github imports', () => {
const remixURLResolve = new RemixURLResolver()
const fileName: string = 'github.com/ethereum/populus/docs/assets/Greeter.sol'
let results: object = {}
before(done => {
remixURLResolve.resolve(fileName)
.then((sources: object) => {
results = sources
done()
})
.catch((e: Error) => {
throw e
})
})
it('should have 3 items', () => {
assert.equal(Object.keys(results).length, 3)
})
it('should return contract content of given github path', () => {
const expt: object = {
cleanURL: 'github.com/ethereum/populus/docs/assets/Greeter.sol',
content: 'pragma solidity ^0.4.0;\n\ncontract Greeter {\n string public greeting;\n\n // TODO: Populus seems to get no bytecode if `internal`\n function Greeter() public {\n greeting = \'Hello\';\n }\n\n function setGreeting(string _greeting) public {\n greeting = _greeting;\n }\n\n function greet() public constant returns (string) {\n return greeting;\n }\n}\n',
type: 'github'
}
assert.deepEqual(results, expt)
})
})
// Test https imports
describe('test getting https imports', () => {
const remixURLResolve = new RemixURLResolver()
const fileName: string = 'https://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol'
let results: object = {}
before(done => {
remixURLResolve.resolve(fileName)
.then((sources: object) => {
results = sources
done()
})
.catch((e: Error) => {
throw e
})
})
it('should have 3 items', () => {
assert.equal(Object.keys(results).length, 3)
})
it('should return contract content from raw github url', () => {
const expt: object = {
content: 'contract mortal {\n /* Define variable owner of the type address*/\n address owner;\n\n /* this function is executed at initialization and sets the owner of the contract */\n function mortal() { owner = msg.sender; }\n\n /* Function to recover the funds on the contract */\n function kill() { if (msg.sender == owner) suicide(owner); }\n}\n\ncontract greeter is mortal {\n /* define variable greeting of the type string */\n string greeting;\n\n /* this runs when the contract is executed */\n function greeter(string _greeting) public {\n greeting = _greeting;\n }\n\n /* main function */\n function greet() constant returns (string) {\n return greeting;\n }\n}',
cleanURL: 'https://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol',
type: 'https'
}
assert.deepEqual(results, expt)
})
})
// Test http imports
describe('test getting http imports', () => {
const remixURLResolve = new RemixURLResolver()
const fileName: string = 'http://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol'
let results: object = {}
before(done => {
remixURLResolve.resolve(fileName)
.then((sources: object) => {
results = sources
done()
})
.catch((e: Error) => {
throw e
})
})
it('should have 3 items', () => {
assert.equal(Object.keys(results).length, 3)
})
it('should return contract content from raw github url', () => {
const expt: object = {
content: 'contract mortal {\n /* Define variable owner of the type address*/\n address owner;\n\n /* this function is executed at initialization and sets the owner of the contract */\n function mortal() { owner = msg.sender; }\n\n /* Function to recover the funds on the contract */\n function kill() { if (msg.sender == owner) suicide(owner); }\n}\n\ncontract greeter is mortal {\n /* define variable greeting of the type string */\n string greeting;\n\n /* this runs when the contract is executed */\n function greeter(string _greeting) public {\n greeting = _greeting;\n }\n\n /* main function */\n function greet() constant returns (string) {\n return greeting;\n }\n}',
cleanURL: 'http://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol',
type: 'http'
}
assert.deepEqual(results, expt)
})
})
})
})
})

@ -0,0 +1,21 @@
{
"compileOnSave": false,
"include": ["./src"],
"compilerOptions": {
"baseUrl": "./src",
"outDir": "./dist",
"sourceMap": true,
"declaration": false,
"module": "commonjs",
"strict": true,
"noImplicitAny": false,
"strictPropertyInitialization": false,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": ["node_modules/@types"],
"lib": ["dom", "es2018"],
"paths": {
"remix-url-resolver": ["./"]
}
}
}

@ -0,0 +1,113 @@
{
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": false,
"deprecation": {
"severity": "warn"
},
"forin": false,
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
false,
"single"
],
"radix": true,
"semicolon": [
true,
"never"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}
Loading…
Cancel
Save