├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── definitions ├── base.json ├── descriptors.json ├── objects.json └── types.json ├── package.json ├── spec ├── autocomplete-solidity-spec.js ├── files │ ├── StringLib.sol │ ├── ballot.sol │ ├── bank.sol │ ├── pyramid.sol │ └── test.sol └── parser-spec.js └── src ├── autocomplete-solidity.js ├── providers ├── Provider.js ├── base-provider.js └── context-provider.js └── util ├── contracts.js └── parser.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.3.1 - Jun 22, 2016 2 | * Added time descriptors 3 | 4 | ### 0.3.0 - Jun 16, 2016 5 | * Added library support 6 | * Fixed some errors that would occur when coding in unique situations 7 | 8 | ### 0.2.0 - May 20, 2016 9 | * Added parser 10 | * Added contextual suggestions 11 | 12 | ### 0.1.0 - May 14, 2016 13 | * Initial Release 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autocomplete-solidity 2 | 3 | *An Autocomplete+ provider for Solidity.* 4 | 5 | This package parses your Solidity files to give you contextual autocomplete suggestions. That means not only does this package give you suggestions for base constructs / types, but it will also suggest your variables, functions, contracts, etc. What's more is that it will only give you suggestions when they allowed syntax-wise. (This is based on context level so you still could get some suggestions out of place, but nothing too radical should be happening.) 6 | 7 | ### Autocomplete Example 8 | ![autocomplete-solidity](https://cloud.githubusercontent.com/assets/2007045/15446666/daf7144c-1ee4-11e6-93fa-e2902ef954b9.gif) 9 | -------------------------------------------------------------------------------- /definitions/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "contract": { 4 | "leftLabel": "contract", 5 | "snippet": "contract ${1:SomeContract} {\n\t${2}\n}", 6 | "type": "keyword" 7 | }, 8 | 9 | "import": { 10 | "description": "Imports a Solidity file", 11 | "snippet": "import \"${1}\"${2}", 12 | "type": "import" 13 | }, 14 | 15 | "library": { 16 | "leftLabel": "library", 17 | "snippet": "library ${1:SomeLibrary} {\n\t${2}\n}", 18 | "type": "keyword" 19 | } 20 | }, 21 | 22 | "contract": { 23 | "anonymous": { 24 | "description": "Make the hash of the event signature not be added as a first topic", 25 | "text": "anonymous", 26 | "type": "keyword" 27 | }, 28 | 29 | "constant": { 30 | "text": "constant", 31 | "type": "keyword" 32 | }, 33 | 34 | "external": { 35 | "description": "Sets the visibility to contracts only", 36 | "descriptionMoreURL": "https://github.com/ethereum/wiki/wiki/Solidity-Features#visibility-specifiers-1", 37 | "text": "external", 38 | "type": "keyword" 39 | }, 40 | 41 | "function": { 42 | "snippet": "function ${1:someFunction}(${2}) ${3}{\n\t${4}\n}", 43 | "type": "keyword" 44 | }, 45 | 46 | "indexed": { 47 | "description": "Cause the respective argument to be treated as a log topic instead of data", 48 | "text": "indexed", 49 | "type": "keyword" 50 | }, 51 | 52 | "inheritable": { 53 | "description": "Sets the visibility to this contract / inherited contracts", 54 | "descriptionMoreURL": "https://github.com/ethereum/wiki/wiki/Solidity-Features#visibility-specifiers-1", 55 | "text": "inheritable", 56 | "type": "keyword" 57 | }, 58 | 59 | "internal": { 60 | "description": "Sets visibility so it can only be accessed from internally", 61 | "text": "internal", 62 | "type": "keyword" 63 | }, 64 | 65 | "private": { 66 | "description": "Sets the visibility to this contract", 67 | "descriptionMoreURL": "https://github.com/ethereum/wiki/wiki/Solidity-Features#visibility-specifiers-1", 68 | "text": "private", 69 | "type": "keyword" 70 | }, 71 | 72 | "public": { 73 | "description": "Sets the visibility to anywhere (default)", 74 | "descriptionMoreURL": "https://github.com/ethereum/wiki/wiki/Solidity-Features#visibility-specifiers-1", 75 | "text": "public", 76 | "type": "keyword" 77 | }, 78 | 79 | "returns": { 80 | "snippet": "returns (${1}) ", 81 | "type": "keyword" 82 | } 83 | }, 84 | 85 | "function": { 86 | "block": { 87 | "description": "Access the current block's properties", 88 | "text": "block", 89 | "type": "variable" 90 | }, 91 | 92 | "constant": { 93 | "text": "constant", 94 | "type": "keyword" 95 | }, 96 | 97 | "delete": { 98 | "description": "Use to delete structs", 99 | "text": "delete", 100 | "type": "keyword" 101 | }, 102 | 103 | "ecrecover": { 104 | "description": "Recover the public key from an elliptic curve signature", 105 | "leftLabel": "address", 106 | "snippet": "ecrecover(${1:data}, ${2:v}, ${3:r}, ${4:s})${5}", 107 | "type": "function" 108 | }, 109 | 110 | "msg": { 111 | "description": "Access the current message's properties", 112 | "text": "msg", 113 | "type": "variable" 114 | }, 115 | 116 | "now": { 117 | "description": "Current block's timestamp (alias for block.timestamp)", 118 | "leftLabel": "uint", 119 | "text": "now", 120 | "type": "variable" 121 | }, 122 | 123 | "return": { 124 | "snippet": "return ${1}", 125 | "type": "keyword" 126 | }, 127 | 128 | "ripemd160": { 129 | "description": "Computes the RIPEMD-160 hash of the (tightly packed) arguments", 130 | "leftLabel": "bytes20", 131 | "snippet": "ripemd160(${1})${2}", 132 | "type": "function" 133 | }, 134 | 135 | "sha256": { 136 | "description": "Computes the SHA256 hash of the (tightly packed) arguments", 137 | "leftLabel": "bytes32", 138 | "snippet": "sha256(${1})${2}", 139 | "type": "function" 140 | }, 141 | 142 | "sha3": { 143 | "description": "Computes the SHA3 hash of the (tightly packed) arguments", 144 | "leftLabel": "bytes32", 145 | "snippet": "sha3(${1})${2}", 146 | "type": "function" 147 | }, 148 | 149 | "suicide": { 150 | "description": "Suicides the current contract, sending its funds to the given address", 151 | "leftLabel": "void", 152 | "snippet": "suicide(${1:addr})${2}", 153 | "type": "function" 154 | }, 155 | 156 | "this": { 157 | "description": "The current contract - explicitly convertible to address", 158 | "leftLabel": "?", 159 | "text": "this", 160 | "type": "variable" 161 | }, 162 | 163 | "tx": { 164 | "description": "Access the current transaction's properties", 165 | "text": "tx", 166 | "type": "variable" 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /definitions/descriptors.json: -------------------------------------------------------------------------------- 1 | { 2 | "days": { 3 | "description": "1 day == 86400 seconds", 4 | "leftLabel": "day", 5 | "text": "days", 6 | "type": "descriptor" 7 | }, 8 | 9 | "ether": { 10 | "description": "1 ether == 1000000000000000000 wei", 11 | "leftLabel": "ether", 12 | "text": "ether", 13 | "type": "descriptor" 14 | }, 15 | 16 | "finney": { 17 | "description": "1 finey == 1000000000000000 wei", 18 | "leftLabel": "finney", 19 | "text": "finney", 20 | "type": "descriptor" 21 | }, 22 | 23 | "hours": { 24 | "description": "1 hour == 3600 seconds", 25 | "leftLabel": "hour", 26 | "text": "hours", 27 | "type": "descriptor" 28 | }, 29 | 30 | "minutes": { 31 | "description": "1 minute == 60 seconds", 32 | "leftLabel": "minute", 33 | "text": "minutes", 34 | "type": "descriptor" 35 | }, 36 | 37 | "seconds": { 38 | "description": "1 second", 39 | "leftLabel": "second", 40 | "text": "seconds", 41 | "type": "descriptor" 42 | }, 43 | 44 | "szabo": { 45 | "description": "1 szabo == 1000000000000 wei", 46 | "leftLabel": "szabo", 47 | "text": "szabo", 48 | "type": "descriptor" 49 | }, 50 | 51 | "weeks": { 52 | "description": "1 week == 604800 seconds", 53 | "leftLabel": "week", 54 | "text": "weeks", 55 | "type": "descriptor" 56 | }, 57 | 58 | "wei": { 59 | "description": "1 wei == 1/1000000000000000000 ether", 60 | "leftLabel": "wei", 61 | "text": "wei", 62 | "type": "descriptor" 63 | }, 64 | 65 | "years": { 66 | "description": "1 year == 31449600 seconds", 67 | "leftLabel": "year", 68 | "text": "years", 69 | "type": "descriptor" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /definitions/objects.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": { 3 | "balance": { 4 | "description": "The address's current balance", 5 | "leftLabel": "uint", 6 | "text": "balance", 7 | "type": "property" 8 | }, 9 | 10 | "call": { 11 | "description": "Interface with a contract that does not adhere to the ABI", 12 | "leftLabel": "bool", 13 | "snippet": "call(${1})${2}", 14 | "type": "method" 15 | }, 16 | 17 | "delegatecall": { 18 | "description": "Use library code which is stored in another contract", 19 | "leftLabel": "bool", 20 | "snippet": "delegatecall(${1})${2}", 21 | "type": "method" 22 | }, 23 | 24 | "send": { 25 | "description": "Send ether (in wei) to the address", 26 | "leftLabel": "bool", 27 | "snippet": "send(${1:amount})${2}", 28 | "type": "method" 29 | } 30 | }, 31 | 32 | "array": { 33 | "length": { 34 | "description": "The array's current length", 35 | "leftLabel": "uint", 36 | "text": "length", 37 | "type": "property" 38 | }, 39 | 40 | "push": { 41 | "description": "Add an item to dynamic storage arrays or bytes (not string)", 42 | "leftLabel": "uint", 43 | "snippet": "push(${1:item})${2}", 44 | "type": "method" 45 | } 46 | }, 47 | 48 | "block": { 49 | "blockhash": { 50 | "description": "Returns the hash of the given block", 51 | "leftLabel": "uint", 52 | "snippet": "blockhash(${1})${2}", 53 | "type": "method" 54 | }, 55 | 56 | "coinbase": { 57 | "description": "Current block's miner's address", 58 | "leftLabel": "address", 59 | "text": "coinbase", 60 | "type": "property" 61 | }, 62 | 63 | "difficulty": { 64 | "description": "Current block's difficulty", 65 | "leftLabel": "uint", 66 | "text": "difficulty", 67 | "type": "property" 68 | }, 69 | 70 | "gaslimit": { 71 | "description": "Current block's gas limit", 72 | "leftLabel": "uint", 73 | "text": "gaslimit", 74 | "type": "property" 75 | }, 76 | 77 | "number": { 78 | "description": "Current block's number", 79 | "leftLabel": "uint", 80 | "text": "number", 81 | "type": "property" 82 | }, 83 | 84 | "timestamp": { 85 | "description": "Current block's timestamp", 86 | "leftLabel": "uint", 87 | "text": "timestamp", 88 | "type": "property" 89 | } 90 | }, 91 | 92 | "msg": { 93 | "data": { 94 | "description": "The data of the message", 95 | "leftLabel": "bytes", 96 | "text": "data", 97 | "type": "property" 98 | }, 99 | 100 | "gas": { 101 | "description": "The amount of remaining gas", 102 | "leftLabel": "uint", 103 | "text": "gas", 104 | "type": "property" 105 | }, 106 | 107 | "sender": { 108 | "description": "The address of the message sender", 109 | "leftLabel": "address", 110 | "text": "sender", 111 | "type": "property" 112 | }, 113 | 114 | "sig": { 115 | "description": "The hash of the current function signature", 116 | "leftLabel": "bytes4", 117 | "text": "sig", 118 | "type": "property" 119 | }, 120 | 121 | "value": { 122 | "description": "The amount (in wei) sent with the message", 123 | "leftLabel": "uint", 124 | "text": "value", 125 | "type": "property" 126 | } 127 | }, 128 | 129 | "tx": { 130 | "gasprice": { 131 | "description": "The gas price (in wei) of the transaction", 132 | "leftLabel": "uint", 133 | "text": "gasprice", 134 | "type": "property" 135 | }, 136 | 137 | "origin": { 138 | "description": "The sender of the transaction", 139 | "leftLabel": "address", 140 | "text": "origin", 141 | "type": "property" 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /definitions/types.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": { 3 | "description": "Address", 4 | "leftLabel": "address", 5 | "text": "address", 6 | "type": "type" 7 | }, 8 | 9 | "bool": { 10 | "description": "Boolean", 11 | "leftLabel": "bool", 12 | "text": "bool", 13 | "type": "type" 14 | }, 15 | 16 | "byte": { 17 | "description": "1 byte", 18 | "leftLabel": "byte", 19 | "text": "byte", 20 | "type": "type" 21 | }, 22 | 23 | "bytes": { 24 | "description": "Dynamically-sized byte array", 25 | "leftLabel": "bytes", 26 | "text": "bytes", 27 | "type": "type" 28 | }, 29 | 30 | "enum": { 31 | "leftLabel": "enum", 32 | "snippet": "enum ${1:SomeEnum} {\n\t${2}\n}", 33 | "type": "type" 34 | }, 35 | 36 | "event": { 37 | "leftLabel": "event", 38 | "snippet": "event ${1:SomeEvent}(${2})${3}", 39 | "type": "type" 40 | }, 41 | 42 | "int": { 43 | "description": "Integer (256 bits)", 44 | "leftLabel": "int256", 45 | "text": "int", 46 | "type": "type" 47 | }, 48 | 49 | "mapping": { 50 | "leftLabel": "mapping", 51 | "snippet": "mapping (${1:type1} => ${2:type2}) ${3:someMapping}", 52 | "type": "type" 53 | }, 54 | 55 | "modifier": { 56 | "leftLabel": "modifier", 57 | "snippet": "modifier ${1:someModifier}(${2}) {\n\t${3}\n}", 58 | "type": "type" 59 | }, 60 | 61 | "string": { 62 | "description": "Dynamically-sized UTF8-encoded string", 63 | "leftLabel": "string", 64 | "text": "string", 65 | "type": "type" 66 | }, 67 | 68 | "struct": { 69 | "leftLabel": "struct", 70 | "snippet": "struct ${1:SomeStruct} {\n\t${2}\n}", 71 | "type": "type" 72 | }, 73 | 74 | "uint": { 75 | "description": "Unsigned integer (256 bits)", 76 | "leftLabel": "uint256", 77 | "text": "uint", 78 | "type": "type" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autocomplete-solidity", 3 | "main": "./src/autocomplete-solidity", 4 | "version": "0.3.4", 5 | "description": "Autocomplete+ provider for Solidity", 6 | "keywords": [ 7 | "solidity", 8 | "autocomplete-plus", 9 | "autocomplete", 10 | "ethereum", 11 | "ether" 12 | ], 13 | "repository": "https://github.com/austp/autocomplete-solidity", 14 | "license": "MIT", 15 | "engines": { 16 | "atom": ">=1.0.0 <2.0.0" 17 | }, 18 | "providedServices": { 19 | "autocomplete.provider": { 20 | "versions": { 21 | "2.0.0": "provide" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spec/autocomplete-solidity-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | import AutocompleteSolidity from '../src/autocomplete-solidity'; 5 | import parser from '../src/util/parser'; 6 | 7 | describe('AutocompleteSolidity', () => { 8 | let editor, lib_editor, providers; 9 | 10 | let getSuggestions = (_editor = editor) => { 11 | let cursor = _editor.getLastCursor(); 12 | let start = cursor.getBeginningOfCurrentWordBufferPosition(); 13 | let end = cursor.getBufferPosition(); 14 | let prefix = _editor.getTextInRange([start, end]); 15 | 16 | return providers[0].getSuggestions({ 17 | editor: _editor, 18 | bufferPosition: end, 19 | prefix: prefix 20 | }) 21 | .concat( 22 | providers[1].getSuggestions({ 23 | editor: _editor, 24 | bufferPosition: end, 25 | prefix: prefix 26 | }) 27 | ); 28 | }; 29 | 30 | let setText = (x, y, text, _editor = editor) => { 31 | _editor.setTextInBufferRange([[x, y], [x, Infinity]], text); 32 | _editor.setCursorBufferPosition([x, y + text.length]); 33 | parser.parse(_editor); 34 | }; 35 | 36 | beforeEach(() => { 37 | atom.project.setPaths([__dirname]); 38 | 39 | waitsForPromise(() => atom.workspace.open('files/test.sol')); 40 | waitsForPromise(() => atom.workspace.open('files/StringLib.sol')); 41 | waitsForPromise(() => atom.packages.activatePackage('autocomplete-solidity')); 42 | 43 | runs(() => { 44 | providers = AutocompleteSolidity.provide(); 45 | 46 | let editors = atom.workspace.getTextEditors(); 47 | editor = editors[0]; 48 | lib_editor = editors[1]; 49 | }); 50 | }); 51 | 52 | it('autocompletes contracts', () => { 53 | setText(1, 0, 'cont'); 54 | 55 | let suggestion = getSuggestions()[0]; 56 | expect(suggestion) 57 | .toEqual({ 58 | leftLabel: 'contract', 59 | snippet: 'contract ${1:SomeContract} {\n\t${2}\n}', 60 | type: 'keyword', 61 | replacementPrefix: 'cont', 62 | rightLabel: 'keyword' 63 | }); 64 | 65 | setText(53, 8, 'TestContract(0x0).o'); 66 | 67 | suggestion = getSuggestions()[0]; 68 | expect(suggestion) 69 | .toEqual({ 70 | leftLabel: 'address', 71 | replacementPrefix: 'o', 72 | rightLabel: 'variable', 73 | text: 'owner', 74 | type: 'variable' 75 | }); 76 | 77 | setText(53, 8, 'TestContract(0x0).T'); 78 | 79 | suggestion = getSuggestions()[0]; 80 | expect(suggestion) 81 | .toEqual({ 82 | description: 'Calls the TestContract function', 83 | leftLabel: 'void', 84 | replacementPrefix: 'T', 85 | rightLabel: 'function', 86 | snippet: 'TestContract${1}(${2:_owner}, ${3:_amount})${4}', 87 | type: 'function' 88 | }); 89 | }); 90 | 91 | it('autocompletes imports', () => { 92 | setText(1, 0, 'impo'); 93 | 94 | let suggestion = getSuggestions()[0]; 95 | expect(suggestion) 96 | .toEqual({ 97 | description: 'Imports a Solidity file', 98 | snippet: 'import "${1}"${2}', 99 | type: 'import', 100 | replacementPrefix: 'impo', 101 | rightLabel: 'import' 102 | }); 103 | }); 104 | 105 | it('autocompletes libraries', () => { 106 | setText(1, 0, 'libr'); 107 | 108 | let suggestion = getSuggestions()[0]; 109 | expect(suggestion) 110 | .toEqual({ 111 | leftLabel: 'library', 112 | replacementPrefix: 'libr', 113 | rightLabel: 'keyword', 114 | snippet: 'library ${1:SomeLibrary} {\n\t${2}\n}', 115 | type: 'keyword' 116 | }); 117 | 118 | setText(67, 8, 'StringUt', lib_editor); 119 | 120 | suggestion = getSuggestions(lib_editor)[0]; 121 | expect(suggestion) 122 | .toEqual({ 123 | leftLabel: 'library', 124 | functions: { 125 | uintToBytes: { 126 | description: 'Calls the uintToBytes function', 127 | leftLabel: 'bytes32 ret', 128 | snippet: 'uintToBytes(${1:v})${2}', 129 | type: 'function' 130 | }, 131 | bytesToUInt: { 132 | description: 'Calls the bytesToUInt function', 133 | leftLabel: 'uint ret', 134 | snippet: 'bytesToUInt(${1:v})${2}', 135 | type: 'function' 136 | } 137 | }, 138 | text: 'StringUtils', 139 | type: 'type', 140 | replacementPrefix: 'StringUt', 141 | rightLabel: 'type' 142 | }); 143 | 144 | setText(67, 8, 'StringUtils.u', lib_editor); 145 | 146 | suggestion = getSuggestions(lib_editor)[0]; 147 | expect(suggestion) 148 | .toEqual({ 149 | description: 'Calls the uintToBytes function', 150 | leftLabel: 'bytes32 ret', 151 | snippet: 'uintToBytes(${1:v})${2}', 152 | replacementPrefix: 'u', 153 | rightLabel: 'function', 154 | type: 'function' 155 | }); 156 | }); 157 | 158 | it('autocompletes types', () => { 159 | setText(5, 4, 'add'); 160 | 161 | let suggestion = getSuggestions()[0]; 162 | expect(suggestion) 163 | .toEqual({ 164 | description: 'Address', 165 | leftLabel: 'address', 166 | replacementPrefix: 'add', 167 | rightLabel: 'type', 168 | text: 'address', 169 | type: 'type' 170 | }); 171 | 172 | setText(5, 4, 'bo'); 173 | 174 | suggestion = getSuggestions()[0]; 175 | expect(suggestion) 176 | .toEqual({ 177 | description: 'Boolean', 178 | leftLabel: 'bool', 179 | replacementPrefix: 'bo', 180 | rightLabel: 'type', 181 | text: 'bool', 182 | type: 'type' 183 | }); 184 | 185 | setText(5, 4, 'by'); 186 | 187 | suggestion = getSuggestions()[0]; 188 | expect(suggestion) 189 | .toEqual({ 190 | description: '1 byte', 191 | leftLabel: 'byte', 192 | replacementPrefix: 'by', 193 | rightLabel: 'type', 194 | text: 'byte', 195 | type: 'type' 196 | }); 197 | 198 | setText(5, 4, 'by'); 199 | 200 | suggestion = getSuggestions()[1]; 201 | expect(suggestion) 202 | .toEqual({ 203 | description: 'Dynamically-sized byte array', 204 | leftLabel: 'bytes', 205 | replacementPrefix: 'by', 206 | rightLabel: 'type', 207 | text: 'bytes', 208 | type: 'type' 209 | }); 210 | }); 211 | 212 | it('autocompletes variables and parameters', () => { 213 | setText(33, 8, 'ow'); 214 | 215 | let suggestion = getSuggestions()[0]; 216 | expect(suggestion) 217 | .toEqual({ 218 | leftLabel: 'address', 219 | text: 'owner', 220 | type: 'variable', 221 | replacementPrefix: 'ow', 222 | rightLabel: 'variable' 223 | }); 224 | 225 | setText(33, 8, 'owner = _o'); 226 | 227 | suggestion = getSuggestions()[0]; 228 | expect(suggestion) 229 | .toEqual({ 230 | leftLabel: 'address', 231 | text: '_owner', 232 | type: 'parameter', 233 | replacementPrefix: '_o', 234 | rightLabel: 'parameter' 235 | }); 236 | 237 | setText(33, 8, 'am'); 238 | 239 | suggestion = getSuggestions()[0]; 240 | expect(suggestion) 241 | .toEqual({ 242 | leftLabel: 'uint', 243 | text: 'amount', 244 | type: 'variable', 245 | replacementPrefix: 'am', 246 | rightLabel: 'variable' 247 | }); 248 | 249 | setText(33, 8, 'amount = _a'); 250 | 251 | suggestion = getSuggestions()[0]; 252 | expect(suggestion) 253 | .toEqual({ 254 | leftLabel: 'uint', 255 | text: '_amount', 256 | type: 'parameter', 257 | replacementPrefix: '_a', 258 | rightLabel: 'parameter' 259 | }); 260 | }); 261 | 262 | it('autocompletes functions / events', () => { 263 | setText(36, 8, 'an'); 264 | 265 | let suggestion = getSuggestions()[0]; 266 | expect(suggestion) 267 | .toEqual({ 268 | description: 'Calls the anotherFunction function', 269 | leftLabel: 'void', 270 | replacementPrefix: 'an', 271 | rightLabel: 'function', 272 | snippet: 'anotherFunction()${1}', 273 | type: 'function' 274 | }); 275 | 276 | setText(36, 8, 'this.'); 277 | 278 | suggestion = getSuggestions()[0]; 279 | expect(suggestion) 280 | .toEqual({ 281 | description: 'Calls the anotherFunction function', 282 | leftLabel: 'void', 283 | replacementPrefix: '', 284 | rightLabel: 'function', 285 | snippet: 'anotherFunction${1}()${2}', 286 | type: 'function' 287 | }); 288 | 289 | setText(36, 8, 'So'); 290 | 291 | suggestion = getSuggestions()[3]; 292 | expect(suggestion) 293 | .toEqual({ 294 | description: 'Emits the SomeEvent event', 295 | leftLabel: 'void', 296 | replacementPrefix: 'So', 297 | rightLabel: 'event', 298 | snippet: 'SomeEvent(${1:addr})${2}', 299 | type: 'event' 300 | }); 301 | }); 302 | 303 | it('autocompletes structs / enums', () => { 304 | setText(39, 8, 'myStruct.a'); 305 | 306 | let suggestion = getSuggestions()[0]; 307 | expect(suggestion) 308 | .toEqual({ 309 | leftLabel: 'address', 310 | text: 'addr', 311 | type: 'struct', 312 | replacementPrefix: 'a', 313 | rightLabel: 'struct' 314 | }); 315 | 316 | setText(39, 8, 'myStruct.s'); 317 | 318 | suggestion = getSuggestions()[0]; 319 | expect(suggestion) 320 | .toEqual({ 321 | leftLabel: 'SomeEnum', 322 | text: 'someEnum', 323 | type: 'struct', 324 | replacementPrefix: 's', 325 | rightLabel: 'struct' 326 | }); 327 | 328 | setText(39, 8, 'myStruct.someEnum = SomeEnum.O'); 329 | 330 | suggestion = getSuggestions()[0]; 331 | expect(suggestion) 332 | .toEqual({ 333 | text: 'One', 334 | type: 'enum', 335 | replacementPrefix: 'O', 336 | rightLabel: 'enum' 337 | }); 338 | 339 | setText(39, 8, 'myStruct.someEnum = SomeEnum.T'); 340 | 341 | suggestion = getSuggestions()[0]; 342 | expect(suggestion) 343 | .toEqual({ 344 | text: 'Two', 345 | type: 'enum', 346 | replacementPrefix: 'T', 347 | rightLabel: 'enum' 348 | }); 349 | 350 | setText(39, 8, 'SomeStruct.'); 351 | 352 | suggestion = getSuggestions()[0]; 353 | expect(suggestion) 354 | .toEqual(undefined); 355 | }); 356 | 357 | it('autocompletes enums / events / functions / modifiers', () => { 358 | setText(47, 4, 'en'); 359 | 360 | let suggestion = getSuggestions()[0]; 361 | expect(suggestion) 362 | .toEqual({ 363 | leftLabel: 'enum', 364 | snippet: 'enum ${1:SomeEnum} {\n\t${2}\n}', 365 | type: 'type', 366 | replacementPrefix: 'en', 367 | rightLabel: 'type' 368 | }); 369 | 370 | setText(47, 4, 'ev'); 371 | 372 | suggestion = getSuggestions()[0]; 373 | expect(suggestion) 374 | .toEqual({ 375 | leftLabel: 'event', 376 | snippet: 'event ${1:SomeEvent}(${2})${3}', 377 | type: 'type', 378 | replacementPrefix: 'ev', 379 | rightLabel: 'type' 380 | }); 381 | 382 | setText(47, 4, 'fu'); 383 | 384 | suggestion = getSuggestions()[0]; 385 | expect(suggestion) 386 | .toEqual({ 387 | snippet: 'function ${1:someFunction}(${2}) ${3}{\n\t${4}\n}', 388 | type: 'keyword', 389 | replacementPrefix: 'fu', 390 | rightLabel: 'keyword' 391 | }); 392 | 393 | setText(47, 4, 'function testFunction() on'); 394 | 395 | suggestion = getSuggestions()[0]; 396 | expect(suggestion) 397 | .toEqual({ 398 | description: 'Applies the onlyowner modifier', 399 | snippet: 'onlyowner()${1}', 400 | type: 'modifier', 401 | replacementPrefix: 'on', 402 | rightLabel: 'modifier' 403 | }); 404 | 405 | setText(47, 4, 'function testFunction() pu'); 406 | 407 | suggestion = getSuggestions()[0]; 408 | expect(suggestion) 409 | .toEqual({ 410 | description: 'Sets the visibility to anywhere (default)', 411 | descriptionMoreURL: 'https://github.com/ethereum/wiki/wiki/Solidity-Features#visibility-specifiers-1', 412 | replacementPrefix: 'pu', 413 | rightLabel: 'keyword', 414 | text: 'public', 415 | type: 'keyword' 416 | }); 417 | 418 | setText(47, 4, 'function testFunction() ex'); 419 | 420 | suggestion = getSuggestions()[0]; 421 | expect(suggestion) 422 | .toEqual({ 423 | description: 'Sets the visibility to contracts only', 424 | descriptionMoreURL: 'https://github.com/ethereum/wiki/wiki/Solidity-Features#visibility-specifiers-1', 425 | replacementPrefix: 'ex', 426 | rightLabel: 'keyword', 427 | text: 'external', 428 | type: 'keyword' 429 | }); 430 | 431 | setText(47, 4, 'function testFunction() int'); 432 | 433 | suggestion = getSuggestions()[0]; 434 | expect(suggestion) 435 | .toEqual({ 436 | description: 'Sets visibility so it can only be accessed from internally', 437 | text: 'internal', 438 | type: 'keyword', 439 | replacementPrefix: 'int', 440 | rightLabel: 'keyword' 441 | }); 442 | 443 | setText(47, 4, 'function testFunction() pr'); 444 | 445 | suggestion = getSuggestions()[0]; 446 | expect(suggestion) 447 | .toEqual({ 448 | description: 'Sets the visibility to this contract', 449 | descriptionMoreURL: 'https://github.com/ethereum/wiki/wiki/Solidity-Features#visibility-specifiers-1', 450 | replacementPrefix: 'pr', 451 | rightLabel: 'keyword', 452 | text: 'private', 453 | type: 'keyword' 454 | }); 455 | 456 | setText(47, 4, 'function testFunction() re'); 457 | 458 | suggestion = getSuggestions()[0]; 459 | expect(suggestion) 460 | .toEqual({ 461 | snippet: 'returns (${1}) ', 462 | type: 'keyword', 463 | replacementPrefix: 're', 464 | rightLabel: 'keyword' 465 | }); 466 | 467 | setText(47, 4, 'mo'); 468 | 469 | suggestion = getSuggestions()[0]; 470 | expect(suggestion) 471 | .toEqual({ 472 | leftLabel: 'modifier', 473 | snippet: 'modifier ${1:someModifier}(${2}) {\n\t${3}\n}', 474 | type: 'type', 475 | replacementPrefix: 'mo', 476 | rightLabel: 'type' 477 | }); 478 | }); 479 | 480 | it('understands global context', () => { 481 | setText(1, 0, 'funct'); 482 | 483 | let suggestions = getSuggestions(); 484 | expect(suggestions.length).toBe(0); 485 | }); 486 | 487 | it('understands contract context', () => { 488 | setText(5, 4, 'cont'); 489 | 490 | let suggestions = getSuggestions(); 491 | expect(suggestions.length).toBe(0); 492 | }); 493 | 494 | it('understands function context', () => { 495 | setText(33, 8, 'enu'); 496 | 497 | let suggestions = getSuggestions(); 498 | expect(suggestions.length).toBe(0); 499 | 500 | setText(33, 8, 'even'); 501 | 502 | suggestions = getSuggestions(); 503 | expect(suggestions.length).toBe(0); 504 | 505 | setText(33, 8, 'modifi'); 506 | 507 | suggestions = getSuggestions(); 508 | expect(suggestions.length).toBe(0); 509 | 510 | setText(33, 8, 'struc'); 511 | 512 | suggestions = getSuggestions(); 513 | expect(suggestions.length).toBe(0); 514 | }); 515 | }); 516 | -------------------------------------------------------------------------------- /spec/files/StringLib.sol: -------------------------------------------------------------------------------- 1 | // https://github.com/pipermerriam/ethereum-string-utils 2 | // String Utils v0.1 3 | 4 | /// @title String Utils - String utility functions 5 | /// @author Piper Merriam - 6 | library StringLib { 7 | /// @dev Converts an unsigned integert to its string representation. 8 | /// @param v The number to be converted. 9 | function uintToBytes(uint v) constant returns (bytes32 ret) { 10 | if (v == 0) { 11 | ret = '0'; 12 | } 13 | else { 14 | while (v > 0) { 15 | ret = bytes32(uint(ret) / (2 ** 8)); 16 | ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31)); 17 | v /= 10; 18 | } 19 | } 20 | return ret; 21 | } 22 | 23 | /// @dev Converts a numeric string to it's unsigned integer representation. 24 | /// @param v The string to be converted. 25 | function bytesToUInt(bytes32 v) constant returns (uint ret) { 26 | if (v == 0x0) { 27 | throw; 28 | } 29 | 30 | uint digit; 31 | 32 | for (uint i = 0; i < 32; i++) { 33 | digit = uint((uint(v) / (2 ** (8 * (31 - i)))) & 0xff); 34 | if (digit == 0) { 35 | break; 36 | } 37 | else if (digit < 48 || digit > 57) { 38 | throw; 39 | } 40 | ret *= 10; 41 | ret += (digit - 48); 42 | } 43 | return ret; 44 | } 45 | } 46 | 47 | 48 | /// @title String Utils - String utility functions 49 | /// @author Piper Merriam - 50 | library StringUtils { 51 | /// @dev Converts an unsigned integert to its string representation. 52 | /// @param v The number to be converted. 53 | function uintToBytes(uint v) constant returns (bytes32 ret) { 54 | return StringLib.uintToBytes(v); 55 | } 56 | 57 | /// @dev Converts a numeric string to it's unsigned integer representation. 58 | /// @param v The string to be converted. 59 | function bytesToUInt(bytes32 v) constant returns (uint ret) { 60 | return StringLib.bytesToUInt(v); 61 | } 62 | } 63 | 64 | // added for testing 65 | contract SomeContract { 66 | function someFunction() { 67 | // test library autocompletion here (67, 8) 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /spec/files/ballot.sol: -------------------------------------------------------------------------------- 1 | // https://chriseth.github.io/browser-solidity/#version=soljson-latest.js 2 | 3 | contract Ballot { 4 | 5 | struct Voter { 6 | uint weight; 7 | bool voted; 8 | uint8 vote; 9 | address delegate; 10 | } 11 | struct Proposal { 12 | uint voteCount; 13 | } 14 | 15 | address chairperson; 16 | mapping(address => Voter) voters; 17 | Proposal[] proposals; 18 | 19 | // Create a new ballot with $(_numProposals) different proposals. 20 | function Ballot(uint8 _numProposals) { 21 | chairperson = msg.sender; 22 | voters[chairperson].weight = 1; 23 | proposals.length = _numProposals; 24 | } 25 | 26 | // Give $(voter) the right to vote on this ballot. 27 | // May only be called by $(chairperson). 28 | function giveRightToVote(address voter) { 29 | if (msg.sender != chairperson || voters[voter].voted) return; 30 | voters[voter].weight = 1; 31 | } 32 | 33 | // Delegate your vote to the voter $(to). 34 | function delegate(address to) { 35 | Voter sender = voters[msg.sender]; // assigns reference 36 | if (sender.voted) return; 37 | while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender) 38 | to = voters[to].delegate; 39 | if (to == msg.sender) return; 40 | sender.voted = true; 41 | sender.delegate = to; 42 | Voter delegate = voters[to]; 43 | if (delegate.voted) 44 | proposals[delegate.vote].voteCount += sender.weight; 45 | else 46 | delegate.weight += sender.weight; 47 | } 48 | 49 | // Give a single vote to proposal $(proposal). 50 | function vote(uint8 proposal) { 51 | Voter sender = voters[msg.sender]; 52 | if (sender.voted || proposal >= proposals.length) return; 53 | sender.voted = true; 54 | sender.vote = proposal; 55 | proposals[proposal].voteCount += sender.weight; 56 | } 57 | 58 | function winningProposal() constant returns (uint8 winningProposal) { 59 | uint256 winningVoteCount = 0; 60 | for (uint8 proposal = 0; proposal < proposals.length; proposal++) 61 | if (proposals[proposal].voteCount > winningVoteCount) { 62 | winningVoteCount = proposals[proposal].voteCount; 63 | winningProposal = proposal; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /spec/files/bank.sol: -------------------------------------------------------------------------------- 1 | // https://docs.erisindustries.com/tutorials/solidity/solidity-1/ 2 | 3 | // Base class for contracts that are used in a doug system. 4 | contract DougEnabled { 5 | address DOUG; 6 | 7 | function setDougAddress(address dougAddr) returns (bool result){ 8 | // Once the doug address is set, don't allow it to be set again, except by the 9 | // doug contract itself. 10 | if(DOUG != 0x0 && msg.sender != DOUG){ 11 | return false; 12 | } 13 | DOUG = dougAddr; 14 | return true; 15 | } 16 | 17 | // Makes it so that Doug is the only contract that may kill it. 18 | function remove(){ 19 | if(msg.sender == DOUG){ 20 | suicide(DOUG); 21 | } 22 | } 23 | 24 | } 25 | 26 | // The Doug contract. 27 | contract Doug { 28 | 29 | address owner; 30 | 31 | // This is where we keep all the contracts. 32 | mapping (bytes32 => address) public contracts; 33 | 34 | modifier onlyOwner { //a modifier to reduce code replication 35 | if (msg.sender == owner) // this ensures that only the owner can access the function 36 | _ 37 | } 38 | // Constructor 39 | function Doug(){ 40 | owner = msg.sender; 41 | } 42 | 43 | // Add a new contract to Doug. This will overwrite an existing contract. 44 | function addContract(bytes32 name, address addr) onlyOwner returns (bool result) { 45 | DougEnabled de = DougEnabled(addr); 46 | // Don't add the contract if this does not work. 47 | if(!de.setDougAddress(address(this))) { 48 | return false; 49 | } 50 | contracts[name] = addr; 51 | return true; 52 | } 53 | 54 | // Remove a contract from Doug. We could also suicide if we want to. 55 | function removeContract(bytes32 name) onlyOwner returns (bool result) { 56 | if (contracts[name] == 0x0){ 57 | return false; 58 | } 59 | contracts[name] = 0x0; 60 | return true; 61 | } 62 | 63 | function remove() onlyOwner { 64 | address fm = contracts["fundmanager"]; 65 | address perms = contracts["perms"]; 66 | address permsdb = contracts["permsdb"]; 67 | address bank = contracts["bank"]; 68 | address bankdb = contracts["bankdb"]; 69 | 70 | // Remove everything. 71 | if(fm != 0x0){ DougEnabled(fm).remove(); } 72 | if(perms != 0x0){ DougEnabled(perms).remove(); } 73 | if(permsdb != 0x0){ DougEnabled(permsdb).remove(); } 74 | if(bank != 0x0){ DougEnabled(bank).remove(); } 75 | if(bankdb != 0x0){ DougEnabled(bankdb).remove(); } 76 | 77 | // Finally, remove doug. Doug will now have all the funds of the other contracts, 78 | // and when suiciding it will all go to the owner. 79 | suicide(owner); 80 | } 81 | 82 | } 83 | 84 | // Interface for getting contracts from Doug 85 | contract ContractProvider { 86 | function contracts(bytes32 name) returns (address addr) {} 87 | } 88 | 89 | // Base class for contracts that only allow the fundmanager to call them. 90 | // Note that it inherits from DougEnabled 91 | contract FundManagerEnabled is DougEnabled { 92 | 93 | // Makes it easier to check that fundmanager is the caller. 94 | function isFundManager() constant returns (bool) { 95 | if(DOUG != 0x0){ 96 | address fm = ContractProvider(DOUG).contracts("fundmanager"); 97 | return msg.sender == fm; 98 | } 99 | return false; 100 | } 101 | } 102 | 103 | // Permissions database 104 | contract PermissionsDb is DougEnabled { 105 | 106 | mapping (address => uint8) public perms; 107 | 108 | // Set the permissions of an account. 109 | function setPermission(address addr, uint8 perm) returns (bool res) { 110 | if(DOUG != 0x0){ 111 | address permC = ContractProvider(DOUG).contracts("perms"); 112 | if (msg.sender == permC ){ 113 | perms[addr] = perm; 114 | return true; 115 | } 116 | return false; 117 | } else { 118 | return false; 119 | } 120 | } 121 | 122 | } 123 | 124 | // Permissions 125 | contract Permissions is FundManagerEnabled { 126 | 127 | // Set the permissions of an account. 128 | function setPermission(address addr, uint8 perm) returns (bool res) { 129 | if (!isFundManager()){ 130 | return false; 131 | } 132 | address permdb = ContractProvider(DOUG).contracts("permsdb"); 133 | if ( permdb == 0x0 ) { 134 | return false; 135 | } 136 | return PermissionsDb(permdb).setPermission(addr, perm); 137 | } 138 | 139 | } 140 | 141 | // The bank database 142 | contract BankDb is DougEnabled { 143 | 144 | mapping (address => uint) public balances; 145 | 146 | function deposit(address addr) returns (bool res) { 147 | if(DOUG != 0x0){ 148 | address bank = ContractProvider(DOUG).contracts("bank"); 149 | if (msg.sender == bank ){ 150 | balances[addr] += msg.value; 151 | return true; 152 | } 153 | } 154 | // Return if deposit cannot be made. 155 | msg.sender.send(msg.value); 156 | return false; 157 | } 158 | 159 | function withdraw(address addr, uint amount) returns (bool res) { 160 | if(DOUG != 0x0){ 161 | address bank = ContractProvider(DOUG).contracts("bank"); 162 | if (msg.sender == bank ){ 163 | uint oldBalance = balances[addr]; 164 | if(oldBalance >= amount){ 165 | msg.sender.send(amount); 166 | balances[addr] = oldBalance - amount; 167 | return true; 168 | } 169 | } 170 | } 171 | return false; 172 | } 173 | 174 | } 175 | 176 | // The bank 177 | contract Bank is FundManagerEnabled { 178 | 179 | // Attempt to withdraw the given 'amount' of Ether from the account. 180 | function deposit(address userAddr) returns (bool res) { 181 | if (!isFundManager()){ 182 | return false; 183 | } 184 | address bankdb = ContractProvider(DOUG).contracts("bankdb"); 185 | if ( bankdb == 0x0 ) { 186 | // If the user sent money, we should return it if we can't deposit. 187 | msg.sender.send(msg.value); 188 | return false; 189 | } 190 | 191 | // Use the interface to call on the bank contract. We pass msg.value along as well. 192 | bool success = BankDb(bankdb).deposit.value(msg.value)(userAddr); 193 | 194 | // If the transaction failed, return the Ether to the caller. 195 | if (!success) { 196 | msg.sender.send(msg.value); 197 | } 198 | return success; 199 | } 200 | 201 | // Attempt to withdraw the given 'amount' of Ether from the account. 202 | function withdraw(address userAddr, uint amount) returns (bool res) { 203 | if (!isFundManager()){ 204 | return false; 205 | } 206 | address bankdb = ContractProvider(DOUG).contracts("bankdb"); 207 | if ( bankdb == 0x0 ) { 208 | return false; 209 | } 210 | 211 | // Use the interface to call on the bank contract. 212 | bool success = BankDb(bankdb).withdraw(userAddr, amount); 213 | 214 | // If the transaction succeeded, pass the Ether on to the caller. 215 | if (success) { 216 | userAddr.send(amount); 217 | } 218 | return success; 219 | } 220 | 221 | } 222 | 223 | // The fund manager 224 | contract FundManager is DougEnabled { 225 | 226 | // We still want an owner. 227 | address owner; 228 | 229 | // Constructor 230 | function FundManager(){ 231 | owner = msg.sender; 232 | } 233 | 234 | // Attempt to withdraw the given 'amount' of Ether from the account. 235 | function deposit() returns (bool res) { 236 | if (msg.value == 0){ 237 | return false; 238 | } 239 | address bank = ContractProvider(DOUG).contracts("bank"); 240 | address permsdb = ContractProvider(DOUG).contracts("permsdb"); 241 | if ( bank == 0x0 || permsdb == 0x0 || PermissionsDb(permsdb).perms(msg.sender) < 1) { 242 | // If the user sent money, we should return it if we can't deposit. 243 | msg.sender.send(msg.value); 244 | return false; 245 | } 246 | 247 | // Use the interface to call on the bank contract. We pass msg.value along as well. 248 | bool success = Bank(bank).deposit.value(msg.value)(msg.sender); 249 | 250 | // If the transaction failed, return the Ether to the caller. 251 | if (!success) { 252 | msg.sender.send(msg.value); 253 | } 254 | return success; 255 | } 256 | 257 | // Attempt to withdraw the given 'amount' of Ether from the account. 258 | function withdraw(uint amount) returns (bool res) { 259 | if (amount == 0){ 260 | return false; 261 | } 262 | address bank = ContractProvider(DOUG).contracts("bank"); 263 | address permsdb = ContractProvider(DOUG).contracts("permsdb"); 264 | if ( bank == 0x0 || permsdb == 0x0 || PermissionsDb(permsdb).perms(msg.sender) < 1) { 265 | // If the user sent money, we should return it if we can't deposit. 266 | msg.sender.send(msg.value); 267 | return false; 268 | } 269 | 270 | // Use the interface to call on the bank contract. 271 | bool success = Bank(bank).withdraw(msg.sender, amount); 272 | 273 | // If the transaction succeeded, pass the Ether on to the caller. 274 | if (success) { 275 | msg.sender.send(amount); 276 | } 277 | return success; 278 | } 279 | 280 | // Set the permissions for a given address. 281 | function setPermission(address addr, uint8 permLvl) returns (bool res) { 282 | if (msg.sender != owner){ 283 | return false; 284 | } 285 | address perms = ContractProvider(DOUG).contracts("perms"); 286 | if ( perms == 0x0 ) { 287 | return false; 288 | } 289 | return Permissions(perms).setPermission(addr,permLvl); 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /spec/files/pyramid.sol: -------------------------------------------------------------------------------- 1 | // https://ethereumpyramid.com/contract.html 2 | 3 | contract Pyramid { 4 | enum PayoutType { Ether, Bitcoin } 5 | 6 | struct Participant { 7 | PayoutType payoutType; 8 | bytes desc; 9 | address etherAddress; 10 | bytes bitcoinAddress; 11 | } 12 | 13 | Participant[] public participants; 14 | 15 | uint public payoutIdx = 0; 16 | uint public collectedFees; 17 | 18 | address public owner; 19 | address public bitcoinBridge; 20 | 21 | // used later to restrict some methods 22 | modifier onlyowner { if (msg.sender == owner) _ } 23 | 24 | // events make it easier to interface with the contract 25 | event NewParticipant(uint indexed idx); 26 | 27 | function Pyramid(address _bitcoinBridge) { 28 | owner = msg.sender; 29 | bitcoinBridge = _bitcoinBridge; 30 | } 31 | 32 | // fallback function - simple transactions trigger this 33 | function() { 34 | enter(msg.data, ''); 35 | } 36 | 37 | function enter(bytes desc, bytes bitcoinAddress) { 38 | if (msg.value < 1 ether) { 39 | msg.sender.send(msg.value); 40 | return; 41 | } 42 | 43 | if (desc.length > 16 || bitcoinAddress.length > 35) { 44 | msg.sender.send(msg.value); 45 | return; 46 | } 47 | 48 | if (msg.value > 1 ether) { 49 | msg.sender.send(msg.value - 1 ether); 50 | } 51 | 52 | uint idx = participants.length; 53 | participants.length += 1; 54 | participants[idx].desc = desc; 55 | if (bitcoinAddress.length > 0) { 56 | participants[idx].payoutType = PayoutType.Bitcoin; 57 | participants[idx].bitcoinAddress = bitcoinAddress; 58 | } else { 59 | participants[idx].payoutType = PayoutType.Ether; 60 | participants[idx].etherAddress = msg.sender; 61 | } 62 | 63 | NewParticipant(idx); 64 | 65 | if (idx != 0) { 66 | collectedFees += 100 finney; 67 | } else { 68 | // first participant has no one above them, 69 | // so it goes all to fees 70 | collectedFees += 1 ether; 71 | } 72 | 73 | // for every three new participants we can 74 | // pay out to an earlier participant 75 | if (idx != 0 && idx % 3 == 0) { 76 | // payout is triple, minus 10 % fee 77 | uint amount = 3 ether - 300 finney; 78 | 79 | if (participants[payoutIdx].payoutType == PayoutType.Ether) { 80 | participants[payoutIdx].etherAddress.send(amount); 81 | } else { 82 | BitcoinBridge(bitcoinBridge).queuePayment.value(amount)(participants[payoutIdx].bitcoinAddress); 83 | } 84 | 85 | payoutIdx += 1; 86 | } 87 | } 88 | 89 | function getNumberOfParticipants() constant returns (uint n) { 90 | return participants.length; 91 | } 92 | 93 | function collectFees(address recipient) onlyowner { 94 | if (collectedFees == 0) return; 95 | 96 | recipient.send(collectedFees); 97 | collectedFees = 0; 98 | } 99 | 100 | function setBitcoinBridge(address _bitcoinBridge) onlyowner { 101 | bitcoinBridge = _bitcoinBridge; 102 | } 103 | 104 | function setOwner(address _owner) onlyowner { 105 | owner = _owner; 106 | } 107 | } 108 | 109 | contract BitcoinBridge { 110 | function queuePayment(bytes bitcoinAddress) returns(bool successful); 111 | } 112 | -------------------------------------------------------------------------------- /spec/files/test.sol: -------------------------------------------------------------------------------- 1 | // test new contract / import here (1, 0) 2 | 3 | 4 | contract TestContract { 5 | // test types here (5, 4) 6 | 7 | 8 | address owner; 9 | uint amount; 10 | 11 | enum SomeEnum { 12 | One, 13 | Two 14 | } 15 | 16 | event SomeEvent(address indexed addr); 17 | 18 | mapping (address => bool) someMapping; 19 | 20 | modifier onlyowner() { 21 | if (msg.sender == owner) { 22 | _ 23 | } 24 | } 25 | 26 | struct SomeStruct { 27 | address addr; 28 | SomeEnum someEnum; 29 | } 30 | SomeStruct myStruct; 31 | 32 | function TestContract(address _owner, uint _amount) { 33 | // test variable / parameter autocompletion here (33, 8) 34 | 35 | 36 | // test calls to anotherFunction / SomeEvent here (36, 8) 37 | 38 | 39 | // test autocompletions of struct properties / enum properties here (39, 8) 40 | 41 | } 42 | 43 | function anotherFunction() { 44 | 45 | } 46 | 47 | // test enum / event / function / modifier snippets here (47, 4) 48 | 49 | } 50 | 51 | contract SomeContract { 52 | function someFunction() { 53 | // test autocompletions of contracts here (53, 8) 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/parser-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | import parser from '../src/util/parser'; 5 | 6 | describe('AutocompleteSolidity Parser', () => { 7 | let editors; 8 | 9 | beforeEach(() => { 10 | atom.project.setPaths([__dirname]); 11 | 12 | waitsForPromise(() => atom.workspace.open('files/ballot.sol')); 13 | waitsForPromise(() => atom.workspace.open('files/bank.sol')); 14 | waitsForPromise(() => atom.workspace.open('files/pyramid.sol')); 15 | waitsForPromise(() => atom.workspace.open('files/StringLib.sol')); 16 | waitsForPromise(() => atom.packages.activatePackage('autocomplete-solidity')); 17 | 18 | runs(() => { 19 | editors = atom.workspace.getTextEditors(); 20 | 21 | parser.parse(editors[0]); 22 | parser.parse(editors[1]); 23 | parser.parse(editors[2]); 24 | parser.parse(editors[3]); 25 | }); 26 | }); 27 | 28 | it('parses contracts', () => { 29 | let editor = editors[1]; 30 | let structure = parser.getStructure(editor); 31 | 32 | expect(Object.keys(structure.contracts).sort()) 33 | .toEqual([ 34 | 'Bank', 'BankDb', 'ContractProvider', 'Doug', 'DougEnabled', 35 | 'FundManager', 'FundManagerEnabled', 'Permissions', 'PermissionsDb' 36 | ]); 37 | }); 38 | 39 | it('parses libraries', () => { 40 | let editor = editors[3]; 41 | let structure = parser.getStructure(editor); 42 | 43 | for (let contract in structure.contracts) { 44 | let info = structure.contracts[contract]; 45 | 46 | if (contract == 'StringLib' || contract == 'StringUtils') { 47 | expect(info.library).toBe(true); 48 | } else { 49 | expect(info.library).toBe(false); 50 | } 51 | } 52 | }); 53 | 54 | it('parses enums', () => { 55 | let editor = editors[2]; 56 | let structure = parser.getStructure(editor); 57 | 58 | expect(structure.contracts.Pyramid.enums.PayoutType.properties) 59 | .toEqual(['Ether', 'Bitcoin']); 60 | }); 61 | 62 | it('parses events', () => { 63 | let editor = editors[2]; 64 | let structure = parser.getStructure(editor); 65 | 66 | expect(structure.contracts.Pyramid.events.NewParticipant.params) 67 | .toEqual([{name: 'idx', type: 'uint'}]); 68 | }); 69 | 70 | it('parses contracts that extend', () => { 71 | let editor = editors[1]; 72 | let structure = parser.getStructure(editor); 73 | 74 | expect(structure.contracts.Bank.extends) 75 | .toEqual(['FundManagerEnabled']); 76 | }); 77 | 78 | it('parses functions', () => { 79 | let editor = editors[1]; 80 | let structure = parser.getStructure(editor); 81 | 82 | expect(structure.contracts.Bank.functions.deposit.params) 83 | .toEqual([{name: 'userAddr', type: 'address'}]); 84 | 85 | expect(structure.contracts.Bank.functions.deposit.returns) 86 | .toEqual('bool res'); 87 | 88 | expect(structure.contracts.Bank.functions.deposit.variables) 89 | .toEqual({bankdb: {type: 'address'}, success: {type: 'bool'}}); 90 | }); 91 | 92 | it('parses modifiers', () => { 93 | let editor = editors[2]; 94 | let structure = parser.getStructure(editor); 95 | 96 | expect(structure.contracts.Pyramid.modifiers.onlyowner) 97 | .toEqual({params: [], variables: {}}); 98 | }); 99 | 100 | it('parses structs', () => { 101 | let editor = editors[0]; 102 | let structure = parser.getStructure(editor); 103 | 104 | expect(structure.contracts.Ballot.structs.Voter.properties) 105 | .toEqual({ 106 | delegate: {type: 'address'}, 107 | vote: {type: 'uint8'}, 108 | voted: {type: 'bool'}, 109 | weight: {type: 'uint'} 110 | }); 111 | }); 112 | 113 | it('parses variables', () => { 114 | let editor = editors[0]; 115 | let structure = parser.getStructure(editor); 116 | 117 | expect(structure.contracts.Ballot.variables) 118 | .toEqual({ 119 | chairperson: {type: 'address'}, 120 | proposals: {type: 'Proposal[]'}, 121 | voters: {type: 'address=>Voter'} 122 | }); 123 | }); 124 | 125 | it('understands global context', () => { 126 | let editor = editors[0]; 127 | editor.setCursorBufferPosition([1, 0]); 128 | parser.parse(editor); 129 | 130 | let structure = parser.getStructure(editor); 131 | 132 | 133 | 134 | expect(structure.context) 135 | .toEqual({ 136 | contract: '', 137 | level: 'global', 138 | name: '', 139 | prev: '' 140 | }); 141 | }); 142 | 143 | it('understands contract context', () => { 144 | let editor = editors[0]; 145 | editor.setCursorBufferPosition([3, 0]); 146 | parser.parse(editor); 147 | 148 | let structure = parser.getStructure(editor); 149 | 150 | expect(structure.context) 151 | .toEqual({ 152 | contract: 'Ballot', 153 | level: 'contract', 154 | name: '', 155 | prev: '{' 156 | }); 157 | }); 158 | 159 | it('understands enum context', () => { 160 | let editor = editors[2]; 161 | editor.setCursorBufferPosition([3, 28]); 162 | parser.parse(editor); 163 | 164 | let structure = parser.getStructure(editor); 165 | 166 | expect(structure.context) 167 | .toEqual({ 168 | contract: 'Pyramid', 169 | level: 'enum', 170 | name: 'PayoutType', 171 | prev: '{' 172 | }); 173 | }); 174 | 175 | it('understands function context', () => { 176 | let editor = editors[2]; 177 | editor.setCursorBufferPosition([27, 27]); 178 | parser.parse(editor); 179 | 180 | let structure = parser.getStructure(editor); 181 | 182 | expect(structure.context) 183 | .toEqual({ 184 | contract: 'Pyramid', 185 | level: 'function', 186 | name: 'Pyramid', 187 | prev: 'msg.' 188 | }); 189 | }); 190 | 191 | it('understands modifier context', () => { 192 | let editor = editors[2]; 193 | editor.setCursorBufferPosition([21, 25]); 194 | parser.parse(editor); 195 | 196 | let structure = parser.getStructure(editor); 197 | 198 | expect(structure.context) 199 | .toEqual({ 200 | contract: 'Pyramid', 201 | level: 'modifier', 202 | name: 'onlyowner', 203 | prev: '{' 204 | }); 205 | }); 206 | 207 | it('understands struct context', () => { 208 | let editor = editors[2]; 209 | editor.setCursorBufferPosition([6, 18]); 210 | parser.parse(editor); 211 | 212 | let structure = parser.getStructure(editor); 213 | 214 | expect(structure.context) 215 | .toEqual({ 216 | contract: 'Pyramid', 217 | level: 'struct', 218 | name: 'Participant', 219 | prev: '' 220 | }); 221 | }); 222 | 223 | it('parses the fallback function too', () => { 224 | let editor = editors[2]; 225 | editor.setCursorBufferPosition([33, 8]); 226 | parser.parse(editor); 227 | 228 | let structure = parser.getStructure(editor); 229 | 230 | expect(structure.context) 231 | .toEqual({ 232 | contract: 'Pyramid', 233 | level: 'function', 234 | name: '', 235 | prev: '( ) {' 236 | }); 237 | }); 238 | 239 | it('handles brackets just fine', () => { 240 | let editor = editors[2]; 241 | editor.setCursorBufferPosition([63, 0]); 242 | parser.parse(editor); 243 | 244 | let structure = parser.getStructure(editor); 245 | 246 | expect(structure.context) 247 | .toEqual({ 248 | contract: 'Pyramid', 249 | level: 'function', 250 | name: 'enter', 251 | prev: ');' 252 | }); 253 | 254 | editor.setCursorBufferPosition([65, 40]); 255 | parser.parse(editor); 256 | 257 | structure = parser.getStructure(editor); 258 | 259 | expect(structure.context) 260 | .toEqual({ 261 | contract: 'Pyramid', 262 | level: 'function', 263 | name: 'enter', 264 | prev: '100' 265 | }); 266 | 267 | editor.setCursorBufferPosition([79, 66]); 268 | parser.parse(editor); 269 | 270 | structure = parser.getStructure(editor); 271 | 272 | expect(structure.context) 273 | .toEqual({ 274 | contract: 'Pyramid', 275 | level: 'function', 276 | name: 'enter', 277 | prev: '(amount' 278 | }); 279 | }); 280 | 281 | it('fails silently when extra closing brackets are present', () => { 282 | let editor = editors[0]; 283 | 284 | // close contract early 285 | editor.setCursorBufferPosition([2, 17]); 286 | editor.setTextInBufferRange([[2, 17], [2, 18]], '}'); 287 | expect(parser.parse.bind(parser, editor)).not.toThrow(); 288 | editor.setTextInBufferRange([[2, 17], [2, 18]], ''); 289 | 290 | // close struct early 291 | editor.setCursorBufferPosition([4, 18]); 292 | editor.setTextInBufferRange([[4, 18], [4, 19]], '}'); 293 | expect(parser.parse.bind(parser, editor)).not.toThrow(); 294 | editor.setTextInBufferRange([[4, 18], [4, 19]], ''); 295 | 296 | // close function early 297 | editor.setCursorBufferPosition([19, 42]); 298 | editor.setTextInBufferRange([[19, 42], [19, 43]], '}'); 299 | expect(parser.parse.bind(parser, editor)).not.toThrow(); 300 | editor.setTextInBufferRange([[19, 42], [19, 43]], ''); 301 | 302 | // close if early 303 | editor.setCursorBufferPosition([60, 67]); 304 | editor.setTextInBufferRange([[60, 67], [60, 68]], '}'); 305 | expect(parser.parse.bind(parser, editor)).not.toThrow(); 306 | editor.setTextInBufferRange([[60, 67], [60, 68]], ''); 307 | 308 | editor = editors[2]; 309 | 310 | // close modifier early 311 | editor.setCursorBufferPosition([21, 24]); 312 | editor.setTextInBufferRange([[21, 24], [21, 25]], '}'); 313 | expect(parser.parse.bind(parser, editor)).not.toThrow(); 314 | editor.setTextInBufferRange([[21, 24], [21, 25]], ''); 315 | 316 | editor = editors[3]; 317 | 318 | // close else early 319 | editor.setCursorBufferPosition([12, 14]); 320 | editor.setTextInBufferRange([[12, 14], [12, 15]], '}'); 321 | expect(parser.parse.bind(parser, editor)).not.toThrow(); 322 | editor.setTextInBufferRange([[12, 14], [12, 15]], ''); 323 | 324 | // close while early 325 | editor.setCursorBufferPosition([13, 27]); 326 | editor.setTextInBufferRange([[13, 27], [13, 28]], '}'); 327 | expect(parser.parse.bind(parser, editor)).not.toThrow(); 328 | editor.setTextInBufferRange([[13, 27], [13, 28]], ''); 329 | }); 330 | }); 331 | -------------------------------------------------------------------------------- /src/autocomplete-solidity.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | import parser from './util/parser'; 5 | 6 | import baseProvider from './providers/base-provider'; 7 | import contextProvider from './providers/context-provider'; 8 | 9 | let attachConfig = function(obj, prop) { 10 | let config = `autocomplete-solidity.${prop}`; 11 | 12 | obj[prop] = atom.config.get(config); 13 | atom.config.onDidChange(config, () => { 14 | obj[prop] = atom.config.get(config); 15 | }); 16 | }; 17 | 18 | let providers = []; 19 | 20 | export default { 21 | config: { 22 | shorthandOnly: { 23 | title: 'Shorthand Only', 24 | description: 'Only show shorthand types (uint)', 25 | type: 'boolean', 26 | default: true 27 | } 28 | }, 29 | 30 | activate() { 31 | atom.workspace.observeTextEditors((editor) => { 32 | let parse = () => { 33 | let grammar = editor.getGrammar(); 34 | 35 | if (grammar.name == 'Solidity') { 36 | parser.parse(editor); 37 | } 38 | }; 39 | 40 | parse(); 41 | 42 | let disposable = editor.onDidStopChanging(() => parse()); 43 | editor.onDidDestroy(() => disposable.dispose()); 44 | }); 45 | 46 | attachConfig(contextProvider, 'shorthandOnly'); 47 | }, 48 | 49 | provide() { 50 | return [baseProvider, contextProvider]; 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/providers/Provider.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | import parser from '../util/parser'; 5 | 6 | export default class Provider { 7 | constructor() { 8 | // Provider properties 9 | this.context = {}; 10 | this.minimumWordLength = atom.config.get('autocomplete-plus.minimumWordLength'); 11 | this.prefix = ''; 12 | this.structure = {}; 13 | 14 | // Autocomplete+ properties 15 | this.disableForSelector = '.source.solidity .comment'; 16 | this.excludeLowerPriority = true; 17 | this.inclusionPriority = 2; 18 | this.selector = '.source.solidity'; 19 | 20 | atom.config.onDidChange('autocomplete-plus.minimumWordLength', () => { 21 | this.minimumWordLength = atom.config.get('autocomplete-plus.minimumWordLength'); 22 | }); 23 | } 24 | 25 | getPrefix(editor) { 26 | let bufferPosition = editor.getLastCursor().getBufferPosition(); 27 | let line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]); 28 | 29 | let split = line.split(/\s+/g); 30 | this.prefix = split.pop(); 31 | 32 | if (this.prefix.lastIndexOf('(') > this.prefix.lastIndexOf(')')) { 33 | let split = this.prefix.split(/[\(\)]/); 34 | this.prefix = split[split.length - 1]; 35 | } 36 | 37 | if (this.prefix.lastIndexOf('[') > this.prefix.lastIndexOf(']')) { 38 | let split = this.prefix.split(/[\[\]]/); 39 | this.prefix = split[split.length - 1]; 40 | } 41 | } 42 | 43 | getStructure(editor) { 44 | this.structure = parser.getStructure(editor); 45 | this.context = this.structure.context; 46 | } 47 | 48 | getSuggestions(options) { 49 | this.getPrefix(options.editor); 50 | 51 | if (this.prefix.length < this.minimumWordLength) { 52 | return []; 53 | } 54 | 55 | this.getStructure(options.editor); 56 | 57 | // Providers need to define getDefinitions 58 | let definitions = []; 59 | let allDefinitions = this.getDefinitions(); 60 | for (let suggestion in allDefinitions) { 61 | if (suggestion.indexOf(this.prefix) === 0) { 62 | let definition = allDefinitions[suggestion]; 63 | 64 | definition.replacementPrefix = this.prefix; 65 | definition.rightLabel = definition.type; 66 | 67 | definitions.push(definition); 68 | } 69 | } 70 | 71 | return definitions; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/providers/base-provider.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | import definitions from '../../definitions/base.json'; 5 | 6 | import Provider from './Provider'; 7 | 8 | class BaseProvider extends Provider { 9 | getDefinitions() { 10 | let level = this.context.level; 11 | 12 | // modifiers have the same context level as functions 13 | if (level == 'modifier') { 14 | level = 'function'; 15 | } 16 | 17 | return definitions[level] ? definitions[level] : {}; 18 | } 19 | } 20 | 21 | export default new BaseProvider(); 22 | -------------------------------------------------------------------------------- /src/providers/context-provider.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | import descriptor_definitions from '../../definitions/descriptors.json'; 5 | import object_definitions from '../../definitions/objects.json'; 6 | import type_definitions from '../../definitions/types.json'; 7 | 8 | import contracts from '../util/contracts'; 9 | import Provider from './Provider'; 10 | 11 | class TypeProvider extends Provider { 12 | constructor() { 13 | super(); 14 | 15 | this.shorthandOnly = true; 16 | } 17 | 18 | getDefinitions() { 19 | let definitions = {}; 20 | 21 | if (this.context.level == 'global' || this.context.level == 'enum') { 22 | return definitions; 23 | } 24 | 25 | // return descriptors (if applicable) 26 | if (this.context.prev && Number.isInteger(+this.context.prev)) { 27 | return descriptor_definitions; 28 | } 29 | 30 | if (this.context.level == 'function' || this.context.level == 'modifier') { 31 | let period_index = this.prefix.lastIndexOf('.'); 32 | if (period_index !== -1) { 33 | if (period_index > 4 && this.prefix.indexOf('block.') == period_index - 5) { 34 | this.prefix = this.prefix.split('.').pop(); 35 | 36 | return object_definitions.block; 37 | } 38 | 39 | if (period_index > 2 && this.prefix.indexOf('msg.') == period_index - 3) { 40 | this.prefix = this.prefix.split('.').pop(); 41 | 42 | return object_definitions.msg; 43 | } 44 | 45 | if (period_index > 1 && this.prefix.indexOf('tx.') == period_index - 2) { 46 | this.prefix = this.prefix.split('.').pop(); 47 | 48 | return object_definitions.tx; 49 | } 50 | } 51 | } 52 | 53 | // add types (they can be in any context) 54 | let types = {}; 55 | Object.assign(types, type_definitions); 56 | 57 | if (!this.shorthandOnly) { 58 | for (let i = 1; i <= 32; i++) { 59 | types['bytes' + i] = { 60 | description: i + ' byte' + (i > 1 ? 's' : ''), 61 | leftLabel: 'bytes' + i, 62 | text: 'bytes' + i, 63 | type: 'type' 64 | }; 65 | } 66 | 67 | for (let i = 8; i <= 256; i += 8) { 68 | types['int' + i] = { 69 | description: `Integer (${i} bits)`, 70 | leftLabel: 'int' + i, 71 | text: 'int' + i, 72 | type: 'type' 73 | }; 74 | 75 | types['uint' + i] = { 76 | description: `Unsigned integer (${i} bits)`, 77 | leftLabel: 'uint' + i, 78 | text: 'uint' + i, 79 | type: 'type' 80 | }; 81 | } 82 | } 83 | 84 | if (this.context.level == 'function' || this.context.level == 'modifier') { 85 | types.var = { 86 | description: 'Type determined at run-time', 87 | leftLabel: 'var', 88 | text: 'var', 89 | type: 'type' 90 | }; 91 | } 92 | 93 | if (this.context.level != 'contract') { 94 | delete types.enum; 95 | delete types.event; 96 | delete types.modifier; 97 | delete types.struct; 98 | } 99 | 100 | // add user-defined definitions 101 | let contract = this.loadContract(); 102 | Object.assign(types, contract.getTypeDefinitions()); 103 | 104 | // add types to definitions 105 | Object.assign(definitions, types); 106 | 107 | if (this.context.level == 'function' || this.context.level == 'modifier') { 108 | let variables = contract.getVariableDefinitions(); 109 | 110 | // add variables to definitions 111 | Object.assign(definitions, variables); 112 | 113 | // possible short-circuit with 'new' 114 | if (this.context.prev == 'new') { 115 | delete types.var; 116 | return types; 117 | } 118 | 119 | let period_index = this.prefix.lastIndexOf('.'); 120 | if (period_index !== -1) { 121 | let split = this.prefix.split('.'); 122 | 123 | if (period_index > 3 && this.prefix.lastIndexOf('this.') == period_index - 4) { 124 | this.prefix = split.pop(); 125 | 126 | return contract.getFunctionDefinitions(true); 127 | } 128 | 129 | if (period_index > 0 && this.prefix.lastIndexOf(').') == period_index - 1) { 130 | this.prefix = split.pop(); 131 | 132 | let contract_name = split.pop().split('(')[0]; 133 | let contract = contracts.load(this.structure, contract_name); 134 | 135 | return Object.assign({}, contract.getFunctionDefinitions(true), contract.getVariableDefinitions(false)); 136 | } 137 | 138 | let name = split.splice(-2, 1)[0]; 139 | 140 | let type = types[name]; 141 | if (type) { 142 | this.prefix = split.pop(); 143 | 144 | if (type.properties && Array.isArray(type.properties)) { 145 | let property_definitions = {}; 146 | 147 | for (let property of type.properties) { 148 | property_definitions[property] = { 149 | text: property, 150 | type: 'enum' 151 | }; 152 | } 153 | 154 | return property_definitions; 155 | } else if (type.functions) { 156 | return type.functions; 157 | } else { 158 | return {}; 159 | } 160 | } 161 | 162 | if (name == 'coinbase' && split.splice(-2, 1)[0] == 'block') { 163 | this.prefix = split.pop(); 164 | return object_definitions.address; 165 | } 166 | 167 | if (name == 'sender' && split.splice(-2, 1)[0] == 'msg') { 168 | this.prefix = split.pop(); 169 | return object_definitions.address; 170 | } 171 | 172 | if (name == 'origin' && split.splice(-2, 1)[0] == 'tx') { 173 | this.prefix = split.pop(); 174 | return object_definitions.address; 175 | } 176 | 177 | let variable = variables[name]; 178 | if (variable) { 179 | if (variable.leftLabel == 'address') { 180 | this.prefix = split.pop(); 181 | return object_definitions.address; 182 | } 183 | 184 | if (variable.leftLabel.substr(-2) == '[]' || variable.leftLabel == 'bytes') { 185 | this.prefix = split.pop(); 186 | 187 | let definitions = Object.assign({}, object_definitions.array); 188 | 189 | if (variable.leftLabel.indexOf('=>') !== -1) { 190 | delete definitions.push; 191 | } 192 | 193 | return definitions; 194 | } 195 | 196 | if (types[variable.leftLabel]) { 197 | this.prefix = split.pop(); 198 | 199 | let type = types[variable.leftLabel]; 200 | 201 | let property_definitions = {}; 202 | for (let property in type.properties) { 203 | property_definitions[property] = { 204 | leftLabel: type.properties[property].type, 205 | text: property, 206 | type: type.leftLabel 207 | }; 208 | } 209 | 210 | return property_definitions; 211 | } 212 | } else if (name.indexOf('[')) { 213 | variable = variables[name.split('[')[0]]; 214 | if (variable) { 215 | let leftLabel = variable.leftLabel.replace('[]', ''); 216 | let type = types[leftLabel]; 217 | 218 | if (!type && leftLabel.indexOf('=>') !== -1) { 219 | type = types[leftLabel.split('=>')[1].trim()]; 220 | } 221 | 222 | if (type && type.leftLabel != 'enum') { 223 | this.prefix = split.pop(); 224 | 225 | let property_definitions = {}; 226 | for (let property in type.properties) { 227 | property_definitions[property] = { 228 | leftLabel: type.properties[property].type, 229 | text: property, 230 | type: type.leftLabel 231 | }; 232 | } 233 | 234 | return property_definitions; 235 | } 236 | } 237 | } 238 | } 239 | } 240 | 241 | // add functions / events to definitions 242 | Object.assign(definitions, contract.getFunctionDefinitions()); 243 | 244 | // add modifiers to definitions 245 | Object.assign(definitions, contract.getModifierDefinitions()); 246 | 247 | return definitions; 248 | } 249 | 250 | loadContract() { 251 | return contracts.load(this.structure, this.context.contract); 252 | } 253 | } 254 | 255 | export default new TypeProvider(); 256 | -------------------------------------------------------------------------------- /src/util/contracts.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | // we can't cache the contracts because their structure can change at any time 5 | let load = (structure, contract_name) => { 6 | return new Contract(structure, contract_name); 7 | }; 8 | 9 | class Contract { 10 | constructor(structure, contract_name) { 11 | this.context = structure.context; 12 | this.contracts = structure.contracts; 13 | this.functions = {}; 14 | this.modifiers = {}; 15 | this.name = contract_name; 16 | this.variables = {}; 17 | 18 | for (let name in this.contracts[this.name]) { 19 | this[name] = this.contracts[this.name][name]; 20 | } 21 | 22 | if (this.extends && this.extends.length) { 23 | for (let name of this.extends.reverse()) { 24 | let contract = load(structure, name); 25 | 26 | for (let property in contract) { 27 | if (['context', 'contracts', 'extends', 'name'].indexOf(property) !== -1) { 28 | continue; 29 | } 30 | 31 | this[property] = Object.assign({}, contract[property], this[property]); 32 | } 33 | } 34 | } 35 | } 36 | 37 | createFunctionSnippet(name, params, inline) { 38 | let snippet = name; 39 | 40 | let count = 1; 41 | if (inline) { 42 | count ++; 43 | snippet += '${1}'; 44 | } 45 | 46 | snippet += '('; 47 | 48 | if (params.length) { 49 | for (let param of params) { 50 | if (count > (1 + (inline ? 1 : 0))) { 51 | snippet += ', '; 52 | } 53 | 54 | snippet += `\${${count}:${param.name}}`; 55 | 56 | count++; 57 | } 58 | } 59 | 60 | snippet += `)\${${count}}`; 61 | 62 | return snippet; 63 | } 64 | 65 | getFunctionDefinitions(inline = false) { 66 | let definitions = {}; 67 | 68 | if (this.context.level == 'function' || this.context.level == 'modifier') { 69 | if (!inline) { 70 | for (let name in this.events) { 71 | definitions[name] = { 72 | description: `Emits the ${name} event`, 73 | leftLabel: 'void', 74 | snippet: this.createFunctionSnippet(name, this.events[name].params), 75 | type: 'event' 76 | }; 77 | } 78 | } 79 | 80 | for (let name in this.functions) { 81 | if (name === '' || (this.context.level == 'function' && name == this.context.name && this.context.contract == this.name)) { 82 | continue; 83 | } 84 | 85 | definitions[name] = { 86 | description: `Calls the ${name} function`, 87 | leftLabel: this.functions[name].returns, 88 | snippet: this.createFunctionSnippet(name, this.functions[name].params, inline), 89 | type: 'function' 90 | }; 91 | } 92 | } 93 | 94 | return definitions; 95 | } 96 | 97 | getModifierDefinitions() { 98 | let definitions = {}; 99 | 100 | if (this.context.level == 'contract') { 101 | for (let name in this.modifiers) { 102 | definitions[name] = { 103 | description: `Applies the ${name} modifier`, 104 | snippet: this.createFunctionSnippet(name, this.modifiers[name].params), 105 | type: 'modifier' 106 | }; 107 | } 108 | } 109 | 110 | return definitions; 111 | } 112 | 113 | getTypeDefinitions() { 114 | let definitions = {}; 115 | 116 | for (let name in this.enums) { 117 | definitions[name] = { 118 | description: this.enums[name].properties.join(', '), 119 | leftLabel: 'enum', 120 | properties: this.enums[name].properties, // for us to use 121 | text: name, 122 | type: 'type' 123 | }; 124 | } 125 | 126 | for (let name in this.structs) { 127 | definitions[name] = { 128 | description: Object.keys(this.structs[name].properties).join(', '), 129 | leftLabel: 'struct', 130 | properties: this.structs[name].properties, // for us to use 131 | text: name, 132 | type: 'type' 133 | }; 134 | } 135 | 136 | for (let contract in this.contracts) { 137 | if (contract == this.name) { 138 | continue; 139 | } 140 | 141 | let info = this.contracts[contract]; 142 | if (info.library) { 143 | let functions = {}; 144 | for (let name in info.functions) { 145 | functions[name] = { 146 | description: `Calls the ${name} function`, 147 | leftLabel: info.functions[name].returns, 148 | snippet: this.createFunctionSnippet(name, info.functions[name].params), 149 | type: 'function' 150 | }; 151 | } 152 | 153 | definitions[contract] = { 154 | leftLabel: 'library', 155 | functions: functions, 156 | text: contract, 157 | type: 'type' 158 | }; 159 | } else { 160 | definitions[contract] = { 161 | leftLabel: 'contract', 162 | text: contract, 163 | type: 'type' 164 | }; 165 | } 166 | } 167 | 168 | return definitions; 169 | } 170 | 171 | getVariableDefinitions(include_params = true) { 172 | let definitions = {}; 173 | 174 | for (let name in this.variables) { 175 | definitions[name] = { 176 | leftLabel: this.variables[name].type, 177 | text: name, 178 | type: 'variable' 179 | }; 180 | } 181 | 182 | if (this.context.level == 'function' || this.context.level == 'modifier') { 183 | let type = this.context.level == 'function' ? 'functions' : 'modifiers'; 184 | let obj = this[type][this.context.name]; 185 | 186 | if (obj) { 187 | if (include_params) { 188 | for (let param of obj.params) { 189 | definitions[param.name] = { 190 | leftLabel: param.type, 191 | text: param.name, 192 | type: 'parameter' 193 | }; 194 | } 195 | } 196 | 197 | for (let name in obj.variables) { 198 | definitions[name] = { 199 | leftLabel: obj.variables[name].type, 200 | text: name, 201 | type: 'variable' 202 | }; 203 | } 204 | } 205 | } 206 | 207 | return definitions; 208 | } 209 | } 210 | 211 | export default {load}; 212 | -------------------------------------------------------------------------------- /src/util/parser.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | /* jshint esversion: 6 */ 3 | 4 | let structures = {}; 5 | 6 | let parse = (editor) => { 7 | let cursor = editor.getLastCursor(); 8 | let bufferPosition = cursor.getBufferPosition(); 9 | 10 | let text = editor.getTextInRange([[0, 0], bufferPosition]); 11 | text += '__as-cursor__'; 12 | text += editor.getTextInRange([bufferPosition, [Infinity, Infinity]]); 13 | 14 | text = text.replace(/([\(\)\{\}])/g, ' $1'); 15 | 16 | let characters = text.split(''); 17 | 18 | let buffer = ''; 19 | let char = null; 20 | let context = 'global'; 21 | let context_name = ''; 22 | let contract = ''; 23 | let contracts = []; 24 | let contract_types = {}; 25 | let level = 0; 26 | let modifiers = { 27 | comment: false, 28 | contract: false, 29 | contract_extends: false, 30 | enum: false, 31 | enum_properties: false, 32 | event: false, 33 | event_params: false, 34 | function: false, 35 | function_params: false, 36 | library: false, 37 | mapping: false, 38 | modifier: false, 39 | modifier_params: false, 40 | string: false, 41 | struct: false, 42 | struct_properties: false, 43 | type: false 44 | }; 45 | let next_context = ''; 46 | let prev = ''; 47 | let structure = { 48 | context: {}, 49 | contracts: {} 50 | }; 51 | let types = [ 52 | 'address', 'bool', 'byte', 'bytes', 'int', 'string', 'uint' 53 | ]; 54 | 55 | while ((char = characters.shift())) { 56 | /* 57 | * whitespace 58 | */ 59 | if (!modifiers.contract_extends && !modifiers.event_params && 60 | !modifiers.function_params && !modifiers.modifier_params && 61 | !modifiers.struct_properties && !modifiers.mapping) { 62 | if (char == ' ' || char == '\t') { 63 | prev = buffer ? buffer : prev; 64 | buffer = ''; 65 | continue; 66 | } 67 | 68 | if (char == '\n' || char == '\r') { 69 | if (modifiers.comment == 'line') { 70 | modifiers.comment = false; 71 | } 72 | 73 | prev = buffer ? buffer : prev; 74 | buffer = ''; 75 | continue; 76 | } 77 | } 78 | 79 | // we don't want comments / strings to add to the buffer 80 | if (!modifiers.comment && !modifiers.string) { 81 | buffer += char; 82 | } 83 | 84 | // treat periods specially 85 | if (char == '.' && !modifiers.comment && !modifiers.string) { 86 | prev = buffer; 87 | buffer = ''; 88 | continue; 89 | } 90 | 91 | /* 92 | * comments 93 | */ 94 | if (char == '/' && !modifiers.string && !modifiers.comment) { 95 | char = characters.shift(); 96 | if (char == '/') { 97 | modifiers.comment = 'line'; 98 | buffer = buffer.substr(0, buffer.length - 1); 99 | } else if (char == '*') { 100 | modifiers.comment = 'block'; 101 | buffer = buffer.substr(0, buffer.length - 1); 102 | } 103 | 104 | characters.unshift(char); 105 | } 106 | 107 | if (char == '*' && modifiers.comment == 'block') { 108 | char = characters.shift(); 109 | if (char == '/') { 110 | modifiers.comment = false; 111 | continue; 112 | } 113 | } 114 | 115 | /* 116 | * strings 117 | */ 118 | if (char == '\\' && modifiers.string) { 119 | // \ means escape the next character, so just skip past it 120 | characters.shift(); 121 | continue; 122 | } 123 | 124 | if ((char == '"' || char == '"') && !modifiers.comment) { 125 | modifiers.string = modifiers.string == char ? false : char; 126 | if (!modifiers.string) { 127 | prev = buffer = ''; 128 | } 129 | } 130 | 131 | /* 132 | * cursor 133 | */ 134 | if (char == '_' && buffer.substr(-13) == '__as-cursor__') { 135 | // skip over the cursor 136 | buffer = buffer.substr(0, buffer.length - 13); 137 | 138 | structure.context = { 139 | contract: contract, 140 | level: context, 141 | name: context_name, 142 | prev: prev 143 | }; 144 | continue; 145 | } 146 | 147 | /* 148 | * context / structure 149 | */ 150 | if (!modifiers.comment && !modifiers.string) { 151 | if (prev == 'contract') { 152 | next_context = 'contract'; 153 | modifiers.contract = true; 154 | } else if (prev == 'library') { 155 | next_context = 'contract'; 156 | modifiers.contract = true; 157 | modifiers.library = true; 158 | } else if (modifiers.contract) { 159 | modifiers.contract = false; 160 | 161 | contract = prev; 162 | prev = ''; 163 | structure.contracts[contract] = { 164 | enums: {}, 165 | events: {}, 166 | extends: [], 167 | functions: {}, 168 | library: modifiers.library, 169 | modifiers: {}, 170 | structs: {}, 171 | variables: {} 172 | }; 173 | 174 | modifiers.library = false; 175 | 176 | contracts.push(contract); 177 | contract_types[contract] = []; 178 | 179 | modifiers.contract_extends = char; 180 | } else if (modifiers.contract_extends) { 181 | modifiers.contract_extends += char; 182 | } 183 | 184 | if (prev == 'enum') { 185 | next_context = 'enum'; 186 | modifiers.enum = true; 187 | } else if (modifiers.enum) { 188 | if (structure.contracts[contract]) { 189 | modifiers.enum = false; 190 | 191 | context_name = prev; 192 | prev = ''; 193 | structure.contracts[contract].enums[context_name] = { 194 | properties: [] 195 | }; 196 | 197 | contract_types[contract].push(context_name); 198 | 199 | modifiers.enum_properties = char; 200 | } 201 | } else if (modifiers.enum_properties) { 202 | modifiers.enum_properties += char; 203 | } 204 | 205 | if (prev == 'event') { 206 | modifiers.event = true; 207 | } else if (modifiers.event) { 208 | if (structure.contracts[contract]) { 209 | modifiers.event = false; 210 | 211 | context_name = prev; 212 | prev = ''; 213 | structure.contracts[contract].events[context_name] = { 214 | params: [] 215 | }; 216 | 217 | modifiers.event_params = char; 218 | } 219 | } else if (modifiers.event_params) { 220 | modifiers.event_params += char; 221 | } 222 | 223 | if (prev == 'function' && char != '(') { 224 | next_context = 'function'; 225 | modifiers.function = true; 226 | } else if (modifiers.function || prev == 'function') { 227 | if (structure.contracts[contract]) { 228 | modifiers.function = false; 229 | 230 | // fallback function 231 | if (prev == 'function' && char == '(') { 232 | next_context = 'function'; 233 | prev = ''; 234 | } 235 | 236 | context_name = prev; 237 | prev = ''; 238 | structure.contracts[contract].functions[context_name] = { 239 | returns: 'void', 240 | params: [], 241 | variables: {} 242 | }; 243 | 244 | modifiers.function_params = char; 245 | } 246 | } else if (modifiers.function_params) { 247 | modifiers.function_params += char; 248 | } 249 | 250 | if (prev == 'modifier') { 251 | next_context = 'modifier'; 252 | modifiers.modifier = true; 253 | } else if (modifiers.modifier) { 254 | if (structure.contracts[contract]) { 255 | modifiers.modifier = false; 256 | 257 | context_name = prev; 258 | prev = ''; 259 | structure.contracts[contract].modifiers[context_name] = { 260 | params: [], 261 | variables: {} 262 | }; 263 | 264 | modifiers.modifier_params = char; 265 | } 266 | } else if (modifiers.modifier_params) { 267 | modifiers.modifier_params += char; 268 | } 269 | 270 | if (prev == 'struct') { 271 | next_context = 'struct'; 272 | modifiers.struct = true; 273 | } else if (modifiers.struct) { 274 | if (structure.contracts[contract]) { 275 | modifiers.struct = false; 276 | 277 | context_name = prev; 278 | prev = ''; 279 | structure.contracts[contract].structs[context_name] = { 280 | properties: {} 281 | }; 282 | 283 | contract_types[contract].push(context_name); 284 | 285 | modifiers.struct_properties = char; 286 | } 287 | } else if (modifiers.struct_properties) { 288 | modifiers.struct_properties += char; 289 | } 290 | 291 | if (char == ')' && modifiers.event_params) { 292 | if (structure.contracts[contract]) { 293 | let event_params = modifiers.event_params.replace(/[\(\)\s]+/g, ' ').trim().split(','); 294 | for (let definition of event_params) { 295 | let info = definition.trim().split(' '); 296 | let type = info[0]; 297 | let name = info.length == 3 ? info[2] : info[1]; 298 | 299 | if (name && context_name && context == 'contract') { 300 | structure.contracts[contract].events[context_name].params.push({ 301 | name: name, 302 | type: type 303 | }); 304 | } 305 | } 306 | } 307 | 308 | // we can't actually go inside an event, so clear out the context_name 309 | context_name = ''; 310 | modifiers.event_params = false; 311 | } 312 | 313 | // handle abstract functions as well 314 | if (char == '{' || (char == ';' && modifiers.function_params)) { 315 | if (char == '{') { 316 | level++; 317 | context = next_context ? next_context : context; 318 | next_context = ''; 319 | } 320 | 321 | if (modifiers.contract_extends) { 322 | if (structure.contracts[contract]) { 323 | let contract_extends = modifiers.contract_extends.replace('{', '').trim().split(/\s+/); 324 | if (contract_extends[0] == 'is') { 325 | contract_extends.shift(); 326 | contract_extends = contract_extends.join(' ').split(','); 327 | 328 | structure.contracts[contract].extends = contract_extends; 329 | } 330 | } 331 | 332 | modifiers.contract_extends = false; 333 | } 334 | 335 | if (modifiers.function_params) { 336 | let function_params = modifiers.function_params.replace(/\s+/g, ' ').split(/[\(\)]/); 337 | function_params.shift(); 338 | 339 | if (function_params.length) { 340 | if (structure.contracts[contract]) { 341 | let params = function_params.shift().split(','); 342 | for (let param of params) { 343 | let info = param.trim().split(' '); 344 | let type = info[0]; 345 | let name = info[1]; 346 | 347 | if (name && context_name && context == 'function') { 348 | structure.contracts[contract].functions[context_name].params.push({ 349 | name: name, 350 | type: type 351 | }); 352 | } 353 | } 354 | 355 | if (function_params.length == 3) { 356 | function_params.shift(); 357 | 358 | if (context_name && context == 'function') { 359 | structure.contracts[contract].functions[context_name].returns = function_params.shift().trim(); 360 | } 361 | } 362 | } 363 | } 364 | 365 | modifiers.function_params = false; 366 | } 367 | 368 | if (modifiers.modifier_params) { 369 | let modifier_params = modifiers.modifier_params.replace(/\s+/g, ' ').split(/[\(\)]/); 370 | modifier_params.shift(); 371 | 372 | if (modifier_params.length) { 373 | if (structure.contracts[contract]) { 374 | let params = modifier_params.shift().split(','); 375 | for (let param of params) { 376 | let info = param.trim().split(' '); 377 | let type = info[0]; 378 | let name = info[1]; 379 | 380 | if (name && context_name && context == 'modifier') { 381 | structure.contracts[contract].modifiers[context_name].params.push({ 382 | name: name, 383 | type: type 384 | }); 385 | } 386 | } 387 | } 388 | } 389 | 390 | modifiers.modifier_params = false; 391 | } 392 | } 393 | 394 | if (char == '}') { 395 | level--; 396 | 397 | if (modifiers.enum_properties) { 398 | if (structure.contracts[contract]) { 399 | let enum_properties = modifiers.enum_properties.replace(/[\{\}\s]/g, '').split(','); 400 | for (let property of enum_properties) { 401 | if (property && context_name && context == 'enum') { 402 | structure.contracts[contract].enums[context_name].properties.push(property); 403 | } 404 | } 405 | } 406 | 407 | modifiers.enum_properties = false; 408 | } 409 | 410 | if (modifiers.struct_properties) { 411 | if (structure.contracts[contract]) { 412 | let struct_properties = modifiers.struct_properties.replace(/[\{\}\s]+/g, ' ').split(';'); 413 | for (let property of struct_properties) { 414 | let info = property.trim().split(' '); 415 | let name = info.pop(); 416 | let type = info.join(' '); 417 | 418 | if (name && context_name && context == 'struct') { 419 | structure.contracts[contract].structs[context_name].properties[name] = { 420 | type: type 421 | }; 422 | } 423 | } 424 | } 425 | 426 | modifiers.struct_properties = false; 427 | } 428 | 429 | if (level == 1) { 430 | context = 'contract'; 431 | context_name = ''; 432 | } else if (level === 0) { 433 | context = 'global'; 434 | context_name = ''; 435 | contract = ''; 436 | 437 | modifiers.contract = false; 438 | modifiers.enum = false; 439 | modifiers.event = false; 440 | modifiers.function = false; 441 | modifiers.modifier = false; 442 | modifiers.struct = false; 443 | 444 | modifiers.contract_extends = false; 445 | modifiers.event_params = false; 446 | modifiers.function_params = false; 447 | modifiers.modifier_params = false; 448 | modifiers.struct_properties = false; 449 | modifiers.mapping = false; 450 | } 451 | } 452 | 453 | /* 454 | * variables 455 | */ 456 | let is_type = false; 457 | for (let type of types) { 458 | if (prev.indexOf(type) === 0) { 459 | let check = prev.replace('[]', ''); 460 | 461 | if (check == type) { 462 | is_type = true; 463 | break; 464 | } 465 | 466 | if (type == 'bytes') { 467 | for (let i = 1; i <= 32; i++) { 468 | if (check == type + i) { 469 | is_type = true; 470 | break; 471 | } 472 | } 473 | } 474 | 475 | if (type == 'int' || type == 'uint') { 476 | for (let i = 8; i <= 256; i += 8) { 477 | if (check == type + i) { 478 | is_type = true; 479 | break; 480 | } 481 | } 482 | } 483 | } 484 | } 485 | 486 | if (!is_type && contract) { 487 | for (let type of contract_types[contract]) { 488 | let check = prev.replace('[]', ''); 489 | 490 | if (check == type) { 491 | is_type = true; 492 | break; 493 | } 494 | } 495 | 496 | for (let contract of contracts) { 497 | let check = prev.replace('[]', ''); 498 | 499 | if (check == contract && buffer[0] != '(') { 500 | is_type = true; 501 | break; 502 | } 503 | } 504 | } 505 | 506 | if (is_type) { 507 | modifiers.type = prev; 508 | } else if (modifiers.type) { 509 | let type = modifiers.type; 510 | let name = prev.replace(';', ''); 511 | 512 | if (prev == 'public' || prev == 'private' || prev == 'internal') { 513 | // skip this word 514 | prev = modifiers.type; 515 | continue; 516 | } 517 | 518 | if (context == 'contract') { 519 | if (structure.contracts[contract]) { 520 | structure.contracts[contract].variables[name] = { 521 | type: type 522 | }; 523 | } 524 | } else if (context == 'function') { 525 | if (structure.contracts[contract]) { 526 | structure.contracts[contract].functions[context_name].variables[name] = { 527 | type: type 528 | }; 529 | } 530 | } else if (context == 'modifier') { 531 | if (structure.contracts[contract]) { 532 | structure.contracts[contract].modifiers[context_name].variables[name] = { 533 | type: type 534 | }; 535 | } 536 | } 537 | 538 | modifiers.type = false; 539 | } 540 | 541 | // mapping is too complicated to be with the other types 542 | if (prev == 'mapping') { 543 | modifiers.mapping = true; 544 | if (char == ';') { 545 | modifiers.mapping = false; 546 | 547 | buffer = buffer.replace(/\b(public|private|internal)\b/g, ''); 548 | buffer = buffer.replace(/[\(\);]/g, ''); 549 | buffer = buffer.replace(/\s+/g, ' '); 550 | 551 | buffer = buffer.split(' '); 552 | 553 | let name = buffer.pop(); 554 | let type = buffer.join(''); 555 | 556 | if (context == 'contract') { 557 | if (structure.contracts[contract]) { 558 | structure.contracts[contract].variables[name] = { 559 | type: type 560 | }; 561 | } 562 | } else if (context == 'function') { 563 | if (structure.contracts[contract]) { 564 | structure.contracts[contract].functions[context_name].variables[name] = { 565 | type: type 566 | }; 567 | } 568 | } else if (context == 'modifier') { 569 | if (structure.contracts[contract]) { 570 | structure.contracts[contract].modifiers[context_name].variables[name] = { 571 | type: type 572 | }; 573 | } 574 | } 575 | 576 | prev = buffer = ''; 577 | continue; 578 | } 579 | } 580 | } 581 | } 582 | 583 | return structure; 584 | }; 585 | 586 | export default { 587 | getStructure(editor) { 588 | return structures[editor.id]; 589 | }, 590 | 591 | parse(editor) { 592 | structures[editor.id] = parse(editor); 593 | } 594 | }; 595 | --------------------------------------------------------------------------------