├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── lint-rule.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── art ├── Solium.png ├── augur.png ├── ethereum-logo.png ├── gitcoin.png └── solium-badge.svg ├── bin └── solium.js ├── config ├── rulesets │ ├── solium-all.js │ └── solium-recommended.js ├── schemas │ ├── ast-node.js │ ├── config.js │ ├── core-rule-response.js │ ├── core-rule.js │ ├── deprecated │ │ └── config-v0.5.5.js │ ├── error-reported-by-rule.js │ ├── error-supplied-to-solium.js │ ├── fixer-packet.js │ ├── plugin.js │ └── sharable-config.js └── solium.json ├── docs ├── Makefile ├── about.rst ├── conf.py ├── contributing.rst ├── developer-guide.rst ├── experimental-features.rst ├── index.rst ├── known-issues.rst ├── make.bat ├── solium_rules.tgn └── user-guide.rst ├── lib ├── autofix │ ├── merge-fixer-packets.js │ ├── rule-fixer.js │ └── source-code-fixer.js ├── cli-utils │ ├── .default-solium-ignore │ └── .default-soliumrc.json ├── cli.js ├── comment-directive-parser.js ├── reporters │ ├── gcc.js │ └── pretty.js ├── rule-context.js ├── rules.js ├── rules │ ├── arg-overflow.js │ ├── array-declarations.js │ ├── blank-lines.js │ ├── camelcase.js │ ├── comma-whitespace.js │ ├── conditionals-whitespace.js │ ├── constructor.js │ ├── deprecated-suicide.js │ ├── double-quotes.js │ ├── emit.js │ ├── error-reason.js │ ├── function-order.js │ ├── function-whitespace.js │ ├── imports-on-top.js │ ├── indentation.js │ ├── lbrace.js │ ├── linebreak-style.js │ ├── max-len.js │ ├── mixedcase.js │ ├── no-constant.js │ ├── no-empty-blocks.js │ ├── no-experimental.js │ ├── no-trailing-whitespace.js │ ├── no-unused-vars.js │ ├── no-with.js │ ├── operator-whitespace.js │ ├── pragma-on-top.js │ ├── quotes.js │ ├── semicolon-whitespace.js │ ├── uppercase.js │ ├── value-in-payable.js │ ├── variable-declarations.js │ ├── visibility-first.js │ └── whitespace.js ├── solium.js └── utils │ ├── ast-utils.js │ ├── config-inspector.js │ ├── fs-utils.js │ ├── js-utils.js │ ├── node-event-generator.js │ ├── rule-inspector.js │ ├── rule-loader.js │ └── source-code-utils.js ├── package-lock.json ├── package.json ├── snap └── snapcraft.yaml └── test ├── config ├── rulesets │ ├── solium-all.js │ └── solium-recommended.js └── schemas │ └── plugin.js ├── extras └── custom-rules-file.js ├── lib ├── autofix │ ├── merge-fixer-packets.js │ ├── rule-fixer.js │ └── source-code-fixer.js ├── comment-directive-parser.js ├── rule-context.js ├── rules.js ├── rules │ ├── arg-overflow │ │ └── arg-overflow.js │ ├── array-declarations │ │ ├── array-declarations.js │ │ ├── fixed │ │ │ └── ws-btw-op-clos.sol │ │ └── unfixed │ │ │ ├── mixed.sol │ │ │ ├── ws-btw-lit-op.sol │ │ │ └── ws-btw-op-clos.sol │ ├── blank-lines │ │ ├── accept │ │ │ ├── contract-single.sol │ │ │ ├── contract.sol │ │ │ ├── function.sol │ │ │ ├── library-single.sol │ │ │ └── library.sol │ │ ├── blank-lines.js │ │ └── reject │ │ │ ├── contract.sol │ │ │ ├── function.sol │ │ │ └── library.sol │ ├── camelcase │ │ └── camelcase.js │ ├── constructor │ ├── deprecated-suicide │ │ └── deprecated-suicide.js │ ├── double-quotes │ │ ├── accept │ │ │ └── double-quoted.sol │ │ ├── double-quotes.js │ │ └── reject │ │ │ └── single-quoted.sol │ ├── emit │ │ └── emit.js │ ├── error-reason │ │ └── error-reason.js │ ├── function-order │ │ └── function-order.js │ ├── imports-on-top │ │ ├── accept │ │ │ └── on-top.sol │ │ ├── fixes │ │ │ ├── before-pragma.sol │ │ │ └── only-one-error.sol │ │ ├── imports-on-top.js │ │ └── reject │ │ │ └── intermingled.sol │ ├── indentation │ │ ├── accept │ │ │ ├── config-default.sol │ │ │ ├── config-tabs.sol │ │ │ ├── config-two-spaces.sol │ │ │ ├── multiline-array.sol │ │ │ ├── multiline-call-declaration.sol │ │ │ ├── multiline-call-expression.sol │ │ │ ├── one-line-array.sol │ │ │ ├── one-line-function-call.sol │ │ │ ├── one-line-struct.sol │ │ │ └── struct.sol │ │ ├── indentation.js │ │ └── reject │ │ │ ├── chars-before-top-level.sol │ │ │ ├── config-default.sol │ │ │ ├── config-tabs.sol │ │ │ ├── config-two-spaces.sol │ │ │ ├── indented-top-level-closing-brace.sol │ │ │ ├── mixed-tabs-spaces.sol │ │ │ ├── multiline-array.sol │ │ │ ├── multiline-call-declaration.sol │ │ │ ├── multiline-call-expression.sol │ │ │ ├── struct.sol │ │ │ └── top-level-indent.sol │ ├── lbrace │ │ └── lbrace.js │ ├── linebreak-style │ │ ├── linebreak-style.js │ │ ├── unix-endings │ │ └── windows-endings │ ├── max-len │ │ └── max-len.js │ ├── mixedcase │ │ └── mixedcase.js │ ├── no-constant │ │ └── no-constant.js │ ├── no-empty-blocks │ │ └── no-empty-blocks.js │ ├── no-experimental │ │ └── no-experimental.js │ ├── no-trailing-whitespace │ │ └── no-trailing-whitespace.js │ ├── no-unused-vars │ │ └── no-unused-vars.js │ ├── no-with │ │ └── no-with.js │ ├── operator-whitespace │ │ └── operator-whitespace.js │ ├── pragma-on-top │ │ └── pragma-on-top.js │ ├── quotes │ │ ├── double-full.sol │ │ ├── double-quoted.sol │ │ ├── quotes.js │ │ ├── single-full.sol │ │ └── single-quoted.sol │ ├── uppercase │ │ └── uppercase.js │ ├── value-in-payable │ │ └── value-in-payable.js │ ├── variable-declarations │ │ └── variable-declarations.js │ ├── visibility-first │ │ └── visibility-first.js │ └── whitespace │ │ └── whitespace.js ├── solium.js └── utils │ ├── ast-utils.js │ ├── config-inspector.js │ ├── js-utils.js │ ├── node-event-generator.js │ ├── rule-inspector.js │ ├── rule-loader.js │ ├── source-code-utils.js │ └── wrappers.js ├── packagejson.js └── utils └── wrappers.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "rules": { 9 | "indent": [ 10 | "error", 11 | 4 12 | ], 13 | "linebreak-style": [ 14 | "error", 15 | "unix" 16 | ], 17 | "quotes": [ 18 | "error", 19 | "double" 20 | ], 21 | "semi": [ 22 | "error", 23 | "always" 24 | ], 25 | "space-before-function-paren": [ 26 | "error", 27 | "never" 28 | ], 29 | "func-call-spacing": [ 30 | "error", 31 | "never" 32 | ], 33 | "object-curly-spacing": [ 34 | "error", 35 | "always" 36 | ], 37 | "comma-dangle": "error", 38 | "comma-spacing": "error", 39 | "strict": "error", 40 | "no-var": "error", 41 | "no-caller": "error", 42 | "no-console": "off", 43 | "no-undef": "off", 44 | "no-useless-escape": "off", 45 | "no-sparse-arrays": "off", // we give undefined as test cases using pattern [, a, b] 46 | "no-whitespace-before-property": "off" // TODO: apply this rule on array bracket properties only 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | test/lib/rules/linebreak-style/windows-endings eol=crlf -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [duaraghav8] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in the linter 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps to reproduce** 14 | This section may contain all or a combination of the following: 15 | 16 | - The command you used to lint 17 | - The solidity code over which you ran the linter 18 | - Contents of your `.soliumrc.json` file 19 | - Contents of your `.soliumignore` 20 | 21 | In addition, you may add anything you feel is relevant. 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Operating System** 27 | OS you ran the linter on (Windows / MacOS / Linux / Any other) 28 | 29 | **Linter version** 30 | The output of `solium --version` 31 | 32 | **Comments** 33 | If you'd like to share any additional information that doesn't fit in above sections or suggest a solution to the maintainers. 34 | 35 | **Priority** 36 | If you'd like to, specify the priority of this bug for your organisation. Priority could be `High` (meaning that the bug is seriously hindering your workflow), `Medium`, or `Low`. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature you'd like to see in Ethlint 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description** 11 | Describe the idea. If you think there might be any edge cases, feel free to list them out too. 12 | 13 | **Use case** 14 | How did you conclude that you needed this feature? Did you face a problem? 15 | 16 | **Screenshots** 17 | If relevant, you may add screenshots. This can be useful if, for example, you want an improvement in the CLI output. 18 | 19 | **Alternatives** 20 | You may describe any hacks you pulled off to achieve what this feature will directly achieve. 21 | 22 | **Comments** 23 | Any additional information you'd like to share or suggestions on how the idea could be implemented. 24 | 25 | **Priority** 26 | If you'd like to, specify the priority of this feature for your organisation. Priority could be `High` (meaning you absolutely NEED this feature), `Medium`, or `Low`. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/lint-rule.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint Rule 3 | about: Suggest adding a lint rule for style or security 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Name** 11 | If you've thought of one, suggest a short name that gives a good hint about the purpose of the rule. Multiple words must be separated by hyphen. Avoid a name with more than 4 words. 12 | 13 | **Problem** 14 | The problem you faced while writing/analysing the code. 15 | 16 | **Description** 17 | What should the rule do, i.e., **how** should it solve the problem you faced? 18 | 19 | **Priority** 20 | If you'd like to, you can indicate the priority of this rule for your organisation. Priority can be `High` (meaning the rule will be a massive improvement for your workflow), `Medium` or `Low`. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Related Issue 2 | Specify the issue this PR addresses, if one exists. 3 | 4 | If your PR proposes a major change and you haven't discussed it first with the maintainers, we strongly encourage you to first raise an issue for discussion. This will help avoid waste of effort. 5 | 6 | ## Changes 7 | Describe the changes proposed in this PR. 8 | 9 | ## Declaration 10 | - [ ] I have followed the [Contribution Guidelines](https://github.com/duaraghav8/Ethlint/blob/master/CONTRIBUTING.md). 11 | - [ ] All tests are passing. 12 | - [ ] ESLint doesn't produce any `warnings`. 13 | - [ ] ESLint doesn't produce any `errors`. 14 | - [x] I'm awesome. 15 | 16 | **NOTE**: It is fine if ESLint does produce warnings for your PR **if** there is a good reason for it. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .DS_Store 4 | docs/_build 5 | coverage/ 6 | .nyc_output/ 7 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "6" 5 | 6 | os: 7 | - linux 8 | - osx 9 | 10 | script: 11 | - npm run lint 12 | - npm test 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.2.5 (2019-09-14) 4 | - Fixed `no-empty-blocks` to not report inherited constructors with empty blocks ([#264](https://github.com/duaraghav8/Ethlint/issues/264#issue-447647594)). 5 | - Added option `errorMessageMaxLength` for rule `error-reason` to specify a character limit on error message. 6 | 7 | ## 1.2.4 (2019-04-08) 8 | - Added rule `no-trailing-whitespace` to warn the user when code, comment or blank lines contain trailing whitespaces. This rule will supply the `fix` functionality in a future release. 9 | - Added `getLines()` sourceCode utility function for rule developers. This method returns the source code split into lines. 10 | - Added `getComments()` sourceCode utility function for rule developers. This method returns a list of AST Nodes representing comments in the source code. 11 | 12 | ## 1.2.3 (2019-02-11) 13 | - Added support for `solium-disable-previous-line` comment directive. 14 | - Added support for `solium-enable` comment directive. See [configuring with comments](https://ethlint.readthedocs.io/en/latest/user-guide.html#configuring-with-comments). This feature currently has a limitation which has been documented in [Known Issues](https://ethlint.readthedocs.io/en/latest/known-issues.html). 15 | - Added Pull Request template. 16 | - Fixed rule `no-empty-blocks` to report function declarations with empty bodies. Fallback and `payable` functions and `payable` constructors are not reported if their body is empty. See [#254](https://github.com/duaraghav8/Ethlint/issues/254). 17 | - Fixed rule `quotes` to stop reporting false positives due to brackets enclosing strings (see [#240](https://github.com/duaraghav8/Ethlint/issues/240)). 18 | - Modified rule `uppercase` to allow up to 2 leading and trailing underscores for a constant's name. 19 | 20 | ## 1.2.2 (2019-01-13) 21 | - Added support for parsing function declarations inside Inline Assembly blocks. 22 | - Added Issue Templates to the repository for Bug report, Feature request and Lint rule suggestion. 23 | - Added Contribution guidelines. 24 | - Changed `.soliumignore`-related warning messages to be more user-friendly. 25 | - Fixed bugs in parser related to Inline Assembly variable declaration. 26 | - Fixed `uppercase` rule to allow single-character names, where the character must be an alphabet. 27 | - Fixed `indentation` rule to allow Call expression arguments to start with circular bracket ([#223](https://github.com/duaraghav8/Ethlint/issues/223)). 28 | - Fixed `BinaryExpression` position bug in parser that led to [#175](https://github.com/duaraghav8/Ethlint/issues/175) & [#223](https://github.com/duaraghav8/Ethlint/issues/223). 29 | 30 | ## 1.2.1 (2019-01-01) :sparkler: 31 | - Added `fix` functionality to `linebreak-style` rule. 32 | - Added `linebreak-style` rule configuration to default `.soliumrc.json`. 33 | - Added support for tilde for specifying version literals in `pragma` statements. 34 | - Added rule `constructor` to warn the user when the deprecated style of constructor declaration is being used. 35 | - Added `--fix-dry-run` option to CLI to allow users to see a git-style diff of the changes the `--fix` option will make. 36 | - Fixed Hex literal parsing. Incorrect parsing caused the linter to crash in some [cases](https://github.com/duaraghav8/Ethlint/issues/232). 37 | - Fixed source code util's `getTextOnLine()` to account for both linebreak-styles on both platforms (see [issue](https://github.com/duaraghav8/Ethlint/issues/173)) 38 | - Changed documentation URL to [ethlint.readthedocs.io](https://ethlint.readthedocs.io). `solium.readthedocs.io` is deprecated but will receive updates. 39 | - Moved deprecated rules in documentation into their own section. 40 | 41 | ## 1.2.0 (2018-12-25) :santa: 42 | - Deprecated the npm package `solium`. All updates will be pushed simultaneously to npm packages `solium` and `ethlint`. There is no difference between the software being pushed to these packages, but it is highly recommended that you move to the `ethlint` npm package. 43 | - Added Changelog 44 | - Added syntax support for Solidity `0.5`. 45 | - Added `ignore` option for `function-order` rule (See [issue](https://github.com/duaraghav8/Ethlint/issues/235)) 46 | - Added file name in addition to stack trace in the output of `--debug` option. 47 | - Fixed the ignore feature to account for windows line endings in `.soliumignore` file. (Thanks to @romaric-juniet) 48 | - Fixed documentation errors. 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Ethlint 2 | *Thanks for any and all contributions you make to this project!* 3 | 4 | ## Issues and Feature requests 5 | For all bug reports, feature requests, lint rule suggestions and any other issues, use the [Issue Tracker](https://github.com/duaraghav8/Ethlint/issues/). For inquiries and general discussions, please head to the [Gitter Channel](https://gitter.im/Solium-linter/Lobby). 6 | 7 | To track the progress of Ethlint over time, see [Projects](https://github.com/duaraghav8/Ethlint/projects). 8 | 9 | ## Code 10 | Any change that needs to be [committed](https://git-scm.com/docs/git-commit) to this repository is a *code* change. 11 | 12 | - Almost all non-trivial code changes must be accompanied by tests. 13 | - Changes involving features, lint rules or utility methods for developers must be documented. 14 | - All major changes must have a corresponding entry in the Changelog file. See [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) on how to create a good changelog entry. 15 | 16 | ### Practices 17 | 1. Checkout your branch from the `develop` branch and direct all Pull Requests to `develop`. 18 | 2. Use `ES6`. 19 | 3. Use `const` instead of `let` wherever possible. 20 | 4. Avoid the [pyramid](https://cdn-images-1.medium.com/max/1600/1*3lEILqKvoasyVwpdlfVvbw.png) style of closing braces at all cost. 21 | 5. Make sure to both lint and test once you're done making all changes. See [documentation](https://ethlint.readthedocs.io/) on how to perform these. 22 | 6. If proposing a change that hasn't already been discussed in an issue, please raise an associated issue first. For example, it is best to raise an issue for a Lint rule and discuss it with the developers first to avoid the possibility of waste of effort. 23 | 24 | ### License 25 | By making any code contributions to Ethlint, you agree that they will be licensed under the MIT License. 26 | 27 | ## Donations 28 | We currently accept the following payment methods: 29 | - ETH: `0xacc661A56af9793a4437876a52F4Ad3fc3C443d6` 30 | 31 | ## Bounty sponsorship 32 | Please get in touch over our Gitter channel or shoot an email at duaraghav8@gmail.com. 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Raghav Dua, Christopher Gewecke, Federico Bond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 | 7 | [![Build Status](https://travis-ci.org/duaraghav8/Ethlint.svg?branch=master)](https://travis-ci.org/duaraghav8/Ethlint) 8 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/Solium-linter/Lobby) 9 | 10 | Ethlint (Formerly Solium) analyzes your Solidity code for style & security issues and fixes them. 11 | 12 | See [Documentation](https://ethlint.readthedocs.io/), [Changelog](./CHANGELOG.md) and [upcoming releases](https://github.com/duaraghav8/Ethlint/projects). 13 | 14 | Before beginning to work on a contribution, please read the [Guidelines](./CONTRIBUTING.md). 15 | 16 | ## Install 17 | ```bash 18 | npm install -g ethlint 19 | solium -V 20 | ``` 21 | 22 | For backward-compatibility, you can still use `npm install -g solium`. 23 | 24 | If you're currently using the `solium` package for `npm install`, it is highly recommended that you move to `ethlint`. The `solium` package will not receive updates after December, 2019. There are no differences between the updates pushed to `ethlint` and `solium` packages. 25 | 26 | ## Usage 27 | In the root directory of your DApp, run: 28 | ```bash 29 | solium --init 30 | ``` 31 | 32 | This creates 2 files for you: 33 | - `.soliumignore` - contains names of files and directories to ignore while linting 34 | - `.soliumrc.json` - contains configuration that tells Solium how to lint your project. You should modify this file to configure rules, plugins and sharable configs. 35 | 36 | `.soliumrc.json` looks like: 37 | 38 | ```json 39 | { 40 | "extends": "solium:recommended", 41 | "plugins": ["security"], 42 | "rules": { 43 | "quotes": ["error", "double"], 44 | "indentation": ["error", 4], 45 | "linebreak-style": ["error", "unix"] 46 | } 47 | } 48 | ``` 49 | 50 | To know which lint rules Solium applies for you, see [Style rules](http://ethlint.readthedocs.io/en/latest/user-guide.html#list-of-style-rules) and [Security rules](https://www.npmjs.com/package/solium-plugin-security#list-of-rules). 51 | 52 | --- 53 | **NOTE** 54 | 55 | Solium does **not** strictly adhere to Solidity [Style Guide](http://solidity.readthedocs.io/en/latest/style-guide.html). It aims to promote coding practices agreed upon by the community at large. 56 | 57 | --- 58 | 59 | ### Lint 60 | ```bash 61 | solium -f foobar.sol 62 | solium -d contracts/ 63 | ``` 64 | 65 | ### Configure with comments 66 | **Comment Directives** can be used to configure Solium to ignore specific pieces of code. 67 | They follow the pattern `solium-disable`. 68 | 69 | If you only use the directive, Solium disables all rules for the marked code. If that's not desirable, specify the rules to disable after the directive, separated by comma. 70 | 71 | - Disable linting on a specific line 72 | ``` 73 | contract Foo { 74 | /* solium-disable-next-line */ 75 | function() { 76 | bytes32 bar = 'Hello world'; // solium-disable-line quotes 77 | 78 | // solium-disable-next-line security/no-throw, indentation 79 | throw; 80 | } 81 | } 82 | ``` 83 | 84 | - Disable linting on entire file 85 | 86 | ``` 87 | /* solium-disable */ 88 | 89 | contract Foo { 90 | ... 91 | } 92 | ``` 93 | 94 | ### Fix 95 | Solium automatically fixes your code to resolve whatever issues it can. 96 | ```bash 97 | solium -d contracts/ --fix 98 | ``` 99 | 100 | ## Our supporters 101 |

102 | 103 | Ethereum 104 | 105 | 106 | Augur 107 | 108 |    109 | 110 | Gitcoin 111 | 112 |

113 | 114 | If Ethlint helped make your life simpler, please consider donating ETH to `0xacc661A56af9793a4437876a52F4Ad3fc3C443d6` 115 | 116 | #### [IDE and Editor Integrations](http://solium.readthedocs.io/en/latest/user-guide.html#index-9) | [Documentation](https://ethlint.readthedocs.io) | [Demo Video](https://www.youtube.com/watch?v=MlQ6fzwixpI) 117 | -------------------------------------------------------------------------------- /art/Solium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duaraghav8/Ethlint/06063057c23bce8daa844639e5539a4aebec294c/art/Solium.png -------------------------------------------------------------------------------- /art/augur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duaraghav8/Ethlint/06063057c23bce8daa844639e5539a4aebec294c/art/augur.png -------------------------------------------------------------------------------- /art/ethereum-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duaraghav8/Ethlint/06063057c23bce8daa844639e5539a4aebec294c/art/ethereum-logo.png -------------------------------------------------------------------------------- /art/gitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duaraghav8/Ethlint/06063057c23bce8daa844639e5539a4aebec294c/art/gitcoin.png -------------------------------------------------------------------------------- /art/solium-badge.svg: -------------------------------------------------------------------------------- 1 | Secured withSecured withSoliumSolium 2 | -------------------------------------------------------------------------------- /bin/solium.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * @fileoverview CLI Entry point 5 | * @author Raghav Dua 6 | */ 7 | 8 | "use strict"; 9 | 10 | require("../lib/cli").execute(process.argv); 11 | -------------------------------------------------------------------------------- /config/rulesets/solium-all.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview core ruleset solium:all which describes the default severity of all rules but doesn't pass any arguments. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | rules: { 11 | "imports-on-top": "error", 12 | "variable-declarations": "error", 13 | "array-declarations": "error", 14 | "operator-whitespace": "error", 15 | "lbrace": "error", 16 | "function-whitespace": "error", 17 | "semicolon-whitespace": "error", 18 | "comma-whitespace": "error", 19 | "conditionals-whitespace": "error", 20 | "value-in-payable": "error", 21 | "no-unused-vars": "error", 22 | "quotes": "error", 23 | "linebreak-style": "error", 24 | 25 | "mixedcase": "warning", 26 | "camelcase": "warning", 27 | "uppercase": "warning", 28 | "no-empty-blocks": "warning", 29 | "blank-lines": "warning", 30 | "indentation": "warning", 31 | "arg-overflow": "warning", 32 | "whitespace": "warning", 33 | "deprecated-suicide": "warning", 34 | "pragma-on-top": "warning", 35 | "function-order": "warning", 36 | "emit": "warning", 37 | "no-constant": "warning", 38 | "no-experimental": "warning", 39 | "max-len": "warning", 40 | "error-reason": "warning", 41 | "visibility-first": "warning", 42 | "constructor": "warning", 43 | "no-trailing-whitespace": "warning", 44 | 45 | // Turn OFF all deprecated rules 46 | "double-quotes": "off", 47 | "no-with": "off" 48 | } 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /config/rulesets/solium-recommended.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview core ruleset solium:recommended. An entry exists in this set for each core rule of Solium 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | rules: { 11 | "imports-on-top": "error", 12 | "variable-declarations": "error", 13 | "array-declarations": "error", 14 | "no-unused-vars": "error", 15 | "quotes": "error", 16 | "value-in-payable": "error", 17 | "linebreak-style": "error", 18 | 19 | "no-empty-blocks": "warning", 20 | "indentation": "warning", 21 | "whitespace": "warning", 22 | "deprecated-suicide": "warning", 23 | "pragma-on-top": "warning", 24 | "function-whitespace": "warning", 25 | "semicolon-whitespace": "warning", 26 | "comma-whitespace": "warning", 27 | "operator-whitespace": "warning", 28 | "emit": "warning", 29 | "no-constant": "warning", 30 | "max-len": "warning", 31 | "error-reason": "warning", 32 | "constructor": "warning", 33 | "visibility-first": "warning", 34 | 35 | "lbrace": "off", 36 | "mixedcase": "off", 37 | "camelcase": "off", 38 | "uppercase": "off", 39 | "blank-lines": "off", 40 | "arg-overflow": "off", 41 | "function-order": "off", 42 | "conditionals-whitespace": "off", 43 | "no-experimental": "off", 44 | "no-trailing-whitespace": "warning", 45 | 46 | // Disable deprecated rules 47 | "double-quotes": "off", 48 | "no-with": "off" 49 | } 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /config/schemas/ast-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of an Abstract Syntax Tree Node. 3 | * This schema only describes the properies that are mandatory across all nodes. 4 | * Additional attrs can exist depending on the entity the Node represents. 5 | * 6 | * @author Raghav Dua 7 | */ 8 | 9 | "use strict"; 10 | 11 | // A fully qualified, minimal object for this Schema is: 12 | /* 13 | { 14 | "type": "Literal", 15 | "start": 13, 16 | "end": 25 17 | } 18 | */ 19 | 20 | let array = { type: "array" }, number = { type: "number" }, bool = { type: "boolean" }, 21 | string = { type: "string" }, object = { type: "object" }, attrNull = { type: "null" }; 22 | 23 | let Schema = { 24 | 25 | type: "object", 26 | 27 | properties: { 28 | type: { type: "string", minLength: 1 }, 29 | start: { type: "integer", minimum: 0 }, 30 | end: { type: "integer", minimum: 0 } 31 | }, 32 | 33 | patternProperties: { 34 | "^.+$": { 35 | oneOf: [array, string, object, number, attrNull, bool] 36 | } 37 | }, 38 | 39 | required: ["type", "start", "end"], 40 | additionalProperties: false 41 | 42 | }; 43 | 44 | 45 | module.exports = Schema; -------------------------------------------------------------------------------- /config/schemas/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of the latest config object that is supplied to Solium 3 | * @author Raghav Dua 4 | */ 5 | 6 | // A fully qualified object for this Schema is: 7 | /* 8 | { 9 | "plugins": [ 10 | "satoshi" 11 | ], 12 | "extends": "solium:all", 13 | "rules": { 14 | "pragma-on-top": "off", 15 | "no-with": "warning", 16 | "deprecated-suicide": "error", 17 | "variable-declarations": 0, 18 | "imports-on-top": 1, 19 | "array-declarations": 2, 20 | "operator-whitespace": ["off", "double"], 21 | "lbrace": ["warning", 1, 2, {a: 100, h: "world"}], 22 | "mixedcase": ["error"], 23 | "camelcase": [0, 100, "hello", 9.283], 24 | "uppercase": [1], 25 | "double-quotes": [2, "double"], 26 | "satoshi/nakamoto": "error" 27 | }, 28 | "options": { "autofix": true, "autofixDryrun": true, "returnInternalIssues": true } 29 | } 30 | */ 31 | 32 | "use strict"; 33 | 34 | let severityString = { type: "string", enum: ["off", "warning", "error"] }, 35 | severityInt = { type: "integer", minimum: 0, maximum: 2 }, 36 | severityArray = { 37 | type: "array", 38 | minItems: 1, 39 | items: [{ oneOf: [severityString, severityInt] }] 40 | }; 41 | 42 | let Schema = { 43 | type: "object", 44 | 45 | anyOf: [ 46 | { required: ["extends"] }, 47 | { required: ["rules"] }, 48 | { required: ["plugins"] } 49 | ], 50 | 51 | properties: { 52 | 53 | plugins: { 54 | type: "array", 55 | items: { 56 | type: "string", minLength: 1 57 | } 58 | }, 59 | 60 | extends: { 61 | type: "string", 62 | minLength: 1 63 | }, 64 | 65 | rules: { 66 | type: "object", 67 | patternProperties: { 68 | "^.+$": { 69 | oneOf: [severityString, severityInt, severityArray] 70 | } 71 | }, 72 | additionalProperties: false 73 | }, 74 | 75 | options: { 76 | type: "object", 77 | properties: { 78 | autofix: { type: "boolean" }, 79 | autofixDryrun: { type: "boolean" }, 80 | debug: { type: "boolean" }, 81 | returnInternalIssues: { type: "boolean" } 82 | }, 83 | additionalProperties: false 84 | } 85 | 86 | }, 87 | 88 | additionalProperties: false 89 | }; 90 | 91 | 92 | module.exports = Schema; 93 | -------------------------------------------------------------------------------- /config/schemas/core-rule-response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of the latest Solium core rule response object. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | // A fully qualified object for this Schema is: 9 | /* 10 | { 11 | "Literal": function (context) { 12 | // ... 13 | }, 14 | 15 | "Program": function (context) { 16 | // ... 17 | } 18 | } 19 | */ 20 | 21 | let SchemaValidator = require("./core-rule").SchemaValidator; 22 | 23 | let Schema = { 24 | type: "object", 25 | 26 | patternProperties: { 27 | "^.+$": { 28 | shouldBeOfTypeFunction: true // This custom attribute is defined in SchemaValidator of ./core-rule.js 29 | } 30 | }, 31 | 32 | additionalProperties: false 33 | }; 34 | 35 | 36 | module.exports = { Schema: Schema, SchemaValidator: SchemaValidator }; 37 | -------------------------------------------------------------------------------- /config/schemas/core-rule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of the latest Solium core rule object. 3 | * Since this schema must also validate the type of 'create' to be a function, 4 | * we add a special rule "shouldBeOfTypeFunction" and export the SchemaValidator too. 5 | * 6 | * @author Raghav Dua 7 | */ 8 | 9 | "use strict"; 10 | 11 | // A fully qualified object for this Schema is: 12 | /* 13 | { 14 | "meta": { 15 | "docs": { 16 | "recommended": true, 17 | "type": "error", 18 | "description": "This is a rule", 19 | "replacedBy": ["new-rule"] 20 | }, 21 | 22 | "schema": [], 23 | 24 | "fixable": "code", 25 | 26 | "deprecated": true 27 | }, 28 | 29 | "create": function (context) {} 30 | } 31 | */ 32 | 33 | let Ajv = require("ajv"), 34 | SchemaValidator = new Ajv({ allErrors: true }); 35 | 36 | // If this constraint is set to true on any attribute, then that attribute MUST be of type function. If set to false, attr MUST NOT be a function. 37 | SchemaValidator.addKeyword("shouldBeOfTypeFunction", { 38 | validate(isSet, attr) { 39 | return isSet === (typeof attr === "function"); 40 | } 41 | }); 42 | 43 | 44 | let Schema = { 45 | type: "object", 46 | 47 | properties: { 48 | 49 | meta: { 50 | type: "object", 51 | properties: { 52 | 53 | docs: { 54 | type: "object", 55 | properties: { 56 | recommended: { type: "boolean" }, 57 | type: { type: "string", enum: ["error", "warning", "off"] }, 58 | description: { type: "string", minLength: 1 }, 59 | 60 | replacedBy: { 61 | type: "array", 62 | minItems: 1, 63 | items: { type: "string", minLength: 1 } 64 | } 65 | }, 66 | required: ["recommended", "type", "description"] 67 | }, 68 | 69 | schema: { type: "array", items: { type: "object" } }, 70 | 71 | fixable: { 72 | type: "string", enum: ["code", "whitespace"] 73 | }, 74 | 75 | deprecated: { type: "boolean" } 76 | 77 | }, 78 | required: ["docs", "schema"] 79 | }, 80 | 81 | create: { 82 | shouldBeOfTypeFunction: true 83 | } 84 | 85 | }, 86 | 87 | required: ["meta", "create"], 88 | 89 | additionalProperties: false 90 | }; 91 | 92 | 93 | module.exports = { Schema, SchemaValidator }; -------------------------------------------------------------------------------- /config/schemas/deprecated/config-v0.5.5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of the soliumrc config deprecated in v1.0.0. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let optionsSchema = require("../config").properties.options; 9 | 10 | // A fully qualified object for this Schema is: 11 | /* 12 | { 13 | "custom-rules-filename": "~/my-life/my-rules", 14 | "rules": { 15 | "deprecated-suicide": false, 16 | "pragma-on-top": true 17 | }, 18 | "options": { "autofix": true, "returnInternalIssues": true } 19 | } 20 | */ 21 | 22 | let Schema = { 23 | type: "object", 24 | 25 | properties: { 26 | "custom-rules-filename": { 27 | oneOf: [ 28 | { type: "string", minLength: 1 }, 29 | { type: "null" } 30 | ] 31 | }, 32 | 33 | rules: { 34 | type: "object", 35 | patternProperties: { 36 | "^.+$": { type: "boolean" } 37 | }, 38 | additionalProperties: false 39 | }, 40 | 41 | options: optionsSchema 42 | }, 43 | 44 | required: ["rules"], 45 | 46 | additionalProperties: false 47 | }; 48 | 49 | 50 | module.exports = Schema; -------------------------------------------------------------------------------- /config/schemas/error-reported-by-rule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of the error object supplied by a rule to rule-context via report() method. 3 | * NOTE: This is NOT the final error object supplied to solium & then to user. 4 | * 5 | * @author Raghav Dua 6 | */ 7 | 8 | "use strict"; 9 | 10 | // A fully qualified object for this Schema is: 11 | /* 12 | { 13 | "message": "Hello World", 14 | "node": { 15 | "type": "Literal", 16 | "start": 0, 17 | "end": 90 18 | }, 19 | "location": { 20 | "line": 3, 21 | "column": 90 22 | }, 23 | "fix": function (fixer) { 24 | // ... 25 | } 26 | } 27 | */ 28 | 29 | let Ajv = require("ajv"), 30 | astNode = require("./ast-node"), 31 | SchemaValidator = new Ajv({ allErrors: true }); 32 | 33 | 34 | SchemaValidator.addKeyword("shouldBeOfTypeFunction", { 35 | validate: function(isSet, attr) { 36 | return isSet === (typeof attr === "function"); 37 | } 38 | }); 39 | 40 | let Schema = { 41 | 42 | type: "object", 43 | properties: { 44 | 45 | message: { type: "string", minLength: 1 }, 46 | node: astNode, 47 | fix: { shouldBeOfTypeFunction: true }, 48 | location: { 49 | type: "object", 50 | properties: { 51 | line: { type: "integer", minimum: 1 }, // line starts from 1 52 | column: { type: "integer", minimum: 0 } // column starts from 0 53 | }, 54 | additionalProperties: false 55 | } 56 | 57 | }, 58 | 59 | required: ["message", "node"], 60 | additionalProperties: false 61 | 62 | }; 63 | 64 | 65 | module.exports = { 66 | schema: Schema, validationFunc: SchemaValidator.compile(Schema) 67 | }; -------------------------------------------------------------------------------- /config/schemas/error-supplied-to-solium.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of the error object supplied rule-context to Solium via Solium.report() method. 3 | * NOTE: This is still not the final error message object sent to user. 4 | * @author Raghav Dua 5 | */ 6 | 7 | "use strict"; 8 | 9 | let Ajv = require("ajv"), 10 | astNode = require("./ast-node"), 11 | coreRule = require("./core-rule").Schema, 12 | SchemaValidator = new Ajv({ allErrors: true }); 13 | 14 | 15 | SchemaValidator.addKeyword("shouldBeOfTypeFunction", { 16 | validate: function(isSet, attr) { 17 | return isSet === (typeof attr === "function"); 18 | } 19 | }); 20 | 21 | let Schema = { 22 | 23 | type: "object", 24 | properties: { 25 | 26 | message: { type: "string", minLength: 1 }, 27 | node: astNode, 28 | fix: { shouldBeOfTypeFunction: true }, 29 | ruleName: { type: "string", minLength: 1 }, 30 | ruleMeta: coreRule.properties.meta, 31 | type: { type: "string", enum: ["error", "warning"] }, 32 | 33 | location: { 34 | type: "object", 35 | properties: { 36 | line: { type: "integer", minimum: 1 }, // line starts from 1 37 | column: { type: "integer", minimum: 0 } // column starts from 0 38 | }, 39 | additionalProperties: false 40 | } 41 | 42 | }, 43 | 44 | required: ["message", "node", "ruleName", "ruleMeta", "type"], 45 | additionalProperties: false 46 | 47 | }; 48 | 49 | 50 | module.exports = { 51 | schema: Schema, validationFunc: SchemaValidator.compile(Schema) 52 | }; -------------------------------------------------------------------------------- /config/schemas/fixer-packet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of the fixer packet OR array of packets that SHOULD be returned by fix() 3 | * method inside an error object (reported by a rule). 4 | * 5 | * @author Raghav Dua 6 | */ 7 | 8 | "use strict"; 9 | 10 | // A fully qualified object for this Schema is: 11 | /* 12 | [ 13 | {range: [0, 0], text: 'hello'}, 14 | {range: [19, 30], text: ''} 15 | ] 16 | */ 17 | 18 | let Ajv = require("ajv"), validator = new Ajv({ allErrors: true }); 19 | 20 | let singleFixerPacket = { 21 | type: "object", 22 | 23 | properties: { 24 | 25 | range: { 26 | type: "array", 27 | minItems: 2, 28 | maxItems: 2, 29 | items: { 30 | type: "integer", minimum: 0 31 | } 32 | }, 33 | 34 | text: { 35 | type: "string" 36 | } 37 | 38 | }, 39 | 40 | required: ["range", "text"], 41 | additionalProperties: false 42 | }; 43 | 44 | let Schema = { 45 | oneOf: [ 46 | { 47 | type: "array", 48 | minItems: 1, 49 | items: singleFixerPacket 50 | }, 51 | 52 | singleFixerPacket 53 | ] 54 | }; 55 | 56 | 57 | module.exports = { 58 | Schema: Schema, 59 | validationFunc: validator.compile(Schema) 60 | }; 61 | -------------------------------------------------------------------------------- /config/schemas/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of latest Solium Plugin. 3 | * Uses core-rule schema since the structure for core rules & plugins rules is same. 4 | * @author Raghav Dua 5 | */ 6 | 7 | "use strict"; 8 | 9 | // A fully qualified object for this Schema is: 10 | /* 11 | { 12 | "rules": { 13 | 14 | "sample-rule-1": { 15 | "meta": { 16 | "docs": { 17 | "recommended": true, 18 | "type": "error", 19 | "description": "This is a rule", 20 | "replacedBy": ["new-rule"] 21 | }, 22 | 23 | "schema": [], 24 | "fixable": "code", 25 | "deprecated": true 26 | }, 27 | 28 | "create": function (context) {} 29 | } 30 | 31 | }, 32 | 33 | "meta": { 34 | "description": "This is my badass plugin" 35 | } 36 | } 37 | */ 38 | 39 | let coreRule = require("./core-rule"), SchemaValidator = coreRule.SchemaValidator; 40 | 41 | 42 | let Schema = { 43 | 44 | type: "object", 45 | 46 | properties: { 47 | 48 | rules: { 49 | type: "object", 50 | patternProperties: { "^.+$": coreRule.Schema }, 51 | additionalProperties: false 52 | }, 53 | 54 | meta: { 55 | type: "object", 56 | properties: { 57 | description: { type: "string", minLength: 1 } 58 | }, 59 | required: ["description"], 60 | additionalProperties: false 61 | } 62 | 63 | }, 64 | 65 | required: ["rules", "meta"], 66 | additionalProperties: false 67 | 68 | }; 69 | 70 | 71 | module.exports = { Schema: Schema, validationFunc: SchemaValidator.compile(Schema) }; -------------------------------------------------------------------------------- /config/schemas/sharable-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Schema of Solium v1 Sharable Config. 3 | * @author Raghav Dua 4 | */ 5 | 6 | // A fully qualified object for this Schema is: 7 | /* 8 | { 9 | "rules": { 10 | "pragma-on-top": [1], 11 | "quotes": ["error", "double"], 12 | "indentation": [2, "tab"] 13 | } 14 | } 15 | */ 16 | 17 | "use strict"; 18 | 19 | let rulesSchema = require("./config").properties.rules; 20 | 21 | 22 | let Schema = { 23 | 24 | type: "object", 25 | 26 | properties: { 27 | rules: rulesSchema 28 | }, 29 | 30 | required: ["rules"], 31 | additionalProperties: false 32 | 33 | }; 34 | 35 | 36 | module.exports = Schema; -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = Solium 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | ##### 2 | About 3 | ##### 4 | 5 | Solium was authored by `Raghav Dua `_ in 2016. 6 | 7 | It borrows ideas from `ESLint `_, `Solidity Parser `_ and other such ambitious projects. 8 | 9 | The linter was initially designed to strictly follow Solidity's official `Style Guide `_, but has since evolved into a completely customizable tool focused on style and security of smart contract code (read our `v1 release blog `_). 10 | 11 | 12 | .. index:: community 13 | 14 | ********* 15 | Community 16 | ********* 17 | 18 | 19 | Anyone who has contributed to strengthening this Project is a community member. 20 | 21 | - `The Ethereum Foundation `_ (see `Ethereum Inaugral Grants announcement `_) 22 | - `The Augur Project `_ (see `Augur Bounties `_) 23 | - `Beau Gunderson `_ 24 | - `Nicolas Feignon `_ 25 | - `Simon Hajjar `_ 26 | - `Mitchell Van Der Hoeff `_ 27 | - `Jack Peterson `_ 28 | - `Joseph Krug `_ 29 | - `Micah Zoltu `_ 30 | - `Tom Kysar `_ 31 | - `Artem Litchmanov `_ 32 | - `Michelle Pokrass `_ 33 | - `Tristan H `_ 34 | - `Federico Bond `_ 35 | - `Elena Dimitrova `_ 36 | - `Christopher Gewecke `_ 37 | - `Ulrich Petri `_ 38 | - `Leo Arias `_ 39 | - `Alex Chapman `_ 40 | - `Chih Cheng Liang `_ 41 | - `Jooraj Bednar `_ 42 | - `Juan Blanco `_ 43 | - `Florian Sey `_ 44 | - `Alex Step `_ 45 | - `Travis Jacobs `_ 46 | - `Remco Bloemen `_ 47 | - `Brett Sun `_ 48 | - `Franco Victorio `_ 49 | - `Gabriel Alacchi `_ 50 | - `Utkarsh Gupta `_ 51 | - `Ivan Mushketyk `_ 52 | - `Bernd Bohmeier `_ 53 | - `Donatas Stundys `_ 54 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | ############ 2 | Contributing 3 | ############ 4 | 5 | We're constantly looking out for awesome people to join our community and help make Solium a world-class static analyser that keeps production code in check. There are various opportunities for you to contribute to Solium, regardless of whether you're new to the project or deeply familiar with it. 6 | 7 | A few areas where we could use some help are: 8 | 9 | - All the `issues `_ on our repository tagged with ``Contributors needed`` and ``Help wanted``. These include adding new rules, moving the codebase to ES6, fixing some of the existing rules (like `whitespace `_ or `indentation `_). 10 | - If you're, by now, pretty familiar with Solium's codebase, you could also fix the un-tagged issues. 11 | - Feedback & Suggestions - always welcome. The author of solium sucks at User experience and would love to hear about what pain points still exist in your smart contract development workflow (regardless of whether they're directly related to solium or not). 12 | - This documentation! Yep, we could certainly use more eyeballs that can correct typos, paraphrase instructions, introduce diagrams or simply make the docs much cleaner. -------------------------------------------------------------------------------- /docs/experimental-features.rst: -------------------------------------------------------------------------------- 1 | ##################### 2 | Experimental Features 3 | ##################### 4 | 5 | At any given time, Solium might have a few experimental features in production. They're experimental in order to determine whether and to what extent they're beneficial. And we could really use your feedback on experimental features! 6 | 7 | These features haven't been mentioned anywhere in the developer and user guides to ensure that you're aware when you're using an experimental feature in your workflow. 8 | 9 | Only this section contains the list of the features. They are subject to change or even removal in subsequent releases. You can either use them temporarily or freeze the Solium version in your app to ensure thay always work for you (not recommended). 10 | 11 | As a rule of thumb, **never use the features listed in this section in your production apps**. 12 | 13 | 14 | .. index:: list of experimental features 15 | 16 | ***************************** 17 | List of Experimental features 18 | ***************************** 19 | 20 | v1.0.8 21 | ====== 22 | 23 | - Intuitive Util methods to aid rule devs determine node types. PR: `149 `_ Trial ends on: **25th Dec '17** 24 | 25 | 26 | See `experimental features issue `_ for discussions on them. -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: ../art/Solium.png 2 | :align: center 3 | 4 | Solium analyzes your Solidity code for style & security issues and fixes them. 5 | 6 | Standardize Smart Contract practices across your organisation. Integrate with your build system. Deploy with confidence! 7 | 8 | Solium does not strictly follow Solidity Style Guide. The practices it enforces by default are best practices for the community at large. 9 | 10 | 11 | Contents 12 | ======== 13 | 14 | :ref:`Keyword Index `, :ref:`Search Page ` 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | 19 | user-guide.rst 20 | developer-guide.rst 21 | experimental-features.rst 22 | known-issues.rst 23 | contributing.rst 24 | about.rst 25 | -------------------------------------------------------------------------------- /docs/known-issues.rst: -------------------------------------------------------------------------------- 1 | ############ 2 | Known Issues 3 | ############ 4 | 5 | While Solium is being actively maintained, a few major issues are still lurking around and we thought it best to make you aware of them so you don't spend time discovering them instead. 6 | 7 | - Solium is currently **file-aware instead of being project-aware**. What this means is that while linting, Solium doesn't have the context of all the contracts and how they may be using the contract currently being linted. A consequence of this is that the linter currently flags a state variable as unused if it doesn't find its usage in the same contract, whereas its clearly possible that you're ``import`` ing the contract elsewhere to use that variable (See `issue `_). This is a fairly critical problem and will be resolved in a future release. We believe a codebase-aware linter would be much more powerful because of its broader context. 8 | 9 | - The linter's internal parser supports Solidity ``v0.5``. This means that it supports the `calldata `_ storage location specifier, but in a non-backward-compatible manner. If you're currently using Solidity version < 0.5 and have used ``calldata`` as a name for a variable or function parameter, you might see false lint issues because ``calldata`` is treated as location and hence, the variable name is seen as ``null``. Regardless of whether you use Solium or not, it is a good idea to rename all such variables to keep your code compatible with Solidity 0.5. 10 | 11 | - When installing the Linter from the ``ethlint`` NPM package, you might see the following warning: 12 | 13 | .. code-block:: bash 14 | 15 | npm WARN solium-plugin-security@0.1.1 requires a peer of solium@^1.0.0 but none is installed. You must install peer dependencies yourself. 16 | 17 | You can safely ignore this warning. 18 | 19 | Solium was recently `renamed `_ to Ethlint and the linter is available for download from both ``solium`` and ``ethlint`` NPM packages. Ethlint comes shipped with its Security plugin. This plugin checks to ensure whether ``solium`` NPM package is installed or not. 20 | 21 | There is currently no way in NPM to *allow any one of the specified packages to satisfy as peer dependency*, so we can't specify ``solium OR ethlint``. We also cannot change ``solium`` to ``ethlint`` in ``peerDependencies`` because its a potential breaking change. See the `original issue `_. 22 | 23 | - There is a limitation when using the ``solium-enable`` comment directive: You cannot disable all rules (using ``// solium-disable`` for example) and then enable a select few (using ``// solium-enable rule1, rule2`` for example). The enabling part doesn't work and rules remain disabled even after using the ``enable`` directive. This is due to how the linter internally represents disabling **all** rules. 24 | 25 | In the below example, the ``security/no-throw`` rule will **not** be enabled on the ``throw;`` statement, against the expectations. 26 | 27 | .. code-block:: javascript 28 | 29 | contract Foo { 30 | // solium-disable 31 | function b11d() { 32 | // solium-enable security/no-throw 33 | throw; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=Solium 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /lib/autofix/merge-fixer-packets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Given an array containing fixer packets, merge them all into a single packet that accounts for changes made by all the smaller packets. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | /** 10 | * Merge all fixer packets into 1 packet and return it. 11 | * @params {(Array|Object)} fixesArrayOrObject Fixer packet(s) to be merged into a single fixer packet. 12 | * @params {String} sourceCode Source code. 13 | */ 14 | module.exports = function(fixesArrayOrObject, sourceCode) { 15 | // If argument is already a single fixes packet, return. 16 | if (!Array.isArray(fixesArrayOrObject)) { 17 | return fixesArrayOrObject; 18 | } 19 | 20 | let fixes = fixesArrayOrObject; 21 | 22 | if (fixes.length === 1) { 23 | return fixesArrayOrObject [0]; 24 | } 25 | 26 | // Sort fixer packets in top-down approach 27 | fixes.sort(function(a, b) { 28 | return (a.range [0] - b.range [0]) || (a.range [1] - b.range [1]); 29 | }); 30 | 31 | // Take start of first fix and end of last fix to merge them all into 1 big fixer packet, 32 | // combining all the code in between. 33 | let start = fixes [0].range [0], end = fixes [fixes.length - 1].range [1]; 34 | let text = "", cursor = Number.MIN_SAFE_INTEGER; 35 | 36 | fixes.forEach(function(fix) { 37 | /** 38 | * Condition below is '>' instead of '>=' on purpose. 39 | * Say we have 2 packets- {range: [12, 20]} & {range: [20, 28], text: 'world'}. 40 | * These should NOT conflict. After 1st packet (which actually makes changes to only 12 to 19 index), 41 | * cursor is set to 20. At start of 2nd packet, the slice (20, 20) returns empty string and hence, 42 | * there is no damage. 43 | */ 44 | if (cursor > fix.range [0]) { 45 | throw new Error("2 or more fix objects in the Fixer Array must not overlap."); 46 | } 47 | 48 | if (fix.range [0] >= 0) { 49 | text += sourceCode.slice(Math.max(0, start, cursor), fix.range [0]); 50 | } 51 | 52 | text += fix.text; 53 | cursor = fix.range [1]; 54 | }); 55 | 56 | text += sourceCode.slice(Math.max(0, start, cursor), end); 57 | 58 | return { 59 | range: [start, end], 60 | text: text 61 | }; 62 | }; -------------------------------------------------------------------------------- /lib/autofix/rule-fixer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Methods exposed to rule developers to define the fixes to be applied over a range in code or an AST Node. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let astUtils = require("../utils/ast-utils"); 9 | 10 | function validateArgs(args) { 11 | if (args.hasOwnProperty("node")) { 12 | if (!astUtils.isASTNode(args.node)) { 13 | throw new Error("A valid AST node was not provided."); 14 | } 15 | } 16 | 17 | if (args.hasOwnProperty("text")) { 18 | let text = args.text; 19 | 20 | // There are no restriction on string length 21 | if (typeof text !== "string") { 22 | throw new Error("A valid string was not provided."); 23 | } 24 | } 25 | 26 | if (args.hasOwnProperty("range")) { 27 | let range = args.range; 28 | 29 | if (!(Array.isArray(range) && range.length === 2 && Number.isInteger(range [0]) && 30 | Number.isInteger(range [1]) && range [0] >= 0 && range [1] >= 0)) { 31 | throw new Error( 32 | "A valid range object was not provided. Should be an Array of 2 unsigned Integers."); 33 | } 34 | } 35 | 36 | if (args.hasOwnProperty("index")) { 37 | let index = args.index; 38 | 39 | if (!(Number.isInteger(index) && index >= 0)) { 40 | throw new Error("A valid index was not provided. Must be an unsigned Integer."); 41 | } 42 | } 43 | 44 | if (args.hasOwnProperty("char")) { 45 | let char = args.char; 46 | 47 | if (!(typeof char === "string" && char.length === 1)) { 48 | throw new Error("A valid character was not provided. Must be a string with length of 1."); 49 | } 50 | } 51 | } 52 | 53 | 54 | function insertTextAt(index, text) { 55 | validateArgs({ index, text }); 56 | 57 | return { 58 | range: [index, index], 59 | text: text 60 | }; 61 | } 62 | 63 | 64 | let ruleFixerAPI = { 65 | 66 | insertTextAt, 67 | 68 | insertTextAfter(node, text) { 69 | validateArgs({ node: node, text: text }); 70 | return this.insertTextAfterRange([node.start, node.end], text); 71 | }, 72 | 73 | insertTextBefore(node, text) { 74 | validateArgs({ node: node, text: text }); 75 | return this.insertTextBeforeRange([node.start, node.end], text); 76 | }, 77 | 78 | remove(node) { 79 | return this.removeRange([node.start, node.end]); 80 | }, 81 | 82 | replaceText(node, text) { 83 | validateArgs({ node: node, text: text }); 84 | return this.replaceTextRange([node.start, node.end], text); 85 | }, 86 | 87 | insertTextAfterRange(range, text) { 88 | validateArgs({ range: range, text: text }); 89 | return insertTextAt(range [1], text); 90 | }, 91 | 92 | insertTextBeforeRange(range, text) { 93 | validateArgs({ range: range, text: text }); 94 | return insertTextAt(range [0], text); 95 | }, 96 | 97 | removeRange(range) { 98 | validateArgs({ range: range }); 99 | return this.replaceTextRange(range, ""); 100 | }, 101 | 102 | replaceChar(index, newChar) { 103 | validateArgs({ index: index, char: newChar }); 104 | return this.replaceTextRange([index, index + 1], newChar); 105 | }, 106 | 107 | replaceTextRange(range, text) { 108 | validateArgs({ range: range, text: text }); 109 | return { 110 | range: range, 111 | text: text 112 | }; 113 | } 114 | 115 | }; 116 | 117 | 118 | // eslint-disable-next-line no-unused-vars 119 | function RuleFixer(fixType) { 120 | Object.assign(this, ruleFixerAPI); 121 | } 122 | 123 | 124 | RuleFixer.prototype = { 125 | constructor: RuleFixer 126 | }; 127 | 128 | 129 | module.exports = RuleFixer; 130 | -------------------------------------------------------------------------------- /lib/autofix/source-code-fixer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Apply fixes described by the received fix packets to the given source code and return the 'fixed' code. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | let mergeFixes = require("./merge-fixer-packets"); 10 | 11 | 12 | function compareMessagesByLocation(a, b) { 13 | return (a.line - b.line) || (a.column - b.column); 14 | } 15 | 16 | function compareMessagesByFixRange(a, b) { 17 | return (a.fix.range [0] - b.fix.range [0]) || (a.fix.range [1] - b.fix.range [1]); 18 | } 19 | 20 | 21 | module.exports = { 22 | 23 | /** 24 | * Apply fixes to source code depending on whichever errors can be fixed. 25 | * @param {String} sourceCode Code to fix 26 | * @param {Array} errorMessages Error objects that describe the error and possibly how to fix it. 27 | * @returns {Object} fixed Contains fixed code and information about fixes applied & remaining un-fixed errors. 28 | */ 29 | applyFixes: function(sourceCode, errorMessages) { 30 | let fixedSourceCode = "", fixes = [], fixesApplied = [], remainingMessages = []; 31 | let cursor = Number.NEGATIVE_INFINITY; 32 | 33 | function attemptFix(fix) { 34 | let start = fix.range [0], end = fix.range [1]; 35 | 36 | // If this fix overlaps with the previous one or has negaive range, return. 37 | // Note that when cursor === start, its NOT an overlap since when source code in range 38 | // [i, j] is being edited, the code covered is actually from i to j-1. 39 | if (cursor > start || start > end) { 40 | return false; 41 | } 42 | 43 | fixedSourceCode += sourceCode.slice(Math.max(0, cursor), Math.max(0, start)); 44 | fixedSourceCode += fix.text; 45 | cursor = end; 46 | return true; 47 | } 48 | 49 | // Segregate errors that can be fixed from those that can't for sure. 50 | errorMessages.forEach(function(msg) { 51 | if (msg.fix) { 52 | // If msg.fix is an Array of fix packets, merge them into a single fix packet. 53 | try { 54 | msg.fix = mergeFixes(msg.fix, sourceCode); 55 | } catch (e) { 56 | throw new Error("An error occured while applying fix of rule \"" 57 | + msg.ruleName + "\" for error \"" + msg.message + "\": " + e.message); 58 | } 59 | 60 | return fixes.push(msg); 61 | } 62 | 63 | remainingMessages.push(msg); 64 | }); 65 | 66 | // Fixes will be applied in top-down approach. The fix that arrives first (line-wise, followed by column-wise) 67 | // gets applied first. But if current fix is applied successfully & the next one overlaps the current one, 68 | // then the next one is simply skipped. Hence, it is NOT guranteed that all fixes will be applied. 69 | fixes.sort(compareMessagesByFixRange).forEach(function(msg) { 70 | if (attemptFix(msg.fix)) { 71 | return fixesApplied.push(msg); 72 | } 73 | 74 | remainingMessages.push(msg); 75 | }); 76 | 77 | fixedSourceCode += sourceCode.slice(Math.max(0, cursor)); 78 | remainingMessages.sort(compareMessagesByLocation); 79 | 80 | return { 81 | fixesApplied: fixesApplied, 82 | fixedSourceCode: fixedSourceCode, 83 | remainingErrorMessages: remainingMessages 84 | }; 85 | } 86 | 87 | }; -------------------------------------------------------------------------------- /lib/cli-utils/.default-solium-ignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/Migrations.sol 3 | -------------------------------------------------------------------------------- /lib/cli-utils/.default-soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": ["security"], 4 | "rules": { 5 | "quotes": ["error", "double"], 6 | "indentation": ["error", 4], 7 | "linebreak-style": ["error", "unix"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/reporters/gcc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview The object responsible for reporting errors on commandline in a compact, readable fashion 3 | * @author Federico Bond 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | reportFatal(message) { 11 | process.stderr.write(`[Fatal error] ${message}\n`); 12 | }, 13 | 14 | reportInternal(message) { 15 | process.stdout.write(`[Warning] ${message}\n`); 16 | }, 17 | 18 | report(filename, sourceCode, lintErrors, fixesApplied) { 19 | let internalIssuesExist = false; 20 | 21 | // Examine internal issues first 22 | lintErrors.forEach((issue, index) => { 23 | if (!issue.internal) { 24 | return; 25 | } 26 | 27 | process.stdout.write(`${issue.message}\n`); 28 | 29 | delete lintErrors [index]; 30 | internalIssuesExist = true; 31 | }); 32 | 33 | internalIssuesExist && process.stdout.write("\n"); 34 | 35 | lintErrors.forEach(error => { 36 | const { line, column, type, message, ruleName } = error; 37 | process.stdout.write(`${filename}:${line}:${column}: ${type}: ${message} [${ruleName}]\n`); 38 | }); 39 | 40 | Array.isArray(fixesApplied) && process.stdout.write(`\nNumber of fixes applied: ${fixesApplied.length}\n`); 41 | } 42 | 43 | }; -------------------------------------------------------------------------------- /lib/rule-context.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview RuleContext object's class definition - this object is what we pass to every rule being executed 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let util = require("util"), 9 | { EOL } = require("os"), 10 | isErrObjectValid = require("../config/schemas/error-reported-by-rule").validationFunc; 11 | 12 | let INHERITABLE_METHODS = [ 13 | "getSourceCode" 14 | ]; 15 | 16 | /** 17 | * context object Constructor to set read-only properties and provide additional functionality to the rules using it 18 | * @param {String} ruleName Name of the rule the object is for 19 | * @param {Object} ruleDesc Description of the rule the object is for 20 | * @param {Object} ruleMeta meta object defined inside the rule file by rule developer 21 | * @param {Object} Solium Main Solium object from which to inherit functionality to provide to the rules 22 | */ 23 | function RuleContext(ruleName, ruleDesc, ruleMeta, Solium) { 24 | let contextObject = this; 25 | 26 | // Set contect attribute 'options' iff options were provided. 27 | ruleDesc.options && Object.assign(contextObject, { options: ruleDesc.options }); 28 | 29 | //set read-only properties of the context object 30 | Object.defineProperties(contextObject, { 31 | 32 | name: { 33 | value: ruleName, 34 | writable: false //though the default is false anyway, I think its better to express your intention clearly 35 | }, 36 | 37 | meta: { 38 | value: ruleDesc, 39 | writable: false 40 | } 41 | 42 | }); 43 | 44 | //inherit all Solium methods which are of relevance to the rule 45 | INHERITABLE_METHODS.forEach(function(methodName) { 46 | contextObject [methodName] = function(s, z, a, b, o) { //every method will receive 5 arguments tops 47 | return Solium [methodName].call(Solium, s, z, a, b, o); 48 | }; 49 | }); 50 | 51 | /** 52 | * wrapper around Solium.report () which adds some additional information to the error object 53 | * @param {Object} error An object describing the lint error, sent by the rule currently running 54 | */ 55 | contextObject.report = function(error) { 56 | 57 | if (!isErrObjectValid(error)) { 58 | throw new Error( 59 | `Rule "${ruleName}": invalid error object was passed. AJV message:${EOL}${util.inspect(isErrObjectValid.errors)}` 60 | ); 61 | } 62 | 63 | Object.assign(error, { ruleName: ruleName, ruleMeta: ruleMeta, type: contextObject.meta.type }); 64 | Solium.report(error); 65 | 66 | }; 67 | } 68 | 69 | 70 | RuleContext.prototype = { constructor: RuleContext }; 71 | module.exports = RuleContext; 72 | -------------------------------------------------------------------------------- /lib/rules/arg-overflow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview In the case of 4+ elements in the same line require they are instead put on a single line each 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | module.exports = { 10 | 11 | meta: { 12 | 13 | docs: { 14 | recommended: true, 15 | type: "warning", 16 | description: "In the case of 4 or more items in the same line, require they are instead put on a single line each" 17 | }, 18 | 19 | schema: [{ 20 | type: "integer", minimum: 1 21 | }] 22 | 23 | }, 24 | 25 | create: function(context) { 26 | 27 | let MAX_IN_SINGLE_LINE = context.options ? context.options [0] : 3; 28 | 29 | let sourceCode = context.getSourceCode(); 30 | 31 | function inspectArrayExpression(emitted) { 32 | let node = emitted.node, elements = node.elements; 33 | let endingLineNum = sourceCode.getEndingLine(node); 34 | 35 | if (emitted.exit) { 36 | return; 37 | } 38 | 39 | if (sourceCode.getLine(node) === endingLineNum) { 40 | if (elements.length > MAX_IN_SINGLE_LINE) { 41 | context.report({ 42 | node: node, 43 | message: "In case of more than " + MAX_IN_SINGLE_LINE + 44 | " elements, array expression needs to be spread over multiple lines with 1 element per line." 45 | }); 46 | } 47 | 48 | return; 49 | } 50 | } 51 | 52 | function inspectStructDeclaration(emitted) { 53 | let node = emitted.node, 54 | body = node.body || [], 55 | endingLineNum = sourceCode.getEndingLine(node); 56 | 57 | if (emitted.exit) { 58 | return; 59 | } 60 | 61 | //raise an error and stop linting if more than 3 attributes exist & declaration is on single line 62 | if (sourceCode.getLine(node) === endingLineNum) { 63 | if (body.length > MAX_IN_SINGLE_LINE) { 64 | context.report({ 65 | node: node, 66 | message: "\"" + node.name + "\": In case of more than " + MAX_IN_SINGLE_LINE + " properties, struct declaration needs to be spread over multiple lines with 1 property per line." 67 | }); 68 | } 69 | return; 70 | } 71 | } 72 | 73 | //function params (if on multiple lines) 74 | function inspectFunctionDeclaration(emitted) { 75 | let node = emitted.node, params = node.params || []; 76 | 77 | let startLine = sourceCode.getLine(node), 78 | lastArgLine = params.length ? sourceCode.getEndingLine(params.slice(-1) [0]) : startLine; 79 | 80 | if (emitted.exit) { 81 | return; 82 | } 83 | 84 | if (startLine === lastArgLine) { 85 | if (params.length > MAX_IN_SINGLE_LINE) { 86 | context.report({ 87 | node: node, 88 | message: "In case of more than " + MAX_IN_SINGLE_LINE + " parameters, drop each into its own line." 89 | }); 90 | } 91 | return; 92 | } 93 | } 94 | 95 | function inspectCallExpression(emitted) { 96 | 97 | let node = emitted.node; 98 | let endingLineNum = sourceCode.getEndingLine(node); 99 | 100 | if (emitted.exit) { 101 | return; 102 | } 103 | 104 | if (sourceCode.getLine(node) === endingLineNum) { 105 | if (node.arguments.length > MAX_IN_SINGLE_LINE) { 106 | context.report({ 107 | node: node, 108 | message: "Function \"" + node.callee.name + "\": in case of more than " + MAX_IN_SINGLE_LINE + " arguments, drop each into its own line." 109 | }); 110 | } 111 | return; 112 | } 113 | } 114 | 115 | return { 116 | CallExpression: inspectCallExpression, 117 | FunctionDeclaration: inspectFunctionDeclaration, 118 | ConstructorDeclaration: inspectFunctionDeclaration, 119 | StructDeclaration: inspectStructDeclaration, 120 | ArrayExpression: inspectArrayExpression 121 | }; 122 | 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /lib/rules/array-declarations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that array declarations don't have space between the type and brackets (i.e. uint[] x, not uint [] x; or uint[ ] x;) 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "error", 15 | description: "Ensure that array declarations don't have space between the type and brackets" 16 | }, 17 | 18 | schema: [], 19 | 20 | fixable: "whitespace" 21 | 22 | }, 23 | 24 | create: function(context) { 25 | 26 | let sourceCode = context.getSourceCode(); 27 | 28 | function inspectType(emitted) { 29 | let node = emitted.node; 30 | 31 | if (emitted.exit || !node.array_parts.length) { 32 | return; 33 | } 34 | 35 | let code = sourceCode.getText(node), whitespaceDetector = /\s*(?=\[)/; 36 | 37 | // First the regex must detect whitespace between literal and opening bracket 38 | let scanned = whitespaceDetector.exec(code), 39 | whitespaceString = scanned [0], index = scanned.index; 40 | 41 | whitespaceString !== "" && context.report({ 42 | node: node, 43 | location: { column: index + 1 }, 44 | message: ( 45 | "There should be no whitespace between \"" 46 | + code.slice(0, index) + "\" and the opening square bracket." 47 | ), 48 | fix: function(fixer) { 49 | return [fixer.removeRange( 50 | [node.start + index, node.start + index + whitespaceString.length])]; 51 | } 52 | }); 53 | 54 | // Next, the regex must detect whitespace between opening & closing brackets 55 | // Since this regex doesn't exclude the brackets themselves, we have to move positions to acquire 56 | // the right info. 57 | if (node.array_parts.length === 1 && node.array_parts [0] === null) { 58 | let bracketString, indexWs; 59 | 60 | whitespaceDetector = /\[(\s*)\]/; 61 | scanned = whitespaceDetector.exec(code); 62 | bracketString = scanned [0], whitespaceString = scanned [1], index = scanned.index, indexWs = index + 1; 63 | 64 | (whitespaceString !== "") && context.report({ 65 | 66 | node: node, 67 | location: { column: indexWs + 1 }, 68 | 69 | fix: function(fixer) { 70 | return [fixer.replaceTextRange( 71 | [node.start + index, node.start + index + bracketString.length], "[]")]; 72 | }, 73 | 74 | message: "There should be no whitespace between opening & closing square brackets. Use [] instead." 75 | 76 | }); 77 | } 78 | } 79 | 80 | return { 81 | Type: inspectType 82 | }; 83 | 84 | } 85 | 86 | }; 87 | -------------------------------------------------------------------------------- /lib/rules/camelcase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that contract, library, modifier and struct names follow CamelCase notation 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure that contract, library, modifier and struct names follow CamelCase notation" 16 | }, 17 | 18 | schema: [] 19 | 20 | }, 21 | 22 | create: function(context) { 23 | 24 | let camelCaseRegEx = /^([A-Z][A-Za-z0-9]+)+$/; 25 | let nodesToWatch = { 26 | "ContractStatement": "Contract", 27 | "LibraryStatement": "Library", 28 | "StructDeclaration": "Struct", 29 | "EventDeclaration": "Event" 30 | }; 31 | 32 | function createInspector(nodeDesc) { 33 | return (function inspect(emitted) { 34 | let node = emitted.node; 35 | 36 | if (emitted.exit) { 37 | return; 38 | } 39 | 40 | if (!camelCaseRegEx.test(node.name)) { 41 | context.report({ 42 | node: node, 43 | message: nodeDesc + " name '" + node.name + "' doesn't follow the CamelCase notation." 44 | }); 45 | } 46 | }); 47 | } 48 | 49 | return Object.keys(nodesToWatch).reduce(function(listeners, nodeName) { 50 | 51 | listeners [nodeName] = createInspector(nodesToWatch [nodeName]); 52 | return listeners; 53 | 54 | }, {}); 55 | 56 | } 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /lib/rules/conditionals-whitespace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that there is exactly one space between conditional operators and parenthetic blocks 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "error", 15 | description: "Ensure that there is exactly one space between conditional operators and parenthetic blocks" 16 | }, 17 | 18 | schema: [] 19 | 20 | }, 21 | 22 | create: function(context) { 23 | 24 | let sourceCode = context.getSourceCode(); 25 | 26 | function inspectIfStatement(emitted) { 27 | let node = emitted.node; 28 | 29 | if (emitted.exit) { 30 | return; 31 | } 32 | 33 | /** 34 | * Ensure a single space between 'if' token and the opening parenthesis 'if (...)' 35 | */ 36 | let ifTokenLength = "if".length, 37 | nodeCode = sourceCode.getText(node).slice(ifTokenLength, ifTokenLength + 2); 38 | 39 | (nodeCode !== " (") && context.report({ 40 | node: node, 41 | location: { 42 | column: sourceCode.getColumn(node) + ifTokenLength 43 | }, 44 | message: "There should be exactly a single space between the 'if' token and the parenthetic block representing the conditional." 45 | }); 46 | } 47 | 48 | 49 | function inspectWhileStatement(emitted) { 50 | let node = emitted.node; 51 | 52 | if (emitted.exit) { 53 | return; 54 | } 55 | 56 | /** 57 | * Ensure a single space between 'while' token and the opening parenthesis 'while (...)' 58 | */ 59 | let whileTokenLength = "while".length, 60 | nodeCode = sourceCode.getText(node).slice(whileTokenLength, whileTokenLength + 2); 61 | 62 | (nodeCode !== " (") && context.report({ 63 | node: node, 64 | location: { 65 | column: sourceCode.getColumn(node) + whileTokenLength 66 | }, 67 | message: "There should be exactly a single space between the 'while' token and the parenthetic block representing the conditional." 68 | }); 69 | } 70 | 71 | 72 | function inspectForStatement(emitted) { 73 | let node = emitted.node; 74 | 75 | if (emitted.exit) { 76 | return; 77 | } 78 | 79 | /** 80 | * Ensure a single space between 'for' token and the opening parenthesis 'for (...)' 81 | */ 82 | let forTokenLength = "for".length, 83 | nodeCode = sourceCode.getText(node).slice(forTokenLength, forTokenLength + 2); 84 | 85 | (nodeCode !== " (") && context.report({ 86 | node: node, 87 | location: { 88 | column: sourceCode.getColumn(node) + forTokenLength 89 | }, 90 | message: "There should be exactly a single space between the 'for' token and the parenthetic block representing the conditional." 91 | }); 92 | } 93 | 94 | return { 95 | IfStatement: inspectIfStatement, 96 | WhileStatement: inspectWhileStatement, 97 | ForStatement: inspectForStatement 98 | }; 99 | 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /lib/rules/constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Warn against the deprecated use of contract name for constructors 3 | * @author Utkarsh Patil , Daniel McLellan 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | meta: { 10 | docs: { 11 | recommended: true, 12 | type: "warning", 13 | description: "Use 'constructor' instead of function with contract name for constructor declaration" 14 | }, 15 | schema: [], 16 | fixable: "code" 17 | }, 18 | 19 | create(context) { 20 | function lintConstructorName(emitted) { 21 | if (emitted.exit) { 22 | return; 23 | } 24 | 25 | const { node } = emitted, { body } = node; 26 | 27 | body.filter(child => { 28 | return child.type === "FunctionDeclaration"; 29 | }).forEach(funcNode => { 30 | if (funcNode.name !== node.name) { 31 | return; 32 | } 33 | 34 | const errorObject = { 35 | node: funcNode, 36 | fix(fixer) { 37 | let fixed = context.getSourceCode().getText(funcNode).replace("function", ""); 38 | fixed = "constructor" + fixed.substring(fixed.indexOf(funcNode.name) + funcNode.name.length); 39 | return fixer.replaceText(funcNode, fixed); 40 | }, 41 | message: "Constructor declaration style is deprecated" 42 | }; 43 | 44 | context.report(errorObject); 45 | }); 46 | } 47 | 48 | return { 49 | ContractStatement: lintConstructorName 50 | }; 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /lib/rules/deprecated-suicide.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Suggest replacing deprecated suicide for selfdestruct 3 | * @author Federico Bond 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | function isSuicide(node) { 10 | return node.type === "Identifier" && node.name === "suicide"; 11 | } 12 | 13 | module.exports = { 14 | 15 | meta: { 16 | 17 | docs: { 18 | recommended: true, 19 | type: "warning", 20 | description: "Suggest replacing deprecated 'suicide' for 'selfdestruct'" 21 | }, 22 | 23 | schema: [], 24 | 25 | fixable: "code" 26 | 27 | }, 28 | 29 | create: function(context) { 30 | function inspectCallExpression(emittedObject) { 31 | if (!emittedObject.exit) { 32 | return; 33 | } 34 | 35 | let callee = emittedObject.node.callee; 36 | 37 | if (isSuicide(callee)) { 38 | 39 | context.report({ 40 | node: emittedObject.node, 41 | fix: function(fixer) { 42 | return [fixer.replaceTextRange([callee.start, callee.end], "selfdestruct")]; 43 | }, 44 | message: "'suicide' is deprecated. Use 'selfdestruct' instead." 45 | }); 46 | 47 | } 48 | } 49 | 50 | return { 51 | CallExpression: inspectCallExpression 52 | }; 53 | } 54 | 55 | }; -------------------------------------------------------------------------------- /lib/rules/double-quotes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that strings are quoted with double-quotes only 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** 9 | * Determine whether the provided literal is in Hex Notation 10 | * @param {String} literal The literal to test for Hex Notation 11 | * @returns {Boolean} 12 | */ 13 | function isHex(literal) { 14 | let reg = /^[0-9a-f]+$/i; 15 | 16 | //test for '0x' separately because hex notation should not be a part of the standard RegExp 17 | if (literal.slice(0, 2) !== "0x") { 18 | return false; 19 | } 20 | 21 | return reg.test(literal.slice(2)); 22 | } 23 | 24 | module.exports = { 25 | 26 | meta: { 27 | 28 | docs: { 29 | recommended: true, 30 | type: "error", 31 | description: "Ensure that string are quoted with double-quotes only", 32 | replacedBy: ["quotes"] 33 | }, 34 | 35 | schema: [], 36 | deprecated: true 37 | 38 | }, 39 | 40 | create: function(context) { 41 | 42 | let doubleQuotesLiteralRegExp = /^\".*\"$/, 43 | sourceCode = context.getSourceCode(); 44 | 45 | function inspectLiteral(emitted) { 46 | let node = emitted.node, nodeText = sourceCode.getText(node); 47 | 48 | if (emitted.exit || 49 | typeof node.value !== "string" || 50 | (nodeText [0] !== "'" && nodeText [0] !== "\"" && isHex(node.value)) 51 | ) { 52 | return; 53 | } 54 | 55 | if (!doubleQuotesLiteralRegExp.test(nodeText)) { 56 | context.report({ 57 | node: node, 58 | message: "'" + node.value + "': String Literals must be quoted with \"double quotes\" only." 59 | }); 60 | } 61 | } 62 | 63 | return { 64 | Literal: inspectLiteral 65 | }; 66 | 67 | } 68 | 69 | }; 70 | -------------------------------------------------------------------------------- /lib/rules/emit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Use emit statement to trigger a solidity event 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | /** 10 | * While entering nodes, register all events and all call expressions that are not part of emit statement 11 | * While exiting, determine which of the registered call expressions is an event trigger and flag them 12 | * NOTE: This rule currently doesn't flag issues where event declaration & non-"emit"ted event trigger reside in 13 | * different contracts which are linked through inheritance. 14 | * 15 | * TODO: Enhance this rule to also detect event declarations inherited from other contracts 16 | * and whether they're being triggered using "emit". 17 | */ 18 | function create(context) { 19 | const events = [], callExpressions = [], sourceCode = context.getSourceCode(); 20 | 21 | // Determines whether the given call name refers to an event declaration using scope resolution. 22 | function isEvent(expr, eventDeclarations) { 23 | for (let { node, enclosingContract } of eventDeclarations) { 24 | if (expr.callee.name === node.name && sourceCode.isAChildOf(expr, enclosingContract)) { 25 | return true; 26 | } 27 | } 28 | return false; 29 | } 30 | 31 | 32 | // Stores each declared event in the file and its corresponding parent contract 33 | function registerEventName(emitted) { 34 | const { node } = emitted; 35 | (!emitted.exit) && events.push({ node, enclosingContract: sourceCode.getParent(node) }); 36 | } 37 | 38 | function registerNonEmittedCallExpression(emitted) { 39 | const { node } = emitted; 40 | 41 | if (!emitted.exit && sourceCode.getParent(node).type !== "EmitStatement") { 42 | callExpressions.push(node); 43 | } 44 | } 45 | 46 | function reportBadEventTriggers(emitted) { 47 | if (!emitted.exit) { 48 | return; 49 | } 50 | 51 | callExpressions.forEach(node => { 52 | isEvent(node, events) && context.report({ 53 | node, 54 | fix(fixer) { 55 | return fixer.insertTextBefore(node, "emit "); 56 | }, 57 | message: "Use emit statements for triggering events." 58 | }); 59 | }); 60 | } 61 | 62 | 63 | return { 64 | EventDeclaration: registerEventName, 65 | CallExpression: registerNonEmittedCallExpression, 66 | Program: reportBadEventTriggers 67 | }; 68 | } 69 | 70 | 71 | module.exports = { 72 | 73 | meta: { 74 | docs: { 75 | recommended: true, 76 | type: "warning", 77 | description: "Use emit statement to trigger a solidity event" 78 | }, 79 | schema: [], 80 | fixable: "code" 81 | }, 82 | 83 | create 84 | 85 | }; -------------------------------------------------------------------------------- /lib/rules/error-reason.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that error message is provided for revert and require statements 3 | * @author Donatas Stundys 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure that error message is provided for revert and require statements" 16 | }, 17 | 18 | schema: [{ 19 | type: "object", 20 | properties: { 21 | revert: { type: "boolean" }, 22 | require: { type: "boolean" }, 23 | errorMessageMaxLength: { type: "integer", minimum: 1 } 24 | }, 25 | additionalProperties: false 26 | }] 27 | 28 | }, 29 | 30 | create(context) { 31 | 32 | function inspectCallExpression(emitted) { 33 | const { node } = emitted; 34 | 35 | if (emitted.exit) { 36 | return; 37 | } 38 | 39 | const { name } = node.callee, callArgs = node.arguments; 40 | const options = { revert: true, require: true, errorMessageMaxLength: 76 }; 41 | let callArgsErrorMessageIndex = -1; 42 | 43 | if (context.options) { 44 | const { revert: rev, require: req, errorMessageMaxLength: max } = context.options[0]; 45 | 46 | if (rev !== undefined) { 47 | options.revert = rev; 48 | } 49 | if (req !== undefined) { 50 | options.require = req; 51 | } 52 | if (max !== undefined) { 53 | options.errorMessageMaxLength = max; 54 | } 55 | } 56 | 57 | if (options.revert && name === "revert") { 58 | if (callArgs.length < 1) { 59 | return context.report({ node, message: "Provide an error message for revert()" }); 60 | } 61 | callArgsErrorMessageIndex = 0; // Access the first call arg to get error message 62 | } 63 | 64 | if (options.require && name === "require") { 65 | if (callArgs.length < 2) { 66 | return context.report({ node, message: "Provide an error message for require()" }); 67 | } 68 | callArgsErrorMessageIndex = 1; // Access the second call arg to get error message 69 | } 70 | 71 | // If the function does contain the error message, validate it for max length 72 | if (callArgsErrorMessageIndex > -1) { 73 | const { value: errMessage } = callArgs[callArgsErrorMessageIndex]; 74 | 75 | if (typeof errMessage === "string" && errMessage.length > options.errorMessageMaxLength) { 76 | return context.report({ 77 | node, 78 | message: `Error message exceeds max length of ${options.errorMessageMaxLength} characters` 79 | }); 80 | } 81 | } 82 | } 83 | 84 | return { 85 | CallExpression: inspectCallExpression 86 | }; 87 | 88 | } 89 | 90 | }; 91 | -------------------------------------------------------------------------------- /lib/rules/imports-on-top.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that all imports are on top of the file 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | // Get platform specific newline character for applying fixes 9 | const eol = require("os").EOL; 10 | 11 | 12 | module.exports = { 13 | 14 | meta: { 15 | 16 | docs: { 17 | recommended: true, 18 | type: "error", 19 | description: "Ensure that all import statements are on top of the file" 20 | }, 21 | 22 | fixable: "code", 23 | 24 | schema: [] 25 | 26 | }, 27 | 28 | create: function(context) { 29 | 30 | /** 31 | * If ImportStatement node is a direct child of Program node 'parent', 32 | * then it is an element of parent.body array. 33 | * The node should precede any other type of node in the array except for the pragma & pragma experimental directives 34 | * and other import statements. 35 | * The way the fix() algorithm currently works - it only fixes position of 1 import statement in a file in 1 iteration. 36 | * This may be improved in future. 37 | */ 38 | function inspectImportStatement(emitted) { 39 | if (emitted.exit) { 40 | return; 41 | } 42 | 43 | const { node } = emitted, 44 | programNode = context.getSourceCode().getParent(node), 45 | indexOfNode = programNode.body.indexOf(node), 46 | nodesAllowedAbove = ["ExperimentalPragmaStatement", "PragmaStatement", "ImportStatement"]; 47 | 48 | let lastValidNode = programNode.body[0]; // the first node could very well be an unacceptable one, taking care of it below 49 | 50 | // For every node preceding the import node check if it's one of the allowed types. 51 | for (let childNode of programNode.body.slice(0, indexOfNode)) { 52 | 53 | // The moment we find 1 node not allowed above import, report and exit. 54 | // - Remove import from current position 55 | // - Place it right before childNode.start 56 | // - The fix will place it right after the last valid import node 57 | if (!nodesAllowedAbove.includes(childNode.type)) { 58 | return context.report({ 59 | node, 60 | fix(fixer) { 61 | // Add the import statement after the last valid node and remove the import statement 62 | const sourceCode = context.getSourceCode(); 63 | const importStatement = sourceCode.getText(node); 64 | let suffix; 65 | 66 | // If the last valid node is import, we place the current import right below it. 67 | // If its a pragma directive (whether solidity or experimental), add current import after 3 EOLs 68 | if (lastValidNode.type === "ImportStatement") { 69 | suffix = eol; 70 | } else if (nodesAllowedAbove.includes(lastValidNode.type)) { 71 | // Because we've already explicitly checks for import st. above, this one basically 72 | // checks for the remaining, ie, pragma directives 73 | suffix = eol.repeat(3); 74 | } else { 75 | // If lastValidNode is not an allowed one, it means we have to insert the import ABOVE the lvn 76 | return [fixer.insertTextBefore(lastValidNode, importStatement + eol), fixer.remove(node)]; 77 | } 78 | 79 | return [fixer.insertTextAfter(lastValidNode, suffix + importStatement), fixer.remove(node)]; 80 | }, 81 | message: "Import Statement must precede everything except pragma directives." 82 | }); 83 | } else { 84 | // All nodes allowed above an import statement are considered valid nodes 85 | lastValidNode = childNode; 86 | } 87 | } 88 | } 89 | 90 | return { 91 | ImportStatement: inspectImportStatement 92 | }; 93 | 94 | } 95 | 96 | }; 97 | -------------------------------------------------------------------------------- /lib/rules/linebreak-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure consistent linebreak style across codebase 3 | * @author Arjun Nemani 4 | */ 5 | 6 | "use strict"; 7 | 8 | const eol = require("eol"); 9 | 10 | module.exports = { 11 | meta: { 12 | docs: { 13 | recommended: true, 14 | type: "error", 15 | description: "Ensure consistent linebreak style across codebase" 16 | }, 17 | schema: [{ 18 | type: "string", 19 | enum: ["unix", "windows"] 20 | }], 21 | fixable: "whitespace" 22 | }, 23 | 24 | create(context) { 25 | const sourceCode = context.getSourceCode(); 26 | let convertFn = eol.lf.bind(eol); 27 | 28 | if (context.options && context.options[0] === "windows") { 29 | convertFn = eol.crlf.bind(eol); 30 | } 31 | 32 | function inspectProgram(emitted) { 33 | const { node } = emitted; 34 | const txt = sourceCode.getText(); 35 | const convertedTxt = convertFn(txt); 36 | 37 | if (emitted.exit || convertedTxt === txt) { 38 | return; 39 | } 40 | 41 | // TODO: Report the exact row and column position at which the linebreak 42 | // violation occured. Still need to investigate the best way to calculate 43 | // the positions where LB is different from the expected. 44 | context.report({ 45 | node, 46 | fix(fixer) { 47 | return fixer.replaceTextRange([0, txt.length], convertedTxt); 48 | }, 49 | message: "Inconsistent line-break style" 50 | }); 51 | } 52 | 53 | return { 54 | Program: inspectProgram 55 | }; 56 | } 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /lib/rules/max-len.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that a line of code doesn't exceed the specified number of characters. 3 | * @author Leo Arias 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure that a line of code doesn't exceed the specified number of characters" 16 | }, 17 | 18 | schema: [{ 19 | type: "integer", minimum: 1 20 | }] 21 | }, 22 | 23 | create(context) { 24 | const maxLineLength = context.options ? context.options [0] : 145; 25 | const sourceCode = context.getSourceCode(); 26 | 27 | function inspectProgram(emitted) { 28 | if (emitted.exit) { 29 | return; 30 | } 31 | 32 | let lines = sourceCode.text.split(/\r?\n/), 33 | lastLine = -1; 34 | 35 | // Recursive function. 36 | // --elopio - 20180421 37 | function checkNodes(nodes) { 38 | if (!Array.isArray(nodes)) { 39 | nodes = [nodes]; 40 | } 41 | nodes.forEach(node => { 42 | let lineNumber = sourceCode.getLine(node) - 1; 43 | 44 | if (lineNumber > lastLine && lines[lineNumber].length > maxLineLength) { 45 | context.report({ 46 | node, 47 | message: `Line exceeds the limit of ${maxLineLength} characters` 48 | }); 49 | 50 | lastLine = lineNumber; 51 | } 52 | 53 | checkNodes(node.body || []); 54 | }); 55 | } 56 | 57 | checkNodes(emitted.node.body); 58 | } 59 | 60 | return { 61 | Program: inspectProgram 62 | }; 63 | 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /lib/rules/mixedcase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that variable, function, parameter and declerative expression names follow mixedCase notation 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure that all variable, function and parameter names follow the mixedCase naming convention" 16 | }, 17 | 18 | schema: [] 19 | 20 | }, 21 | 22 | create: function(context) { 23 | 24 | let mixedCaseRegEx = /^_?[a-z][a-zA-Z0-9]*_?$/; 25 | let similarNodes = [ 26 | "FunctionDeclaration", 27 | "ModifierDeclaration" 28 | ]; 29 | 30 | function report(node, name) { 31 | context.report({ 32 | node: node, 33 | message: "'" + name + "' doesn't follow the mixedCase notation" 34 | }); 35 | } 36 | 37 | function inspectFuncOrModif(emitted) { 38 | let node = emitted.node; 39 | 40 | /** 41 | * If node's parent is contract / library and node is either a modifier (which means Inheritance), 42 | * do not apply mixedcase 43 | */ 44 | if (emitted.exit || 45 | ((node.parent.type === "ContractStatement" || node.parent.type === "LibraryStatement") && 46 | (node.type === "FunctionDeclaration" && node.parent.name === node.name)) 47 | ) { 48 | return; 49 | } 50 | 51 | if (!mixedCaseRegEx.test(node.name)) { 52 | report(node, node.name); 53 | } 54 | } 55 | 56 | function inspectVariableDeclarator(emitted) { 57 | let node = emitted.node; 58 | 59 | if (emitted.exit) { 60 | return; 61 | } 62 | 63 | if (!mixedCaseRegEx.test(node.id.name)) { 64 | context.report({ 65 | node: node, 66 | message: "Identifier name '" + node.id.name + "' doesn't follow the mixedCase notation" 67 | }); 68 | } 69 | } 70 | 71 | function inspectDeclaration(emitted) { 72 | let node = emitted.node; 73 | 74 | if (emitted.exit) { 75 | return; 76 | } 77 | 78 | if (!node.is_constant && !mixedCaseRegEx.test(node.name)) { 79 | report(node, node.name); 80 | } 81 | } 82 | 83 | function inspectInformalParameter(emitted) { 84 | let node = emitted.node; 85 | 86 | if (emitted.exit) { 87 | return; 88 | } 89 | 90 | /** 91 | * node.is could either be an object containing "name" or null. 92 | * It is null when there is no name (eg- `function foo() returns(bool) {}`) 93 | * Here, bool's node will have "literal" object but "id" as null. 94 | */ 95 | if (node.id && !mixedCaseRegEx.test(node.id)) { 96 | report(node, node.id); 97 | } 98 | } 99 | 100 | 101 | let response = { 102 | InformalParameter: inspectInformalParameter, 103 | StateVariableDeclaration: inspectDeclaration, 104 | DeclarativeExpression: inspectDeclaration, 105 | VariableDeclarator: inspectVariableDeclarator 106 | }; 107 | 108 | similarNodes.forEach(function(nodeName) { 109 | response [nodeName] = inspectFuncOrModif; 110 | }); 111 | 112 | return response; 113 | 114 | } 115 | 116 | }; 117 | -------------------------------------------------------------------------------- /lib/rules/no-constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Use view over deprecated constant in function declarations 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | function create(context) { 10 | function reportIfConstant(emitted) { 11 | const { node } = emitted, enclosingFunction = context.getSourceCode().getParent(node); 12 | 13 | if (emitted.exit || node.name !== "constant" || enclosingFunction.type !== "FunctionDeclaration") { 14 | return; 15 | } 16 | 17 | // Only report this modifier if it is 'constant' and is being used in a function declaration. 18 | context.report({ 19 | node, 20 | fix(fixer) { 21 | return fixer.replaceText(node, "view"); 22 | }, 23 | message: "Use 'view' instead of deprecated 'constant'." 24 | }); 25 | } 26 | 27 | return { 28 | ModifierArgument: reportIfConstant 29 | }; 30 | } 31 | 32 | 33 | module.exports = { 34 | 35 | meta: { 36 | docs: { 37 | recommended: true, 38 | type: "warning", 39 | description: "Use view over deprecated constant in function declarations" 40 | }, 41 | schema: [], 42 | fixable: "code" 43 | }, 44 | 45 | create 46 | 47 | }; -------------------------------------------------------------------------------- /lib/rules/no-experimental.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that experimental features are not used in production 3 | * @author Ivan Mushketyk 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure that experimental features are not used in production" 16 | }, 17 | 18 | schema: [] 19 | }, 20 | 21 | create(context) { 22 | 23 | function reportNode(emitted) { 24 | if (emitted.exit) { 25 | return; 26 | } 27 | 28 | return context.report({ 29 | node: emitted.node, 30 | message: "Avoid using experimental features in production code" 31 | }); 32 | } 33 | 34 | return { 35 | ExperimentalPragmaStatement: reportNode 36 | }; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /lib/rules/no-trailing-whitespace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Disallow trailing spaces and tabs at the end of lines 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | function isBlankLine(text) { 10 | return /^[ \t]*$/.test(text); 11 | } 12 | 13 | function isLineInsideAComment(line, lineNumber, commentNodes, sourceCode) { 14 | for (let i = 0; i < commentNodes.length; i++) { 15 | const c = commentNodes[i]; 16 | 17 | switch (c.type) { 18 | case "Line": 19 | if (lineNumber === sourceCode.getLine(c)) { 20 | return true; 21 | } 22 | break; 23 | 24 | case "Block": 25 | if (lineNumber >= sourceCode.getLine(c) && lineNumber < sourceCode.getEndingLine(c)) { 26 | return true; 27 | } 28 | break; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | 35 | 36 | function create(context) { 37 | // For each line L of source code 38 | // If L is blank and skipBlankLines is set, skip 39 | // If L is a comment and ignoreComments is set, skip 40 | // Else report line only if it contains trailing whitespace 41 | function reportLinesWithTrailingWhitespace(emitted) { 42 | if (emitted.exit) { 43 | return; 44 | } 45 | 46 | // Call getSourceCode() inside the handler function. 47 | // This is because comments array is not populated before 48 | // a rule's create() is called. 49 | // So if SourceCode object is obtained directly inside create(), 50 | // comments array will always be empty. 51 | const sourceCode = context.getSourceCode(); 52 | 53 | const { node } = emitted, 54 | codeLines = sourceCode.getLines(), 55 | comments = sourceCode.getComments(); 56 | 57 | const options = (context.options && context.options[0]) || {}, 58 | skipBlankLines = options.skipBlankLines || false, 59 | ignoreComments = options.ignoreComments || false; 60 | 61 | codeLines.forEach((line, i) => { 62 | if ( 63 | (skipBlankLines && isBlankLine(line)) || 64 | (ignoreComments && isLineInsideAComment(line, i+1, comments, sourceCode)) 65 | ) { 66 | return; 67 | } 68 | 69 | const clean = line.trimRight(); 70 | 71 | // TODO: Add fix capability 72 | // This requires us to know the start index of the code 73 | // on the flagged line. This index will be used to supply 74 | // a range that will be replaced with the fixed code. 75 | const issue = { 76 | node, 77 | location: { 78 | line: i+1, 79 | column: line.length - clean.length 80 | }, 81 | message: "Line contains trailing whitespace" 82 | }; 83 | 84 | if (line !== clean) { 85 | context.report(issue); 86 | } 87 | }); 88 | } 89 | 90 | return { 91 | Program: reportLinesWithTrailingWhitespace 92 | }; 93 | } 94 | 95 | 96 | module.exports = { 97 | 98 | meta: { 99 | docs: { 100 | recommended: true, 101 | type: "warning", 102 | description: "Disallow trailing spaces and tabs at the end of lines" 103 | }, 104 | 105 | schema: [{ 106 | type: "object", 107 | properties: { 108 | skipBlankLines: { type: "boolean" }, 109 | ignoreComments: { type: "boolean" } 110 | }, 111 | additionalProperties: false 112 | }] 113 | }, 114 | 115 | create 116 | }; -------------------------------------------------------------------------------- /lib/rules/no-unused-vars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Flag all the variables that were declared but never used 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "error", 15 | description: "Flag all the variables that were declared but never used" 16 | }, 17 | 18 | schema: [] 19 | 20 | }, 21 | 22 | create: function(context) { 23 | 24 | let allVariableDeclarations = {}; 25 | 26 | //collect all variable declarations from VariableDeclarators and DeclarativeExpressions 27 | function inspectVariableDeclarator(emitted) { 28 | let node = emitted.node; 29 | 30 | if (!emitted.exit) { 31 | allVariableDeclarations [node.id.name] = node; 32 | } 33 | } 34 | 35 | function inspectDeclarativeExpression(emitted) { 36 | let node = emitted.node; 37 | 38 | //do not examine if the declaration is part of a Struct definition 39 | if (!emitted.exit && node.parent.type !== "StructDeclaration") { 40 | allVariableDeclarations [node.name] = node; 41 | } 42 | } 43 | 44 | //While exiting Progam Node, all the vars that haven't been used still exist inside VariableDeclarations. Report them 45 | function inspectProgram(emitted) { 46 | 47 | if (emitted.exit) { 48 | Object.keys(allVariableDeclarations).forEach(function(name) { 49 | context.report({ 50 | node: allVariableDeclarations [name], 51 | message: "Variable '" + name + "' is declared but never used." 52 | }); 53 | }); 54 | } 55 | 56 | } 57 | 58 | //As soon as the first use of a variable is encountered, delete that variable's node from allVariableDeclarations 59 | function inspectIdentifier(emitted) { 60 | if (!emitted.exit) { 61 | let node = emitted.node, 62 | sourceCode = context.getSourceCode(); 63 | 64 | if ( 65 | allVariableDeclarations [node.name] && 66 | sourceCode.getParent(node).type !== "VariableDeclarator" 67 | ) { 68 | delete allVariableDeclarations [node.name]; 69 | } 70 | } 71 | } 72 | 73 | 74 | return { 75 | Identifier: inspectIdentifier, 76 | Program: inspectProgram, 77 | DeclarativeExpression: inspectDeclarativeExpression, 78 | VariableDeclarator: inspectVariableDeclarator 79 | }; 80 | 81 | } 82 | 83 | }; 84 | -------------------------------------------------------------------------------- /lib/rules/no-with.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure no use of with statements in the code 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure no use of with statements in the code" 16 | }, 17 | 18 | deprecated: true, 19 | schema: [] 20 | 21 | }, 22 | 23 | create: function(context) { 24 | 25 | function inspectWithStatement(emitted) { 26 | if (emitted.exit) { 27 | return; 28 | } 29 | 30 | context.report({ 31 | node: emitted.node, 32 | message: "Use of 'with' statement" 33 | }); 34 | } 35 | 36 | return { 37 | WithStatement: inspectWithStatement 38 | }; 39 | 40 | } 41 | 42 | }; -------------------------------------------------------------------------------- /lib/rules/pragma-on-top.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure a) A PRAGMA directive exists and b) its on top of the file 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | const { EOL } = require("os"); 9 | 10 | module.exports = { 11 | 12 | meta: { 13 | 14 | docs: { 15 | recommended: true, 16 | type: "warning", 17 | description: "Ensure a) A PRAGMA directive exists and b) its on top of the file" 18 | }, 19 | 20 | schema: [], 21 | 22 | fixable: "code" 23 | 24 | }, 25 | 26 | create: function(context) { 27 | 28 | let missingNodeOnTopErrorReported = false; 29 | 30 | /** 31 | * This executes only when we're leaving the Program node. At that time, 32 | * we check whether the "missing pragma on top" error has already been reported or not. 33 | * If not, we proceed to report it here. This happens when there are no pragma statements in 34 | * the entire file. If there is one (but not on top of file), it gets reported by inspectPragmaStatement(). 35 | * NOTE: A Pragma dir must exist at absolute top, even before pragma experimental. 36 | */ 37 | function inspectProgram(emitted) { 38 | let node = emitted.node, body = node.body; 39 | 40 | if (!emitted.exit || missingNodeOnTopErrorReported) { 41 | return; 42 | } 43 | 44 | (body.length > 0) && (body [0].type !== "PragmaStatement") && context.report({ 45 | node: node, 46 | message: "No Pragma directive found at the top of file." 47 | }); 48 | } 49 | 50 | 51 | function inspectPragmaStatement(emitted) { 52 | let node = emitted.node, 53 | sourceCode = context.getSourceCode(), pragmaParent = sourceCode.getParent(node); 54 | 55 | // If pragma statement is on top, exit now. No further checks required. 56 | if (emitted.exit || node.start === pragmaParent.body [0].start) { 57 | return; 58 | } 59 | 60 | const pragmaCode = sourceCode.getText(node); 61 | 62 | context.report({ 63 | node: node, 64 | message: `"${pragmaCode}" should be at the top of the file.`, 65 | fix: function(fixer) { 66 | return [fixer.remove(node), 67 | fixer.insertTextBefore(pragmaParent.body [0], `${pragmaCode}${EOL}`)]; 68 | } 69 | }); 70 | 71 | missingNodeOnTopErrorReported = true; 72 | } 73 | 74 | 75 | // Experimental pragmas, if they exist, must be above everything EXCEPT pragma solidity & other experimental pragmas. 76 | function inspectExperimentalPragmaStatement(emitted) { 77 | if (emitted.exit) { 78 | return; 79 | } 80 | 81 | const { node } = emitted, 82 | nodesAllowedAbove = ["ExperimentalPragmaStatement", "PragmaStatement"], 83 | programNode = context.getSourceCode().getParent(node); 84 | 85 | for (let childNode of programNode.body) { 86 | // If we've reached this exp. pragma while traversing body, it means its position is fine. 87 | if (node.start === childNode.start) { 88 | return; 89 | } 90 | 91 | // We found the first node not allowed above experimental pragma, report and exit. 92 | const pragmaCode = context.getSourceCode().getText(node); 93 | 94 | if (nodesAllowedAbove.indexOf(childNode.type) < 0) { 95 | const errObject = { 96 | node, 97 | fix(fixer) { 98 | return [fixer.remove(node), 99 | fixer.insertTextBefore(childNode, `${pragmaCode}${EOL}`)]; 100 | }, 101 | message: "Experimental Pragma must precede everything except Solidity Pragma." 102 | }; 103 | 104 | return context.report(errObject); 105 | } 106 | } 107 | } 108 | 109 | return { 110 | Program: inspectProgram, 111 | PragmaStatement: inspectPragmaStatement, 112 | ExperimentalPragmaStatement: inspectExperimentalPragmaStatement 113 | }; 114 | 115 | } 116 | 117 | }; 118 | -------------------------------------------------------------------------------- /lib/rules/quotes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that all strings use only 1 style - either double quotes or single quotes. Defaults to double quotes. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | const jsStringEscape = require("js-string-escape"); 9 | 10 | /** 11 | * Determine whether the provided literal is in Hex Notation 12 | * @param {String} literal The literal to test for Hex Notation 13 | * @returns {Boolean} 14 | */ 15 | function isHex(literal) { 16 | const reg = /^[0-9a-f]+$/i; 17 | 18 | //test for '0x' separately because hex notation should not be a part of the standard RegExp 19 | if (literal.slice(0, 2) !== "0x") { 20 | return false; 21 | } 22 | 23 | return reg.test(literal.slice(2)); 24 | } 25 | 26 | module.exports = { 27 | 28 | meta: { 29 | 30 | docs: { 31 | recommended: true, 32 | type: "error", 33 | description: "Ensure that all strings use only 1 style - either double quotes or single quotes." 34 | }, 35 | 36 | fixable: "code", 37 | 38 | schema: [{ 39 | type: "string", 40 | enum: ["double", "single"] 41 | }] 42 | 43 | }, 44 | 45 | create(context) { 46 | let quote = "\"", 47 | quoteStyle = "double", sourceCode = context.getSourceCode(); 48 | 49 | if (context.options && context.options [0] === "single") { 50 | quote = "'"; 51 | quoteStyle = "single"; 52 | } 53 | 54 | const selectedQuoteStyleLiteralRegExp = new RegExp("^(\\(\\s*)?\\" + quote + ".*\\" + quote + "(\\s*\\))?$"); 55 | 56 | 57 | function inspectLiteral(emitted) { 58 | const node = emitted.node, nodeText = sourceCode.getText(node); 59 | 60 | if (emitted.exit || typeof node.value !== "string" || 61 | (nodeText [0] !== "'" && nodeText [0] !== "\"" && isHex(node.value))) { 62 | return; 63 | } 64 | 65 | if (!selectedQuoteStyleLiteralRegExp.test(nodeText)) { 66 | const errorObject = { 67 | node, 68 | fix(fixer) { 69 | const fixedString = quote + jsStringEscape(node.value) + quote, 70 | currentQuote = (quote === "'" ? "\"" : "'"); 71 | const openingQuoteI = nodeText.indexOf(currentQuote), 72 | closingQuoteI = nodeText.lastIndexOf(currentQuote); 73 | const fixedNodeText = nodeText.slice(0, openingQuoteI) + fixedString + nodeText.slice(closingQuoteI+1); 74 | 75 | return fixer.replaceText(node, fixedNodeText); 76 | }, 77 | message: `String literal must be quoted with ${quoteStyle} quotes.` 78 | }; 79 | 80 | context.report(errorObject); 81 | } 82 | } 83 | 84 | 85 | function inspectImportStatement(emitted) { 86 | const { node } = emitted, 87 | nodeText = sourceCode.getText(node), expectedString = `${quote}${node.from}${quote}`; 88 | 89 | // If using the expected quote style, exit now. 90 | if (emitted.exit || nodeText.indexOf(expectedString) > -1) { 91 | return; 92 | } 93 | 94 | context.report({ 95 | node, 96 | fix(fixer) { 97 | // TODO: escape string having quotes inside them, ie, use jsStringEscape() 98 | 99 | const start = nodeText.indexOf(node.from) - 1, // index of opening quote 100 | end = start + 1 + node.from.length; // index of closing quote 101 | let f = `${nodeText.slice(0, start)}${quote}${node.from}${quote}${nodeText.slice(end+1)}`; 102 | 103 | return fixer.replaceText(node, f); 104 | }, 105 | location: { 106 | column: sourceCode.getColumn(node) + nodeText.indexOf(node.from) - 1 107 | }, 108 | message: `"${node.from}": Import statements must use ${quoteStyle} quotes only.` 109 | }); 110 | 111 | } 112 | 113 | 114 | return { 115 | Literal: inspectLiteral, 116 | ImportStatement: inspectImportStatement 117 | }; 118 | } 119 | 120 | }; 121 | -------------------------------------------------------------------------------- /lib/rules/semicolon-whitespace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that there is no whitespace or comments before semicolons 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure that there is no whitespace or comments before semicolons" 16 | }, 17 | 18 | schema: [] 19 | 20 | }, 21 | 22 | create: function(context) { 23 | 24 | let sourceCode = context.getSourceCode(); 25 | 26 | function inspectExpressionStatement(emitted) { 27 | let node = emitted.node; 28 | 29 | if (emitted.exit) { 30 | return; 31 | } 32 | 33 | let code = sourceCode.getText(node); 34 | 35 | //ensure there's no whitespace or comments before semicolon 36 | (code [code.length - 1] === ";" && /(\s|\/)/.test(code [code.length - 2])) && context.report({ 37 | node: node, 38 | location: { 39 | column: code.length - 2 40 | }, 41 | message: "There should be no whitespace or comments before the semicolon." 42 | }); 43 | } 44 | 45 | 46 | function inspectUsingStatement(emitted) { 47 | let node = emitted.node; 48 | 49 | if (emitted.exit) { 50 | return; 51 | } 52 | 53 | let code = sourceCode.getText(node); 54 | 55 | //ensure there's no whitespace or comments before semicolon 56 | (code [code.length - 1] === ";" && /(\s|\/)/.test(code [code.length - 2])) && context.report({ 57 | node: node, 58 | location: { 59 | column: code.length - 2 60 | }, 61 | message: "There should be no whitespace or comments before the semicolon." 62 | }); 63 | } 64 | 65 | function inspectVariableDeclaration(emitted) { 66 | let node = emitted.node, code = sourceCode.getText(node); 67 | 68 | if (emitted.exit) { 69 | return; 70 | } 71 | 72 | //ensure there's no whitespace or comments before semicolon 73 | (code [code.length - 1] === ";" && /(\s|\/)/.test(code [code.length - 2])) && context.report({ 74 | node: node, 75 | location: { 76 | column: code.length - 2 77 | }, 78 | message: "There should be no whitespace or comments before the semicolon." 79 | }); 80 | } 81 | 82 | //If we're dealing with abstract function declaration, we need to ensure no whitespce or comments before semicolon 83 | function inspectFunctionDeclaration(emitted) { 84 | let node = emitted.node; 85 | 86 | if (emitted.exit) { 87 | return; 88 | } 89 | 90 | let code = sourceCode.getText(node); 91 | 92 | (node.is_abstract && code [code.length - 1] === ";" && /(\s|\/)/.test(code [code.length - 2])) && 93 | context.report({ 94 | node: node, 95 | location: { 96 | column: code.length - 2 97 | }, 98 | message: "There should be no whitespace or comments before the semicolon." 99 | }); 100 | } 101 | 102 | function inspectImportStatement(emitted) { 103 | let node = emitted.node; 104 | 105 | if (emitted.exit) { 106 | return; 107 | } 108 | 109 | let code = sourceCode.getText(node); 110 | 111 | //ensure there's no whitespace or comments before semicolon 112 | (code [code.length - 1] === ";" && /(\s|\/)/.test(code [code.length - 2])) && context.report({ 113 | node: node, 114 | location: { 115 | column: code.length - 2 116 | }, 117 | message: "There should be no whitespace or comments before the semicolon." 118 | }); 119 | } 120 | 121 | return { 122 | ImportStatement: inspectImportStatement, 123 | FunctionDeclaration: inspectFunctionDeclaration, 124 | VariableDeclaration: inspectVariableDeclaration, 125 | UsingStatement: inspectUsingStatement, 126 | ExpressionStatement: inspectExpressionStatement 127 | }; 128 | 129 | } 130 | 131 | }; 132 | -------------------------------------------------------------------------------- /lib/rules/uppercase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that all constants (and only constants) contain only upper case letters and underscore 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "warning", 15 | description: "Ensure that all constants (and only constants) contain only upper case letters and underscore" 16 | }, 17 | 18 | schema: [] 19 | 20 | }, 21 | 22 | create(context) { 23 | 24 | let upperCaseRegEx = /^_{0,2}[A-Z]([A-Z_0-9]*[A-Z0-9])?_{0,2}$/; 25 | 26 | function reportNode(node) { 27 | context.report({ 28 | node: node, 29 | message: `"${node.name}" doesn't follow the UPPER_CASE notation` 30 | }); 31 | } 32 | 33 | function inspectStateVariableDeclaration(emitted) { 34 | let node = emitted.node; 35 | 36 | if (emitted.exit) { 37 | return; 38 | } 39 | 40 | node.is_constant && !upperCaseRegEx.test(node.name) && reportNode(node); 41 | } 42 | 43 | return { 44 | StateVariableDeclaration: inspectStateVariableDeclaration 45 | }; 46 | 47 | } 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /lib/rules/value-in-payable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure "msg.value" is only used in functions with the "payable" modifier 3 | * @author Ivan Mushketyk 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "error", 15 | description: "Ensure 'msg.value' is only used in functions with the 'payable' modifier" 16 | }, 17 | 18 | schema: [] 19 | }, 20 | 21 | create(context) { 22 | 23 | let currentFunctionDeclaration = null; 24 | 25 | function inspectFunctionDeclaration(emitted) { 26 | if (emitted.exit) { 27 | currentFunctionDeclaration = null; 28 | return; 29 | } 30 | 31 | currentFunctionDeclaration = emitted.node; 32 | 33 | } 34 | 35 | function inspectMemberExpression(emitted) { 36 | if (emitted.exit) { 37 | return; 38 | } 39 | 40 | const { node } = emitted; 41 | 42 | if (_isMsgValueAccess(node) 43 | && _isInFunction() 44 | && !_isPrivateFunction() 45 | && !_isInPayableFunction()) { 46 | 47 | context.report({ 48 | node: emitted.node, 49 | message: `Function '${currentFunctionDeclaration.name}' must be declared payable if using 'msg.value'` 50 | }); 51 | } 52 | } 53 | 54 | function _isMsgValueAccess(node) { 55 | return node.object.name === "msg" && node.property.name === "value"; 56 | } 57 | 58 | function _isInFunction() { 59 | return currentFunctionDeclaration !== null; 60 | } 61 | 62 | function _isPrivateFunction() { 63 | return ( 64 | _containsModifier(currentFunctionDeclaration, "private") || 65 | _containsModifier(currentFunctionDeclaration, "internal") 66 | ); 67 | } 68 | 69 | function _isInPayableFunction() { 70 | return _containsModifier(currentFunctionDeclaration, "payable"); 71 | } 72 | 73 | function _containsModifier(functionDeclaration, modifierName) { 74 | return functionDeclaration.modifiers.map(modifier => { 75 | return modifier.name; 76 | }).includes(modifierName); 77 | } 78 | 79 | return { 80 | FunctionDeclaration: inspectFunctionDeclaration, 81 | MemberExpression: inspectMemberExpression 82 | }; 83 | 84 | } 85 | 86 | }; 87 | -------------------------------------------------------------------------------- /lib/rules/variable-declarations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that names 'l', 'O' & 'I' are not used for variables 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | meta: { 11 | 12 | docs: { 13 | recommended: true, 14 | type: "error", 15 | description: "Ensure that names \"l\", \"O\" & \"I\" are not used for variables" 16 | }, 17 | 18 | schema: [{ 19 | type: "array", 20 | items: { type: "string", minLength: 1 }, 21 | minItems: 1 22 | }] 23 | 24 | }, 25 | 26 | create: function(context) { 27 | 28 | let disallowedNames = context.options ? context.options [0] : ["l", "O", "I"]; 29 | 30 | function inspectVariableDeclarator(emitted) { 31 | let node = emitted.node, variableName = node.id.name; 32 | 33 | if (emitted.exit) { 34 | return; 35 | } 36 | 37 | disallowedNames.forEach(function(disallowedName) { 38 | if (variableName === disallowedName) { 39 | context.report({ 40 | node: node, 41 | message: "Using \"" + variableName + "\" for a variable name should be avoided." 42 | }); 43 | } 44 | }); 45 | } 46 | 47 | function inspectDeclarativeExpression(emitted) { 48 | let node = emitted.node, variableName = node.name; 49 | 50 | if (emitted.exit) { 51 | return; 52 | } 53 | 54 | disallowedNames.forEach(function(disallowedName) { 55 | if (variableName === disallowedName) { 56 | context.report({ 57 | node: node, 58 | message: "Using '" + variableName + "' for a variable name should be avoided." 59 | }); 60 | } 61 | }); 62 | } 63 | 64 | // Aliased because both the inspect functions access the same property "name" of the node passed to them. 65 | // So no need for 2 separate functions for now (will create separate when different attrs are accessed in future) 66 | let inspectStateVariableDeclaration = inspectDeclarativeExpression; 67 | 68 | return { 69 | VariableDeclarator: inspectVariableDeclarator, 70 | DeclarativeExpression: inspectDeclarativeExpression, 71 | StateVariableDeclaration: inspectStateVariableDeclaration 72 | }; 73 | 74 | } 75 | 76 | }; 77 | -------------------------------------------------------------------------------- /lib/rules/visibility-first.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that the visibility modifier for a function should come before any custom modifiers. 3 | * @author Harrison Beckerich 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | meta: { 10 | docs: { 11 | recommended: true, 12 | type: "warning", 13 | description: "Ensure that the visibility modifier for a function should come before any custom modifiers" 14 | }, 15 | 16 | schema: [] 17 | }, 18 | 19 | create(context) { 20 | // Find index of the first visibility modifier in declaration. 21 | // Find the first non-VM before this first VM found above. 22 | // If non-VM found, report the VM. 23 | function inspectFD(emitted) { 24 | const { node } = emitted, 25 | visibilityModifiers = ["public", "external", "internal", "private"]; 26 | const modifiers = (node.modifiers || []), 27 | firstVisibilityModifierIndex = modifiers.findIndex(m => visibilityModifiers.includes(m.name)); 28 | 29 | // If no visibility modifiers exist in function declaration, exit now 30 | if (emitted.exit || firstVisibilityModifierIndex === -1) { 31 | return; 32 | } 33 | 34 | const firstNonVisModifBeforeFirstVisModif = modifiers.slice(0, firstVisibilityModifierIndex).find(m => !visibilityModifiers.includes(m.name)); 35 | 36 | // TODO: Add fix() for this rule 37 | if (firstNonVisModifBeforeFirstVisModif) { 38 | const issue = { 39 | node: modifiers[firstVisibilityModifierIndex], 40 | message: `Visibility modifier "${modifiers[firstVisibilityModifierIndex].name}" should come before other modifiers.` 41 | }; 42 | context.report(issue); 43 | } 44 | } 45 | 46 | return { 47 | FunctionDeclaration: inspectFD 48 | }; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /lib/utils/config-inspector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions to examine the state of a configuration object obtained from soliumrc. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let Ajv = require("ajv"), 9 | currentConfigSchema = require("../../config/schemas/config"), 10 | deprecatedConfigSchema = require("../../config/schemas/deprecated/config-v0.5.5"), 11 | sharableConfigSchema = require("../../config/schemas/sharable-config"); 12 | 13 | let SchemaValidator = new Ajv({ allErrors: true }), 14 | validateCurrentConfig = SchemaValidator.compile(currentConfigSchema), 15 | validateDepConfig = SchemaValidator.compile(deprecatedConfigSchema), 16 | validateSharableConfig = SchemaValidator.compile(sharableConfigSchema); 17 | 18 | 19 | module.exports = { 20 | 21 | /** 22 | * Determine whether a valid configuration object was passed by user. 23 | * @param {Object} config The config object to examine. 24 | * @returns {Boolean} decision True if config is valid, false otherwise. 25 | */ 26 | isValid: function(config) { 27 | return (validateCurrentConfig(config) || validateDepConfig(config)); 28 | }, 29 | 30 | /** 31 | * Determine whether the configuration object passed by user is a current one or a deprecated one. 32 | * @param {Object} config The config object to examine. 33 | * @returns {Boolean} decision True if config is deprecated, false otherwise. 34 | */ 35 | isFormatDeprecated: function(config) { 36 | // This function assumes that a VALID config object is passed, ie, the object has passed isValid() test above. 37 | return !validateCurrentConfig(config); 38 | }, 39 | 40 | /** 41 | * Determine whether the supplied config is a valid Sharable Config 42 | * @param {Object} config The config object to examine. 43 | * @returns {Boolean} decision True if config is valid, false otherwise. 44 | */ 45 | isAValidSharableConfig: validateSharableConfig 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /lib/utils/fs-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for working with files 3 | * @author Federico Bond 4 | */ 5 | 6 | "use strict"; 7 | 8 | const fs = require("fs"); 9 | 10 | module.exports = { 11 | 12 | isDirectory(path) { 13 | try { 14 | return fs.statSync(path).isDirectory(); 15 | } catch (e) { 16 | if (e.code === "ENOENT") { 17 | return false; 18 | } 19 | throw e; 20 | } 21 | }, 22 | 23 | isFile(path) { 24 | try { 25 | return fs.statSync(path).isFile(); 26 | } catch (e) { 27 | if (e.code === "ENOENT") { 28 | return false; 29 | } 30 | throw e; 31 | } 32 | } 33 | 34 | }; 35 | -------------------------------------------------------------------------------- /lib/utils/js-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for rest of teh code base 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | 10 | /** 11 | * Check if given argument is a non-null, non-Array javascript object 12 | * @param {Object} possibleObject Argument to check for validity 13 | * @returns {Boolean} isObject true if given argument is object, false otherwise 14 | */ 15 | isStrictlyObject: function(possibleObject) { 16 | return ( 17 | possibleObject !== null && //because typeof null equals 'object', make sure the object is non-null 18 | typeof possibleObject === "object" && 19 | possibleObject.constructor.name === "Object" 20 | ); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /lib/utils/node-event-generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Class definition of the object responsible for notifying the rules when a node they've subscribed to is entered or left 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** 9 | * EventGenerator object constructor 10 | * @param {Object} emitter EventEmitter object 11 | */ 12 | function EventGenerator(emitter) { 13 | this.emitter = emitter; 14 | 15 | // Increase limit on number of listeners to allow more rules to subscribe peacefully 16 | // This limit may further be increased in future 17 | this.emitter.setMaxListeners(35); 18 | } 19 | 20 | EventGenerator.prototype = { 21 | 22 | constructor: EventGenerator, 23 | 24 | /** 25 | * emit event that the node with type = node.type is being entered 26 | * @param {Object} node The AST node being entered 27 | */ 28 | enterNode(node) { 29 | this.emitter.emit(node.type, { 30 | node, 31 | exit: false 32 | }); 33 | }, 34 | 35 | /** 36 | * emit event that the node with type = node.type is being left 37 | * @param {Object} node The AST node being left 38 | */ 39 | leaveNode(node) { 40 | this.emitter.emit(node.type, { 41 | node, 42 | exit: true 43 | }); 44 | } 45 | 46 | }; 47 | 48 | 49 | module.exports = EventGenerator; 50 | -------------------------------------------------------------------------------- /lib/utils/rule-inspector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for examining a rule object 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let coreRuleSchema = require("../../config/schemas/core-rule"), 9 | coreRuleResponseSchema = require("../../config/schemas/core-rule-response"); 10 | 11 | let SchemaValidator = coreRuleSchema.SchemaValidator, 12 | validateCoreRule = SchemaValidator.compile(coreRuleSchema.Schema), 13 | validateCoreRuleResponse = SchemaValidator.compile(coreRuleResponseSchema.Schema); 14 | 15 | module.exports = { 16 | 17 | /** 18 | * Determine whether the supplied argument qualifies as a core rule object 19 | * @param {Object} ruleObject The object to validate 20 | * @returns {Boolean} isValid True if object is a valid core solium rule, false otherwise. 21 | */ 22 | isAValidRuleObject: validateCoreRule, 23 | 24 | /** 25 | * Determine whether the response given by a rule's create() method is a set of valid 26 | * node names (event listeners) with their corresponding handler functions. 27 | * @param {Object} ruleResponseObject Object to validate 28 | * @returns {Boolean} isValid True if object is a valid rule response, false otherwise. 29 | */ 30 | isAValidRuleResponseObject: validateCoreRuleResponse, 31 | 32 | /** 33 | * Determine whether the options object supplied is valid according to the schema passed. 34 | * @param {Array} options List of options 35 | * @param {Array} listItemsSchema A list of schema objects defining schema for every item in the options list. 36 | * @returns {Boolean} isValid True if options list is valid, false otherwise. 37 | */ 38 | areValidOptionsPassed: function(options, listItemsSchema) { 39 | let validateOptionsList = SchemaValidator.compile({ 40 | type: "array", 41 | minItems: listItemsSchema.length, 42 | additionalItems: false, 43 | items: listItemsSchema 44 | }); 45 | 46 | return validateOptionsList(options); 47 | } 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethlint", 3 | "version": "1.2.5", 4 | "description": "Linter to identify and fix Style & Security issues in Solidity", 5 | "main": "./lib/solium.js", 6 | "bin": { 7 | "solium": "./bin/solium.js" 8 | }, 9 | "scripts": { 10 | "test": "nyc --reporter=text --reporter=html mocha --require should --reporter spec --recursive", 11 | "lint": "eslint lib/ test/ bin/ config/", 12 | "lint-fix": "eslint lib/ test/ bin/ config/ --fix" 13 | }, 14 | "keywords": [ 15 | "lint", 16 | "static-analysis", 17 | "solidity", 18 | "abstract-syntax-tree", 19 | "ethereum", 20 | "smart-contracts", 21 | "ethlint", 22 | "solium", 23 | "blockchain", 24 | "code-quality", 25 | "dapp", 26 | "developer-tools", 27 | "security", 28 | "soliumconfig", 29 | "soliumplugin" 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/duaraghav8/Ethlint.git" 34 | }, 35 | "author": "Raghav Dua ", 36 | "license": "MIT", 37 | "dependencies": { 38 | "ajv": "^5.2.2", 39 | "chokidar": "^1.6.0", 40 | "colors": "^1.1.2", 41 | "commander": "^2.9.0", 42 | "diff": "^3.5.0", 43 | "eol": "^0.9.1", 44 | "js-string-escape": "^1.0.1", 45 | "lodash": "^4.14.2", 46 | "sol-digger": "0.0.2", 47 | "sol-explore": "1.6.1", 48 | "solium-plugin-security": "0.1.1", 49 | "solparse": "2.2.8", 50 | "text-table": "^0.2.0" 51 | }, 52 | "devDependencies": { 53 | "chai": "^3.5.0", 54 | "eslint": "^4.13.0", 55 | "mocha": "^3.4.2", 56 | "nyc": "^11.2.1", 57 | "should": "^11.0.0", 58 | "solium-config-test": "0.0.1", 59 | "solium-config-test-invalid-schema": "0.0.0", 60 | "solium-config-test-invalid-syntax": "0.0.0", 61 | "solium-plugin-test": "0.1.5", 62 | "solium-plugin-test-invalid-schema": "0.0.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: solium 2 | version: git 3 | summary: A customizable, stand-alone linter for Ethereum Solidity 4 | description: | 5 | Solium is a linter for Solidity which uses Abstract Syntax Trees and allows 6 | the user to enable/disable existing rules and add their own ones! 7 | 8 | grade: stable 9 | confinement: strict 10 | 11 | apps: 12 | solium: 13 | command: solium 14 | plugs: [home, network] 15 | 16 | parts: 17 | solium: 18 | source: . 19 | plugin: nodejs 20 | -------------------------------------------------------------------------------- /test/config/rulesets/solium-all.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that all rule files present in the lib/rules/ directory have a corresponding entry in solium-all. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let fs = require("fs"), path = require("path"); 9 | let rsSoliumAll = require("../../../config/rulesets/solium-all"); 10 | let JS_EXT = ".js"; 11 | 12 | describe("Tests for solium-all.js ruleset", function() { 13 | 14 | it("should have a set of properties", function(done) { 15 | rsSoliumAll.should.be.type("object"); 16 | rsSoliumAll.should.have.ownProperty("rules"); 17 | rsSoliumAll.rules.should.be.type("object"); 18 | 19 | done(); 20 | }); 21 | 22 | it("should have an entry for every rule file in lib/rules directory", function(done) { 23 | let listOfRuleFiles = fs.readdirSync(__dirname + "/../../../lib/rules/"); 24 | 25 | listOfRuleFiles.forEach(function(filename) { 26 | if (path.extname(filename) === JS_EXT) { 27 | rsSoliumAll.rules.should.have.ownProperty(filename.slice(0, -JS_EXT.length)); 28 | } 29 | }); 30 | 31 | done(); 32 | }); 33 | 34 | }); -------------------------------------------------------------------------------- /test/config/rulesets/solium-recommended.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that all rule files present in the lib/rules/ directory have a corresponding entry in solium-recommended. 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let fs = require("fs"), path = require("path"); 9 | let rsSoliumAll = require("../../../config/rulesets/solium-recommended"); 10 | let JS_EXT = ".js"; 11 | 12 | describe("Tests for solium-recommended.js ruleset", function() { 13 | 14 | it("should have a set of properties", function(done) { 15 | rsSoliumAll.should.be.type("object"); 16 | rsSoliumAll.should.have.ownProperty("rules"); 17 | rsSoliumAll.rules.should.be.type("object"); 18 | 19 | done(); 20 | }); 21 | 22 | it("should have an entry for every rule file in lib/rules directory", function(done) { 23 | let listOfRuleFiles = fs.readdirSync(__dirname + "/../../../lib/rules/"); 24 | 25 | listOfRuleFiles.forEach(function(filename) { 26 | if (path.extname(filename) === JS_EXT) { 27 | rsSoliumAll.rules.should.have.ownProperty(filename.slice(0, -JS_EXT.length)); 28 | } 29 | }); 30 | 31 | done(); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /test/extras/custom-rules-file.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | 5 | "CUSTOM_RULE": function(context) { 6 | context.on("IfStatement", function(emitted) { 7 | if (emitted.exit) { 8 | return; 9 | } 10 | 11 | context.report({ 12 | node: emitted.node, 13 | message: "*******************THIS IS MY CUSTOM RULE**********************" 14 | }); 15 | }); 16 | }, 17 | 18 | //this rule overlaps a built-in rule 19 | "lbrace": function(context) { 20 | context.on("IfStatement", function(emitted) { 21 | if (emitted.exit) { 22 | return; 23 | } 24 | 25 | context.report({ 26 | node: emitted.node, 27 | message: "*******************THIS IS MY CUSTOM RULE**********************" 28 | }); 29 | }); 30 | }, 31 | 32 | //this rule is defined in this file, but shouldn't be included in rules because config.rules doesn't enable it 33 | "not-included": function(context) { 34 | context.on("IfStatement", function(emitted) { 35 | if (emitted.exit) { 36 | return; 37 | } 38 | 39 | context.report({ 40 | node: emitted.node, 41 | message: "*******************THIS IS MY CUSTOM RULE**********************" 42 | }); 43 | }); 44 | } 45 | 46 | }; -------------------------------------------------------------------------------- /test/lib/autofix/merge-fixer-packets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for autofix/merge-fixer-packets.js 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | let mfp = require("../../../lib/autofix/merge-fixer-packets"), 10 | ruleFixer = require("../../../lib/autofix/rule-fixer"); 11 | 12 | 13 | describe("check exposed API", function() { 14 | 15 | it("should expose a single function", function(done) { 16 | mfp.should.be.type("function"); 17 | done(); 18 | }); 19 | 20 | it("should handle all valid inputs", function(done) { 21 | let sourceCode = "abcdefghijklmnopqrstuvwxyz?<>,.!~", 22 | codeModification = "123d@@@efgh+++lop===qr$$$stuvwz*###"; // 'abc' in start & '!~' in end are left out as-it-is 23 | let rf = new ruleFixer("code"); 24 | 25 | // fixerPackets represent the outputs of the functions exposed by rule-fixer 26 | // when a particular node & text are provided 27 | let fixerPackets = [ 28 | rf.insertTextAfter({ type: "a", start: 0, end: 3 }, "123"), 29 | rf.insertTextBefore({ type: "a", start: 4, end: 6 }, "@@@"), 30 | rf.replaceText({ type: "a", start: 8, end: 11 }, "+++"), 31 | rf.remove({ type: "a", start: 12, end: 14 }), 32 | rf.insertTextAfterRange([15, 16], "==="), 33 | rf.insertTextBeforeRange([18, 22], "$$$"), 34 | rf.removeRange([23, 25]), 35 | rf.replaceChar(26, "*"), 36 | rf.replaceTextRange([27, 31], "###") 37 | ]; 38 | 39 | // Send a single packet as object, should be returned as-it-is 40 | let result = mfp(fixerPackets [0], sourceCode); 41 | result.should.be.type("object"); 42 | result.should.have.ownProperty("range"); 43 | result.should.have.ownProperty("text"); 44 | result.range.should.be.Array(); 45 | result.range.length.should.equal(2); 46 | result.range [0].should.equal(fixerPackets [0].range [0]); 47 | result.range [1].should.equal(fixerPackets [0].range [1]); 48 | result.text.should.equal(fixerPackets [0].text); 49 | 50 | // Send a single packet as item of array, should return the packet itself 51 | result = mfp(fixerPackets.slice(0, 1), sourceCode); 52 | result.should.be.type("object"); 53 | result.should.have.ownProperty("range"); 54 | result.should.have.ownProperty("text"); 55 | result.range.should.be.Array(); 56 | result.range.length.should.equal(2); 57 | result.range [0].should.equal(fixerPackets [0].range [0]); 58 | result.range [1].should.equal(fixerPackets [0].range [1]); 59 | result.text.should.equal(fixerPackets [0].text); 60 | 61 | // Send a list of (shuffled) packets, should return 1 final packet that 62 | // ranges from start to end, accounting for all text changes 63 | result = mfp(fixerPackets, sourceCode); 64 | result.should.be.type("object"); 65 | result.should.have.ownProperty("range"); 66 | result.should.have.ownProperty("text"); 67 | result.range.should.be.Array(); 68 | result.range.length.should.equal(2); 69 | result.range [0].should.equal(fixerPackets [0].range [0]); 70 | result.range [1].should.equal(fixerPackets.slice(-1) [0].range [1]); 71 | result.text.should.equal(codeModification); 72 | 73 | done(); 74 | }); 75 | 76 | it("should handle all logically invalid inputs", function(done) { 77 | // Since merge-fixer-packets is a module called internally, it is guaranteed to receive a 78 | // valid fixer packet or array of packets. Therefore we need not test it for all the fuzzy values. 79 | 80 | // overlapping ranges 81 | mfp.bind( 82 | mfp, [{ range: [2, 5], text: "" }, { range: [4, 8], text: "%%%" }], "abcdefghijk" 83 | ).should.throw(); 84 | 85 | done(); 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /test/lib/rules/array-declarations/fixed/ws-btw-op-clos.sol: -------------------------------------------------------------------------------- 1 | contract Dodo { 2 | uint[] x = [1,2,55]; 3 | ab.cde.fg.hi[] doo = [ ]; 4 | bytes32[] myBytesYay; 5 | mapping (uint => string)[] m; 6 | uint[] karma; 7 | string[] narcos; 8 | } 9 | -------------------------------------------------------------------------------- /test/lib/rules/array-declarations/unfixed/mixed.sol: -------------------------------------------------------------------------------- 1 | contract Dodo { 2 | uint [ ] x = [1,2,55]; 3 | ab.cde.fg.hi [ ] doo = [ ]; 4 | bytes32 5 | [ 6 | ] myBytesYay; 7 | mapping (uint => string) 8 | 9 | [ ] m; 10 | uint [ ] karma; 11 | string[] narcos; 12 | } 13 | -------------------------------------------------------------------------------- /test/lib/rules/array-declarations/unfixed/ws-btw-lit-op.sol: -------------------------------------------------------------------------------- 1 | contract Dodo { 2 | uint [] x = [1,2,55]; 3 | ab.cde.fg.hi [] doo = [ ]; 4 | bytes32 [] myBytesYay; 5 | mapping (uint => string) 6 | [] m; 7 | uint 8 | [] karma; 9 | string 10 | [] narcos; 11 | } 12 | -------------------------------------------------------------------------------- /test/lib/rules/array-declarations/unfixed/ws-btw-op-clos.sol: -------------------------------------------------------------------------------- 1 | contract Dodo { 2 | uint[ ] x = [1,2,55]; 3 | ab.cde.fg.hi[ ] doo = [ ]; 4 | bytes32[] myBytesYay; 5 | mapping (uint => string)[ 6 | 7 | ] m; 8 | uint[ ] karma; 9 | string[ 10 | ] narcos; 11 | } 12 | -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/accept/contract-single.sol: -------------------------------------------------------------------------------- 1 | contract Abc {} -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/accept/contract.sol: -------------------------------------------------------------------------------- 1 | contract Abc { 2 | 3 | } 4 | 5 | 6 | contract Bcd { 7 | 8 | } 9 | 10 | 11 | contract Cde { 12 | 13 | } -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/accept/function.sol: -------------------------------------------------------------------------------- 1 | contract A { 2 | function spam(); 3 | function ham(); 4 | constructor(uint x, string foobar){} 5 | function(){} 6 | function baba(){} 7 | } 8 | 9 | 10 | contract B is A { 11 | struct Chumma { 12 | uint x; 13 | } 14 | 15 | function spam() { 16 | 17 | } 18 | 19 | function ham() { 20 | 21 | } 22 | 23 | constructor(address baz) { 24 | 25 | } 26 | 27 | function(uint x) { 28 | 29 | } 30 | 31 | function lola() { 32 | 33 | } 34 | } 35 | 36 | 37 | contract Chumma { 38 | /// @notice this function returns something important 39 | /// @return description 40 | function someFunction() constant returns(uint256) { 41 | return 1; 42 | } 43 | 44 | function() { 45 | // just a fallback function :) 46 | } 47 | 48 | /// @notice gets something else important 49 | function anotherFunction() constant returns(uint256) 50 | { 51 | return 2; 52 | } 53 | 54 | constructor(address owner, uint age) public payable { 55 | if (true) { lol(); } 56 | } 57 | 58 | /** 59 | * @notice this function returns something important 60 | * @return description 61 | */ 62 | function yetAnotherFunction() constant returns(uint256) 63 | { 64 | return 3; 65 | } 66 | } -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/accept/library-single.sol: -------------------------------------------------------------------------------- 1 | library Abc {} -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/accept/library.sol: -------------------------------------------------------------------------------- 1 | library Abc { 2 | 3 | } 4 | 5 | 6 | library Bcd { 7 | 8 | } 9 | 10 | 11 | library Cde { 12 | 13 | } -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/reject/contract.sol: -------------------------------------------------------------------------------- 1 | contract Abc { 2 | 3 | } contract Bcd { 4 | 5 | } 6 | contract Cde { 7 | 8 | } 9 | 10 | contract Def { 11 | 12 | } -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/reject/function.sol: -------------------------------------------------------------------------------- 1 | contract B is A { 2 | function spam() { 3 | 4 | } 5 | function ham() { 6 | 7 | } function sam() { 8 | 9 | } 10 | constructor(uint x, string ham) { 11 | // boo! 12 | } function() { 13 | if (myLifeIsSad()) { 14 | staySad("always"); 15 | } 16 | } 17 | function normalFunc() { 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /test/lib/rules/blank-lines/reject/library.sol: -------------------------------------------------------------------------------- 1 | library Abc { 2 | 3 | } library Bcd { 4 | 5 | } 6 | library Cde { 7 | 8 | } 9 | 10 | library Def { 11 | 12 | } -------------------------------------------------------------------------------- /test/lib/rules/constructor/constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for the constructor rule 3 | * @author Utkarsh Patil , Daniel McLellan 4 | */ 5 | 6 | "use strict"; 7 | 8 | let Solium = require("../../../../lib/solium"); 9 | 10 | let config = { 11 | "rules": { 12 | "constructor": "warning" 13 | } 14 | }; 15 | 16 | describe("[RULE] constructor: Rejections", function() { 17 | it("should reject constructor names that are the same as contract names", function(done) { 18 | let codes = [` 19 | contract Foo { 20 | function Foo() {} 21 | function bar() { 22 | var x = 100; 23 | } 24 | } 25 | `, ` 26 | contract Foo { 27 | function Foo(string name, address account) { 28 | a.b(c); 29 | } 30 | } 31 | `]; 32 | codes.forEach(code => { 33 | let errors = Solium.lint(code, config); 34 | errors.should.be.Array(); 35 | errors.should.have.size(1); 36 | }); 37 | 38 | Solium.reset(); 39 | done(); 40 | }); 41 | }); 42 | 43 | describe("[RULE] constructor: Acceptances", function() { 44 | it("should accept contracts that don't use deprecated constructors", function(done) { 45 | let codes = [` 46 | contract Foo { 47 | function notAConstructor() { 48 | var x = 100; 49 | } 50 | } 51 | `, ` 52 | contract Foo { 53 | constructor() { 54 | a.b(); 55 | } 56 | } 57 | `, ` 58 | contract Foo { 59 | constructor(string name, address account) {} 60 | } 61 | `]; 62 | 63 | codes.forEach(code => { 64 | const errors = Solium.lint(code, config); 65 | errors.should.be.Array(); 66 | errors.should.have.size(0); 67 | }); 68 | 69 | Solium.reset(); 70 | done(); 71 | }); 72 | }); 73 | 74 | describe("[RULE] constructor: fixes", () => { 75 | 76 | it("should replace deprecated style of constructor declaration with new one", done => { 77 | const declarations = [ 78 | { 79 | bad: "function Foo();", 80 | good: "constructor();" 81 | }, 82 | { 83 | bad: "function Foo(string name, address account) { var x = 90767; }", 84 | good: "constructor(string name, address account) { var x = 90767; }" 85 | }, 86 | { 87 | bad: "function Foo(string name, address account) {}", 88 | good: "constructor(string name, address account) {}" 89 | }, 90 | { 91 | bad: "function \t Foo \t\t (string name, address account) {}", 92 | good: "constructor \t\t (string name, address account) {}" 93 | }, 94 | { 95 | bad: "function\n\nFoo\n(string name, address account) {}", 96 | good: "constructor\n(string name, address account) {}" 97 | } 98 | ]; 99 | 100 | declarations.forEach(({ bad, good }) => { 101 | const { fixesApplied, fixedSourceCode, 102 | errorMessages } = Solium.lintAndFix(`contract Foo { ${bad} }`, config); 103 | 104 | fixesApplied.should.be.Array(); 105 | fixesApplied.should.have.size(1); 106 | errorMessages.should.be.Array(); 107 | errorMessages.should.be.empty(); 108 | fixedSourceCode.should.equal(`contract Foo { ${good} }`); 109 | 110 | console.log(fixedSourceCode); 111 | }); 112 | 113 | done(); 114 | }); 115 | 116 | }); 117 | -------------------------------------------------------------------------------- /test/lib/rules/deprecated-suicide/deprecated-suicide.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for deprecated-suicide rule 3 | * @author Federico Bond 4 | */ 5 | 6 | "use strict"; 7 | 8 | let Solium = require("../../../../lib/solium"); 9 | let wrappers = require("../../../utils/wrappers"), 10 | toContract = wrappers.toContract, toFunction = wrappers.toFunction; 11 | 12 | let userConfig = { 13 | "custom-rules-filename": null, 14 | "rules": { 15 | "deprecated-suicide": true 16 | } 17 | }; 18 | 19 | describe("[RULE] deprecated-suicide", function() { 20 | 21 | it("should reject contracts using suicide", function(done) { 22 | let code = toContract("function foo () { suicide(0x0); }"), 23 | errors = Solium.lint(code, userConfig); 24 | 25 | errors.constructor.name.should.equal("Array"); 26 | errors.length.should.equal(1); 27 | 28 | Solium.reset(); 29 | done(); 30 | }); 31 | 32 | it("should replace suicide by selfdestruct when fix is applied", function(done) { 33 | let unfixedCode = toFunction("suicide(0x0);"), 34 | fixedCode = toFunction("selfdestruct(0x0);"); 35 | 36 | let fixed = Solium.lintAndFix(unfixedCode, userConfig); 37 | 38 | fixed.should.be.type("object"); 39 | fixed.should.have.ownProperty("fixedSourceCode"); 40 | fixed.should.have.ownProperty("errorMessages"); 41 | fixed.should.have.ownProperty("fixesApplied"); 42 | 43 | fixed.fixedSourceCode.should.equal(fixedCode); 44 | fixed.errorMessages.should.be.Array(); 45 | fixed.errorMessages.length.should.equal(0); 46 | fixed.fixesApplied.should.be.Array(); 47 | fixed.fixesApplied.length.should.equal(1); 48 | 49 | 50 | unfixedCode = toFunction("suicide (0x0);"); 51 | fixedCode = toFunction("selfdestruct (0x0);"); 52 | 53 | fixed = Solium.lintAndFix(unfixedCode, userConfig); 54 | 55 | fixed.should.be.type("object"); 56 | fixed.should.have.ownProperty("fixedSourceCode"); 57 | fixed.should.have.ownProperty("errorMessages"); 58 | fixed.should.have.ownProperty("fixesApplied"); 59 | 60 | fixed.fixedSourceCode.should.equal(fixedCode); 61 | fixed.errorMessages.should.be.Array(); 62 | fixed.errorMessages.length.should.equal(0); 63 | fixed.fixesApplied.should.be.Array(); 64 | fixed.fixesApplied.length.should.equal(1); 65 | 66 | 67 | unfixedCode = toContract("function a () { suicide(0x0); }\nfunction b () { suicide(0x0); }"); 68 | fixedCode = toContract("function a () { selfdestruct(0x0); }\nfunction b () { selfdestruct(0x0); }"); 69 | 70 | fixed = Solium.lintAndFix(unfixedCode, userConfig); 71 | 72 | fixed.should.be.type("object"); 73 | fixed.should.have.ownProperty("fixedSourceCode"); 74 | fixed.should.have.ownProperty("errorMessages"); 75 | fixed.should.have.ownProperty("fixesApplied"); 76 | 77 | fixed.fixedSourceCode.should.equal(fixedCode); 78 | fixed.errorMessages.should.be.Array(); 79 | fixed.errorMessages.length.should.equal(0); 80 | fixed.fixesApplied.should.be.Array(); 81 | fixed.fixesApplied.length.should.equal(2); 82 | 83 | Solium.reset(); 84 | done(); 85 | }); 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /test/lib/rules/double-quotes/accept/double-quoted.sol: -------------------------------------------------------------------------------- 1 | string a = "hello world"; 2 | bytes32 b = "foo bar"; 3 | string c = "'hello world'"; 4 | string d = "foo \"bar\""; 5 | string addr = "0x0189"; 6 | address contractAddr = 0x19236a; -------------------------------------------------------------------------------- /test/lib/rules/double-quotes/double-quotes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for double-quotes rule 3 | * @author Raghav Dua 4 | */ 5 | 6 | // 2 extra errors produced due to deprecation warning since this rule is now deprecated 7 | // and config format is also deprecated & we asked for internal issues. 8 | 9 | "use strict"; 10 | 11 | let Solium = require("../../../../lib/solium"), 12 | wrappers = require("../../../utils/wrappers"), 13 | fs = require("fs"), 14 | path = require("path"); 15 | 16 | let toContract = wrappers.toContract; 17 | let userConfig = { 18 | "custom-rules-filename": null, 19 | "rules": { 20 | "double-quotes": true 21 | }, 22 | "options": { "returnInternalIssues": true } 23 | }; 24 | 25 | describe("[RULE] double-quotes: Acceptances", function() { 26 | 27 | it("should accept strings quoted with double quotes", function(done) { 28 | let code = fs.readFileSync(path.join(__dirname, "./accept/double-quoted.sol"), "utf8"), 29 | errors = Solium.lint(toContract(code), userConfig); 30 | 31 | errors.constructor.name.should.equal("Array"); 32 | errors.length.should.equal(2); 33 | 34 | Solium.reset(); 35 | done(); 36 | }); 37 | 38 | }); 39 | 40 | 41 | describe("[RULE] double-quotes: Rejections", function() { 42 | 43 | it("should reject strings quoted with single quotes", function(done) { 44 | let code = fs.readFileSync(path.join(__dirname, "./reject/single-quoted.sol"), "utf8"), 45 | errors = Solium.lint(toContract(code), userConfig); 46 | 47 | errors.constructor.name.should.equal("Array"); 48 | errors.length.should.equal(7); 49 | 50 | Solium.reset(); 51 | done(); 52 | }); 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /test/lib/rules/double-quotes/reject/single-quoted.sol: -------------------------------------------------------------------------------- 1 | string a = 'hello world'; 2 | bytes32 b = 'foo bar'; 3 | string c = '\'hello world\''; 4 | string d = 'foo \"bar\"'; 5 | string addr = '0x0189'; -------------------------------------------------------------------------------- /test/lib/rules/imports-on-top/accept/on-top.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^4.4.0; 2 | pragma experimental ABIEncoderV2; 3 | pragma experimental "v0.5.0"; 4 | 5 | import "filename"; 6 | import * as symbolName from "filename"; 7 | import {symbol1 as alias, symbol2} from "filename"; 8 | import "filename" as symbolName; -------------------------------------------------------------------------------- /test/lib/rules/imports-on-top/fixes/before-pragma.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | 4 | contract Halo { 5 | function foo () returns (uint) { 6 | return 0; 7 | } 8 | } 9 | 10 | import "nano.sol"; 11 | -------------------------------------------------------------------------------- /test/lib/rules/imports-on-top/fixes/only-one-error.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | 4 | import * as symbolName from "filename"; 5 | 6 | 7 | contract Halo { 8 | function foo () returns (uint) { 9 | return 0; 10 | } 11 | } 12 | 13 | import "nano.sol"; 14 | 15 | library Foo { 16 | function bar () returns (uint) { 17 | return 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/lib/rules/imports-on-top/reject/intermingled.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | 4 | import "filename" as symbolName; 5 | 6 | 7 | contract Halo { 8 | 9 | function foo () returns (uint) { 10 | return 0; 11 | } 12 | } 13 | 14 | import * as symbolName from "filename"; 15 | 16 | library Foo { 17 | function bar () returns (uint) { 18 | return 1; 19 | } 20 | } 21 | 22 | import "nano.sol"; -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/config-default.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function accessPolicy() 7 | public 8 | returns (IAccessPolicy); 9 | 10 | function Counter() { 11 | count = 0; 12 | myObject 13 | .funcA(10) 14 | .property 15 | [2]; 16 | } 17 | 18 | function inc() { 19 | count++; 20 | _address.transfer(100); 21 | 22 | return 3 23 | .mul(4) 24 | .add(100) 25 | .div(2); 26 | 27 | other.call( 28 | 'transferFrom', 29 | myAddress, 30 | this, 31 | bond 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/config-tabs.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function accessPolicy() 7 | public 8 | returns (IAccessPolicy); 9 | 10 | function Counter() { 11 | count = 0; 12 | myObject 13 | .funcA(10) 14 | .property 15 | [2]; 16 | } 17 | 18 | function inc() { 19 | count++; 20 | _address.transfer(100); 21 | 22 | return 3 23 | .mul(4) 24 | .add(100) 25 | .div(2); 26 | 27 | other.call( 28 | 'transferFrom', 29 | myAddress, 30 | this, 31 | bond 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/config-two-spaces.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function accessPolicy() 7 | public 8 | returns (IAccessPolicy); 9 | 10 | function Counter() { 11 | count = 0; 12 | myObject 13 | .funcA(10) 14 | .property 15 | [2]; 16 | } 17 | 18 | function inc() { 19 | count++; 20 | _address.transfer(100); 21 | 22 | return 3 23 | .mul(4) 24 | .add(100) 25 | .div(2); 26 | 27 | other.call( 28 | 'transferFrom', 29 | myAddress, 30 | this, 31 | bond 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/multiline-array.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | uint[] public array; 6 | 7 | function Counter() { 8 | count = 0; 9 | array = [ 10 | 1, 11 | 2, 12 | 3 13 | ]; 14 | } 15 | 16 | function inc() { 17 | count++; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/multiline-call-declaration.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function Counter() { 7 | count = 0; 8 | } 9 | 10 | function inc() { 11 | count++; 12 | } 13 | 14 | function incBy( 15 | uint n 16 | ) { 17 | count = count + n; 18 | } 19 | 20 | function incByTwo() { 21 | incBy( 22 | 2 23 | ); 24 | } 25 | 26 | // TODO: Uncomment below code once Issue #268 is resolved 27 | /* 28 | function abstractFunc1() 29 | payable 30 | returns(uint, string); 31 | 32 | function abstractFunc2( 33 | uint foo, 34 | string bar 35 | ) payable; 36 | 37 | function abstractFunc2( 38 | uint bax, 39 | uint bar 40 | ) 41 | payable 42 | returns(string); 43 | */ 44 | } 45 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/multiline-call-expression.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function Counter() { 7 | count = 0; 8 | } 9 | 10 | function inc() { 11 | count++; 12 | } 13 | 14 | function incBy(uint n) { 15 | count = count + n; 16 | } 17 | 18 | function incByTwo() { 19 | incBy( 20 | 2 21 | ); 22 | } 23 | 24 | function pack(uint32 param1, uint16 param2, uint32 param3) public pure returns(bytes32) { 25 | return bytes32( 26 | (uint256(param1) << 128) | 27 | (uint256(param2) << 64) | 28 | (uint256(param3) << 32) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/one-line-array.sol: -------------------------------------------------------------------------------- 1 | contract Foo { 2 | uint[] arr = [1,2]; 3 | } 4 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/one-line-function-call.sol: -------------------------------------------------------------------------------- 1 | contract Foo { 2 | function foo() {} 3 | 4 | function bar() { 5 | foo(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/one-line-struct.sol: -------------------------------------------------------------------------------- 1 | contract Foo { 2 | struct Bar { a uint; b char; } 3 | } 4 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/accept/struct.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Foo { 4 | uint public foo; 5 | 6 | struct Bar { 7 | string baz; 8 | string qux; 9 | } 10 | 11 | function setFoo(uint _foo) { 12 | foo = _foo; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/chars-before-top-level.sol: -------------------------------------------------------------------------------- 1 | /**/contract Foo {} 2 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/config-default.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Foo { 4 | uint public a; 5 | uint public b; 6 | uint public c; 7 | 8 | function singleLineWrong() { 9 | a = 1; 10 | 11 | other.call( 12 | 'transferFrom', 13 | myAddress, 14 | this, 15 | bond 16 | ); 17 | } 18 | 19 | function firstLineWrong() { 20 | a = 1; 21 | b = 2; 22 | c = 3; 23 | } 24 | 25 | function secondLineWrong() { 26 | a = 1; 27 | b = 2; 28 | c = 3; 29 | } 30 | 31 | function thirdLineWrong() { 32 | a = 1; 33 | b = 2; 34 | c = 3; 35 | } 36 | 37 | function allLinesWrong() { 38 | a = 1; 39 | b = 2; 40 | c = 3; 41 | } 42 | 43 | function chainedFunctions() { 44 | myObj 45 | .foo() 46 | .bar(100, "hello"); 47 | 48 | myObj 49 | .foo() 50 | .bar(100, "hello"); 51 | } 52 | 53 | function abstractFunc() 54 | payable 55 | returns (uint, bytes32); 56 | 57 | function abstractFunc() 58 | payable 59 | returns (uint, bytes32); 60 | } 61 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/config-tabs.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function Counter() { 7 | count = 0; 8 | } 9 | 10 | function inc() { 11 | count++; 12 | } 13 | 14 | function abstractFunc() 15 | payable 16 | returns (uint, bytes32); 17 | 18 | function abstractFunc() 19 | payable 20 | returns (uint, bytes32); 21 | } 22 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/config-two-spaces.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function Counter() { 7 | count = 0; 8 | } 9 | 10 | function inc() { 11 | count++; 12 | } 13 | 14 | function abstractFunc() 15 | payable 16 | returns (uint, bytes32); 17 | 18 | function abstractFunc() 19 | payable 20 | returns (uint, bytes32); 21 | } 22 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/indented-top-level-closing-brace.sol: -------------------------------------------------------------------------------- 1 | contract Foo { 2 | } 3 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/mixed-tabs-spaces.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function Counter() { 7 | count = 0; 8 | } 9 | 10 | function inc() { 11 | count++; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/multiline-array.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint[] public array; 5 | 6 | function allItemsWrong() { 7 | array = [ 8 | 1, 9 | 2, 10 | 3 11 | ]; 12 | } 13 | 14 | function firstItemWrong() { 15 | array = [ 16 | 1, 17 | 2, 18 | 3 19 | ]; 20 | } 21 | 22 | function secondItemWrong() { 23 | array = [ 24 | 1, 25 | 2, 26 | 3 27 | ]; 28 | } 29 | 30 | function thirdItemWrong() { 31 | array = [ 32 | 1, 33 | 2, 34 | 3 35 | ]; 36 | } 37 | 38 | function wholeLineWrong() { 39 | array = [ 40 | 1, 41 | 2, 42 | 3 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/multiline-call-declaration.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function Counter() { 7 | count = 0; 8 | } 9 | 10 | function inc() { 11 | count++; 12 | } 13 | 14 | function incBy( 15 | uint n 16 | ) { 17 | count = count + n; 18 | } 19 | 20 | function incByTwo() { 21 | incBy( 22 | 2 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/multiline-call-expression.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Counter { 4 | uint public count; 5 | 6 | function Counter() { 7 | count = 0; 8 | } 9 | 10 | function inc() { 11 | count++; 12 | } 13 | 14 | function incBy(uint n) { 15 | count = count + n; 16 | } 17 | 18 | function incByTwo() { 19 | incBy( 20 | 2 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/struct.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Foo { 4 | uint public foo; 5 | 6 | struct FirstLineWrong { 7 | string baz; 8 | string qux; 9 | string quux; 10 | } 11 | 12 | struct SecondLineWrong { 13 | string baz; 14 | string qux; 15 | string quux; 16 | } 17 | 18 | struct ThirdLineWrong { 19 | string baz; 20 | string qux; 21 | string quux; 22 | } 23 | 24 | struct AllLinesWrong { 25 | string baz; 26 | string qux; 27 | string quux; 28 | } 29 | 30 | struct WholeDeclarationWrong { 31 | string baz; 32 | string qux; 33 | string quux; 34 | } 35 | 36 | function setFoo(uint _foo) { 37 | foo = _foo; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/lib/rules/indentation/reject/top-level-indent.sol: -------------------------------------------------------------------------------- 1 | contract Foo {} 2 | -------------------------------------------------------------------------------- /test/lib/rules/linebreak-style/linebreak-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for linebreak-style rule 3 | * @author Arjun Nemani 4 | */ 5 | 6 | "use strict"; 7 | 8 | const Solium = require("../../../../lib/solium"), 9 | wrappers = require("../../../utils/wrappers"), 10 | { addPragma } = wrappers, 11 | path = require("path"), 12 | fs = require("fs"); 13 | 14 | const userConfigUnix = { 15 | rules: { 16 | "linebreak-style": "error" 17 | } 18 | }; 19 | 20 | describe("[RULE] linebreak-style: Acceptances for Unix Line breaks", function() { 21 | it("should accept when receiving Unix Line breaks", function(done) { 22 | const code = fs 23 | .readFileSync(path.join(__dirname, "./unix-endings")) 24 | .toString(); 25 | const errors = Solium.lint(addPragma(code), userConfigUnix); 26 | 27 | errors.should.be.Array(); 28 | errors.length.should.equal(0); 29 | 30 | Solium.reset(); 31 | done(); 32 | }); 33 | }); 34 | 35 | describe("[RULE] linebreak-style: Rejections for Unix Line breaks", function() { 36 | it("should reject when receiving Windows Line breaks", function(done) { 37 | const code = fs 38 | .readFileSync(path.join(__dirname, "./windows-endings")) 39 | .toString(); 40 | const errors = Solium.lint(code, userConfigUnix); 41 | 42 | errors.should.be.Array(); 43 | errors.length.should.be.above(0); 44 | 45 | Solium.reset(); 46 | done(); 47 | }); 48 | }); 49 | 50 | 51 | 52 | describe("[RULE] linebreak-style: Fixes for Unix Line breaks", function() { 53 | it("should change to Unix Line Breaks when receiving Windows Line breaks", function(done) { 54 | const unfixedCode = fs 55 | .readFileSync(path.join(__dirname, "./windows-endings")) 56 | .toString(); 57 | const fixedCode = fs 58 | .readFileSync(path.join(__dirname, "./unix-endings")) 59 | .toString(); 60 | const newCode = Solium.lintAndFix(unfixedCode, userConfigUnix); 61 | 62 | newCode.fixedSourceCode.should.equal(fixedCode); 63 | 64 | Solium.reset(); 65 | done(); 66 | }); 67 | }); 68 | 69 | const userConfigWindows = { 70 | rules: { 71 | "linebreak-style": ["error", "windows"] 72 | } 73 | }; 74 | 75 | describe("[RULE] linebreak-style: Acceptances for Windows Line breaks", function() { 76 | it("should accept when receiving Windows Line breaks", function(done) { 77 | const code = fs 78 | .readFileSync(path.join(__dirname, "./windows-endings")) 79 | .toString(); 80 | 81 | const errors = Solium.lint(code, userConfigWindows); 82 | 83 | errors.should.be.Array(); 84 | errors.length.should.equal(0); 85 | 86 | Solium.reset(); 87 | done(); 88 | }); 89 | }); 90 | 91 | describe("[RULE] linebreak-style: Rejections for Windows Line breaks", function() { 92 | it("should reject when receiving Unix Line breaks", function(done) { 93 | const code = fs 94 | .readFileSync(path.join(__dirname, "./unix-endings")) 95 | .toString(); 96 | 97 | const errors = Solium.lint(code, userConfigWindows); 98 | 99 | errors.should.be.Array(); 100 | errors.length.should.be.above(0); 101 | 102 | Solium.reset(); 103 | done(); 104 | }); 105 | }); 106 | 107 | describe("[RULE] linebreak-style: Fixes for Windows Line breaks", function() { 108 | it("should change to Windows Line breaks when receiving Unix Line breaks", function(done) { 109 | const unfixedCode = fs 110 | .readFileSync(path.join(__dirname, "./unix-endings")) 111 | .toString(); 112 | const fixedCode = fs 113 | .readFileSync(path.join(__dirname, "./windows-endings")) 114 | .toString(); 115 | 116 | const newCode = Solium.lintAndFix(unfixedCode, userConfigWindows); 117 | newCode.fixedSourceCode.should.equal(fixedCode); 118 | 119 | Solium.reset(); 120 | done(); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/lib/rules/linebreak-style/unix-endings: -------------------------------------------------------------------------------- 1 | // This is comment containing \n and \r\n 2 | // Lets hope it all works!! 3 | // \t hello world \r\r 4 | // hello \n\\n world 5 | 6 | contract Foo { 7 | string myName = "Harry\r\n\\\npotter"; 8 | string myName = "Harry\n\rpotter"; 9 | function f () { 10 | var myName = "Harry\/\npotter"; 11 | var foobar = "Hello world"; 12 | string fuu = "chumma"; 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /test/lib/rules/linebreak-style/windows-endings: -------------------------------------------------------------------------------- 1 | // This is comment containing \n and \r\n 2 | // Lets hope it all works!! 3 | // \t hello world \r\r 4 | // hello \n\\n world 5 | 6 | contract Foo { 7 | string myName = "Harry\r\n\\\npotter"; 8 | string myName = "Harry\n\rpotter"; 9 | function f () { 10 | var myName = "Harry\/\npotter"; 11 | var foobar = "Hello world"; 12 | string fuu = "chumma"; 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /test/lib/rules/no-constant/no-constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-constant rule 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | const Solium = require("../../../../lib/solium"); 9 | const userConfig = { 10 | "rules": { 11 | "no-constant": "error" 12 | } 13 | }; 14 | 15 | 16 | describe("[RULE] emit: Acceptances", () => { 17 | 18 | it("should accept function declarations that don't have constant modifier", done => { 19 | const declarations = [ 20 | "function foo() view pure returns(bool);", 21 | "function foo() view pure returns(bool) {}", 22 | "function foo() {}", 23 | "function foo();", 24 | "function(){}", 25 | "function foo() myModifier(100, \"hello\") boo;" 26 | ]; 27 | 28 | declarations.forEach(func => { 29 | const issues = Solium.lint(`contract Foo { ${func} }`, userConfig); 30 | 31 | issues.should.be.Array(); 32 | issues.should.be.empty(); 33 | }); 34 | 35 | done(); 36 | }); 37 | 38 | }); 39 | 40 | 41 | describe("[RULE] emit: Rejections", () => { 42 | 43 | it("should reject function declarations that have constant modifier", done => { 44 | const declarations = [ 45 | "function foo() view pure constant returns(bool);", 46 | "function foo() constant pure returns(bool) {}", 47 | "function foo() constant {}", 48 | "function foo() constant;", 49 | "function()constant{}", 50 | "function foo() myModifier(100, \"hello\") constant boo;" 51 | ]; 52 | 53 | declarations.forEach(func => { 54 | const issues = Solium.lint(`contract Foo { ${func} }`, userConfig); 55 | 56 | issues.should.be.Array(); 57 | issues.should.have.size(1); 58 | }); 59 | 60 | done(); 61 | }); 62 | 63 | }); 64 | 65 | 66 | describe("[RULE] emit: fixes", () => { 67 | 68 | it("should replace constant with view", done => { 69 | const declarations = [ 70 | { bad: "function foo() view pure constant returns(bool);", good: "function foo() view pure view returns(bool);" }, 71 | { bad: "function foo() constant pure returns(bool) {}", good: "function foo() view pure returns(bool) {}" }, 72 | { bad: "function foo() constant {}", good: "function foo() view {}" }, 73 | { bad: "function foo() constant;", good: "function foo() view;" }, 74 | { bad: "function()constant{}", good: "function()view{}" }, 75 | { 76 | bad: "function foo() myModifier(100, \"hello\") constant boo;", 77 | good: "function foo() myModifier(100, \"hello\") view boo;" 78 | } 79 | ]; 80 | 81 | declarations.forEach(({ bad, good }) => { 82 | const { fixesApplied, fixedSourceCode, 83 | errorMessages } = Solium.lintAndFix(`contract Foo { ${bad} }`, userConfig); 84 | 85 | fixesApplied.should.be.Array(); 86 | fixesApplied.should.have.size(1); 87 | errorMessages.should.be.Array(); 88 | errorMessages.should.be.empty(); 89 | fixedSourceCode.should.equal(`contract Foo { ${good} }`); 90 | }); 91 | 92 | done(); 93 | }); 94 | 95 | }); -------------------------------------------------------------------------------- /test/lib/rules/no-experimental/no-experimental.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure that experimental features are not used in production 3 | * @author Ivan Mushketyk 4 | */ 5 | 6 | "use strict"; 7 | 8 | const Solium = require("../../../../lib/solium"); 9 | 10 | const userConfig = { 11 | "rules": { 12 | "no-experimental": "error" 13 | } 14 | }; 15 | 16 | describe("[RULE] no-experimental: Acceptances", () => { 17 | 18 | it("should accept contracts without 'pragma experimental'", done => { 19 | const code = "pragma solidity ^0.4.0; contract Foo {} library Bar {}", 20 | errors = Solium.lint(code, userConfig); 21 | 22 | errors.should.be.Array(); 23 | errors.should.be.empty(); 24 | 25 | Solium.reset(); 26 | done(); 27 | }); 28 | }); 29 | 30 | describe("[RULE] no-experimental: Rejections", function() { 31 | 32 | it("should reject contracts with 'pragma experimental'", done => { 33 | let code = ` 34 | pragma experimental "^0.5.0"; 35 | contract Foo {} 36 | `; 37 | let errors = Solium.lint(code, userConfig); 38 | errors.should.be.Array(); 39 | errors.should.have.size(1); 40 | 41 | code = ` 42 | pragma solidity ^0.4.0; 43 | pragma experimental "^0.5.0"; 44 | contract Foo {} 45 | contract Bar {} 46 | `; 47 | errors = Solium.lint(code, userConfig); 48 | errors.should.be.Array(); 49 | errors.should.have.size(1); 50 | 51 | Solium.reset(); 52 | done(); 53 | }); 54 | 55 | }); -------------------------------------------------------------------------------- /test/lib/rules/no-trailing-whitespace/no-trailing-whitespace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-trailing-whitespace rule 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | 9 | const Solium = require("../../../../lib/solium"); 10 | 11 | const config = { 12 | "rules": { 13 | "no-trailing-whitespace": "error" 14 | } 15 | }; 16 | 17 | 18 | describe("[RULE] no-trailing-whitespace: Acceptances", () => { 19 | 20 | it("should accept lines having no trailing whitespaces", done => { 21 | const code = ` 22 | 23 | contract Foo { 24 | // a comment 25 | function baz() returns(uint) { 26 | /* 27 | another happy comment 28 | on multiple lines 29 | */ 30 | 31 | 32 | callHelloWorld( 33 | 100, /* another block comment */ 34 | "voila!", 35 | 36 | 0x1892873871198 37 | ); 38 | } 39 | }`; 40 | 41 | Solium.lint(code, config).should.be.empty(); 42 | done(); 43 | }); 44 | 45 | it("should accept comments & blank lines with trailing whitespaces if they're ignored", done => { 46 | const configLocal = { 47 | "rules": { 48 | "no-trailing-whitespace": [ 49 | "error", 50 | { "skipBlankLines": true, "ignoreComments": true } 51 | ] 52 | } 53 | }; 54 | 55 | const code = ` 56 | \t\t \t 57 | contract Foo { 58 | // a comment 59 | function baz() returns(uint) { 60 | /* \t 61 | another happy comment 62 | on multiple lines\t 63 | */ 64 | 65 | \t \t 66 | callHelloWorld( 67 | 100, /* another block comment */ 68 | "voila!", 69 | \t 70 | 0x1892873871198 71 | ); 72 | } // another line comment\t\t 73 | 74 | 75 | function /* hello */ bax() /* world */ returns(string) { /* this is a stretched \t 76 | comment */ 77 | return "hello"; 78 | } 79 | }`; 80 | 81 | Solium.lint(code, configLocal).should.be.empty(); 82 | done(); 83 | }); 84 | 85 | }); 86 | 87 | 88 | describe("[RULE] no-trailing-whitespace: Rejections", () => { 89 | 90 | it("should reject code, comment & blank lines with trailing whitespaces", done => { 91 | const code = ` 92 | \t\t \t 93 | contract Foo { 94 | // a comment 95 | function baz() returns(uint) { 96 | /* \t 97 | another happy comment 98 | on multiple lines\t 99 | */ 100 | 101 | \t \t 102 | callHelloWorld( 103 | 100, /* another block comment */ 104 | "voila!", 105 | \t 106 | 0x1892873871198\t 107 | ); 108 | } // another line comment\t\t 109 | 110 | 111 | function /* hello */ bax() /* world */ returns(string) { /* this is a stretched \t 112 | comment */ 113 | return "hello"; 114 | } 115 | }`; 116 | 117 | Solium.lint(code, config).should.have.size(16); 118 | done(); 119 | }); 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /test/lib/rules/no-unused-vars/no-unused-vars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-unused-vars rule 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let Solium = require("../../../../lib/solium"); 9 | let wrappers = require("../../../utils/wrappers"); 10 | let toContract = wrappers.toContract; 11 | let toFunction = wrappers.toFunction; 12 | 13 | let userConfig = { 14 | "custom-rules-filename": null, 15 | "rules": { 16 | "no-unused-vars": true 17 | } 18 | }; 19 | 20 | describe("[RULE] no-unused-vars: Acceptances", function() { 21 | 22 | it("should accept all variables that are used at least once in the same program", function(done) { 23 | let code = [ 24 | "uint x = 100; function foo () returns (uint) { return x; }", 25 | "bytes32 x = \"hello\"; function foo () returns (bytes32) { return x; }", 26 | "string x = \"hello\"; function foo () returns (int) { return x; }", 27 | "address x = 0x0; function foo () returns (address) { return x; }", 28 | "mapping (address => uint) x; function foo () returns (mapping) { return x; }", 29 | "function foo() { var bax = 100; result = bax * 89; }", 30 | "function foo() { string bax = \"hello world\"; callMyFunc(bax); }" 31 | ]; 32 | let errors; 33 | 34 | code.forEach(line => { 35 | errors = Solium.lint(toContract(line), userConfig); 36 | errors.should.be.Array(); 37 | errors.should.have.size(0); 38 | }); 39 | 40 | Solium.reset(); 41 | done(); 42 | }); 43 | 44 | it("should accept if the variable's usage occurs above its declaration & definition.", function(done) { 45 | let code = "contract Owned {\nfunction setOwner(address _new) onlyOwner { NewOwner(owner, _new); owner = _new; }\naddress public owner = msg.sender;}", 46 | errors = Solium.lint(code, userConfig); 47 | 48 | errors.constructor.name.should.equal("Array"); 49 | errors.length.should.equal(0); 50 | 51 | Solium.reset(); 52 | done(); 53 | }); 54 | }); 55 | 56 | 57 | describe("[RULE] no-unused-vars: Rejections", function() { 58 | 59 | it("should reject all variables that haven't been used even once", function(done) { 60 | let code = [ 61 | "var x = 100;", 62 | "uint x = 100;", 63 | "bytes32 x = \"hello\";", 64 | "string x = \"hello\";", 65 | "address x = 0x0;", 66 | "mapping (address => uint) x;" 67 | ]; 68 | let errors; 69 | 70 | code = code.map(function(item){return toFunction(item);}); 71 | 72 | errors = Solium.lint(code [0], userConfig); 73 | errors.constructor.name.should.equal("Array"); 74 | errors.length.should.equal(1); 75 | 76 | errors = Solium.lint(code [1], userConfig); 77 | errors.constructor.name.should.equal("Array"); 78 | errors.length.should.equal(1); 79 | 80 | errors = Solium.lint(code [2], userConfig); 81 | errors.constructor.name.should.equal("Array"); 82 | errors.length.should.equal(1); 83 | 84 | errors = Solium.lint(code [3], userConfig); 85 | errors.constructor.name.should.equal("Array"); 86 | errors.length.should.equal(1); 87 | 88 | errors = Solium.lint(code [4], userConfig); 89 | errors.constructor.name.should.equal("Array"); 90 | errors.length.should.equal(1); 91 | 92 | errors = Solium.lint(code [5], userConfig); 93 | errors.constructor.name.should.equal("Array"); 94 | errors.length.should.equal(1); 95 | 96 | Solium.reset(); 97 | done(); 98 | }); 99 | 100 | }); 101 | -------------------------------------------------------------------------------- /test/lib/rules/no-with/no-with.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-with rule 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let Solium = require("../../../../lib/solium"); 9 | let toFunction = require("../../../utils/wrappers").toFunction; 10 | 11 | let userConfig = { 12 | "custom-rules-filename": null, 13 | "rules": { 14 | "no-with": true 15 | }, 16 | "options": { 17 | "returnInternalIssues": true 18 | } 19 | }; 20 | 21 | describe("[RULE] no-with: Rejection", function() { 22 | 23 | it("should produce deprecation warning if this rule is enabled", function(done) { 24 | let code = toFunction(""), errors = Solium.lint(code, userConfig); 25 | 26 | errors.should.be.Array(); 27 | errors.should.be.size(2); 28 | 29 | errors [0].type.should.equal("warning"); 30 | errors [0].internal.should.equal(true); 31 | errors [0].message.startsWith( 32 | "[Deprecated] You are using a deprecated soliumrc configuration format." 33 | ).should.equal(true); 34 | 35 | errors [1].type.should.equal("warning"); 36 | errors [1].internal.should.equal(true); 37 | errors [1].message.should.equal("[Deprecated] Rule \"no-with\" is deprecated."); 38 | 39 | // Only rule deprecation warning with the new config 40 | let newConfig = { 41 | "rules": { 42 | "no-with": "error" 43 | }, 44 | "options": { 45 | "returnInternalIssues": true 46 | } 47 | }; 48 | 49 | errors = Solium.lint(code, newConfig); 50 | 51 | errors.should.be.Array(); 52 | errors.should.be.size(1); 53 | 54 | errors [0].type.should.equal("warning"); 55 | errors [0].internal.should.equal(true); 56 | errors [0].message.should.equal("[Deprecated] Rule \"no-with\" is deprecated."); 57 | 58 | Solium.reset(); 59 | done(); 60 | }); 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /test/lib/rules/quotes/double-full.sol: -------------------------------------------------------------------------------- 1 | import "file"; 2 | import "file.sol"; 3 | import "a.sol"; 4 | import "x"; 5 | import " o"; 6 | import * as sym from "file"; 7 | import {a, b} from "file.sol"; 8 | import "../myContract.sol" as MyContract; 9 | 10 | contract Foo { 11 | string fucker = "hello wordl"; 12 | function f () returns (string){ 13 | var foobar = "Hello world"; 14 | string fuu = "chumma"; 15 | 16 | var x = ( "hello world" ); 17 | var x = ( "hello world" ); 18 | var x = ( "hello world"); 19 | var x = ("hello world" ); 20 | 21 | return ("Hello World"); 22 | 23 | return( 24 | "Lorem Ipsum" 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /test/lib/rules/quotes/double-quoted.sol: -------------------------------------------------------------------------------- 1 | string a = "hello world"; 2 | bytes32 b = "foo bar"; 3 | string c = "'hello world'"; 4 | string d = "foo \"bar\""; 5 | string addr = "0x0189"; 6 | address contractAddr = 0x19236a; 7 | string arr = ["abc", "bcd", "efg", "focus"]; 8 | bytes1 var1 = "x"; 9 | bytes2 var2 = "x"; 10 | bytes3 var3 = "x"; 11 | bytes4 var4 = "x"; 12 | bytes5 var5 = "x"; 13 | bytes6 var6 = "x"; 14 | bytes7 var7 = "x"; 15 | bytes8 var8 = "x"; 16 | bytes9 var9 = "x"; 17 | bytes10 var10 = "x"; 18 | bytes11 var11 = "x"; 19 | bytes12 var12 = "x"; 20 | bytes13 var13 = "x"; 21 | bytes14 var14 = "x"; 22 | bytes15 var15 = "x"; 23 | bytes16 var16 = "x"; 24 | bytes17 var17 = "x"; 25 | bytes18 var18 = "x"; 26 | bytes19 var19 = "x"; 27 | bytes20 var20 = "x"; 28 | bytes21 var21 = "x"; 29 | bytes22 var22 = "x"; 30 | bytes23 var23 = "x"; 31 | bytes24 var24 = "x"; 32 | bytes25 var25 = "x"; 33 | bytes26 var26 = "x"; 34 | bytes27 var27 = "x"; 35 | bytes28 var28 = "x"; 36 | bytes29 var29 = "x"; 37 | bytes30 var30 = "x"; 38 | bytes31 var31 = "x"; 39 | bytes32 var32 = "x"; -------------------------------------------------------------------------------- /test/lib/rules/quotes/single-full.sol: -------------------------------------------------------------------------------- 1 | import 'file'; 2 | import 'file.sol'; 3 | import 'a.sol'; 4 | import 'x'; 5 | import ' o'; 6 | import * as sym from 'file'; 7 | import {a, b} from 'file.sol'; 8 | import '../myContract.sol' as MyContract; 9 | 10 | contract Foo { 11 | string fucker = 'hello wordl'; 12 | function f () returns (string){ 13 | var foobar = 'Hello world'; 14 | string fuu = 'chumma'; 15 | 16 | var x = ( 'hello world' ); 17 | var x = ( 'hello world' ); 18 | var x = ( 'hello world'); 19 | var x = ('hello world' ); 20 | 21 | return ('Hello World'); 22 | 23 | return( 24 | 'Lorem Ipsum' 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /test/lib/rules/quotes/single-quoted.sol: -------------------------------------------------------------------------------- 1 | string a = 'hello world'; 2 | bytes32 b = 'foo bar'; 3 | string c = '\'hello world\''; 4 | string d = 'foo \"bar\"'; 5 | string addr = '0x0189'; 6 | address contractAddr = 0x19236a; 7 | string arr = ['abc', 'bcd', 'efg', 'focus']; 8 | bytes1 var1 = 'x'; 9 | bytes2 var2 = 'x'; 10 | bytes3 var3 = 'x'; 11 | bytes4 var4 = 'x'; 12 | bytes5 var5 = 'x'; 13 | bytes6 var6 = 'x'; 14 | bytes7 var7 = 'x'; 15 | bytes8 var8 = 'x'; 16 | bytes9 var9 = 'x'; 17 | bytes10 var10 = 'x'; 18 | bytes11 var11 = 'x'; 19 | bytes12 var12 = 'x'; 20 | bytes13 var13 = 'x'; 21 | bytes14 var14 = 'x'; 22 | bytes15 var15 = 'x'; 23 | bytes16 var16 = 'x'; 24 | bytes17 var17 = 'x'; 25 | bytes18 var18 = 'x'; 26 | bytes19 var19 = 'x'; 27 | bytes20 var20 = 'x'; 28 | bytes21 var21 = 'x'; 29 | bytes22 var22 = 'x'; 30 | bytes23 var23 = 'x'; 31 | bytes24 var24 = 'x'; 32 | bytes25 var25 = 'x'; 33 | bytes26 var26 = 'x'; 34 | bytes27 var27 = 'x'; 35 | bytes28 var28 = 'x'; 36 | bytes29 var29 = 'x'; 37 | bytes30 var30 = 'x'; 38 | bytes31 var31 = 'x'; 39 | bytes32 var32 = 'x'; -------------------------------------------------------------------------------- /test/lib/rules/uppercase/uppercase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for uppercase rule 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let Solium = require("../../../../lib/solium"); 9 | let wrappers = require("../../../utils/wrappers"); 10 | let toContract = wrappers.toContract; 11 | 12 | let userConfig = { 13 | "custom-rules-filename": null, 14 | "rules": { 15 | "uppercase": true 16 | } 17 | }; 18 | 19 | describe("[RULE] uppercase: Acceptances", function() { 20 | 21 | it("should accept all constants that are uppercase", function(done) { 22 | let code = [ 23 | "uint256 constant N = 19028;", 24 | "uint256 constant A_Z = 18;", 25 | "uint256 constant A__90 = 18;", 26 | "uint constant HE = 100;", 27 | "address constant HELLO_WORLD = 0x0;", 28 | "bytes32 constant HELLOWORLD = \"dd\";", 29 | "string constant HELLO = \"dd\";", 30 | "address constant HELLO_NUMBER_9 = 0x0;", 31 | "uint constant HELLO_NUMBER_0 = 190;", 32 | "string constant HELLO_NUMBER89116 = \"hello\";", 33 | "string constant HELLO_98_NUMBER = \"number\";", 34 | "address constant H0 = 0x1;", 35 | "string constant _F = \"dd\";", 36 | "string constant D_ = \"dd\";", 37 | "string constant __F = \"dd\";", 38 | "string constant D__ = \"dd\";", 39 | "string constant __F__ = \"dd\";", 40 | "string constant _F00BAR = \"dd\";", 41 | "string constant D9HG8_ = \"dd\";", 42 | "string constant __FJO8 = \"dd\";", 43 | "string constant D891JK9__ = \"dd\";", 44 | "string constant __F9018SJ__ = \"dd\";" 45 | ]; 46 | let errors; 47 | 48 | code = code.map(function(item){return toContract(item);}); 49 | 50 | code.forEach(function(declaration) { 51 | errors = Solium.lint(declaration, userConfig); 52 | errors.constructor.name.should.equal("Array"); 53 | errors.length.should.equal(0); 54 | }); 55 | 56 | Solium.reset(); 57 | done(); 58 | }); 59 | 60 | }); 61 | 62 | 63 | describe("[RULE] uppercase: Rejections", function() { 64 | 65 | it("should reject all constants which are not uppercase", function(done) { 66 | let code = [ 67 | "uint constant he = 100;", 68 | "address constant hello_world = 0x0;", 69 | "bytes32 constant helloworld= \"dd\";", 70 | "string constant HellO = \"dd\";", 71 | "string constant HeO = \"dd\";", 72 | "string constant _ = \"dd\";", 73 | "string constant ___F = \"dd\";", 74 | "string constant D___ = \"dd\";", 75 | "string constant ___F___ = \"dd\";", 76 | "string constant ___F00BAR = \"dd\";", 77 | "string constant D9HG8___ = \"dd\";", 78 | "string constant ___F9018SJ___ = \"dd\";" 79 | ]; 80 | let errors; 81 | 82 | code = code.map(function(item){return toContract(item);}); 83 | 84 | code.forEach(function(declaration) { 85 | errors = Solium.lint(declaration, userConfig); 86 | errors.constructor.name.should.equal("Array"); 87 | errors.length.should.equal(1); 88 | }); 89 | 90 | Solium.reset(); 91 | done(); 92 | }); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /test/lib/rules/value-in-payable/value-in-payable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for value-in-payable rule 3 | * @author Ivan Mushketyk 4 | */ 5 | 6 | "use strict"; 7 | 8 | const Solium = require("../../../../lib/solium"), 9 | { toContract } = require("../../../utils/wrappers"); 10 | 11 | let userConfig = { 12 | "rules": { 13 | "value-in-payable": "error" 14 | } 15 | }; 16 | 17 | describe("[RULE] value-in-payable: Acceptances", () => { 18 | 19 | it("should accept functions that access 'msg.value' and have the 'payable' modifier", function(done) { 20 | const code = toContract("function pay() payable { require(msg.value >= MIN_PRICE); }"), 21 | errors = Solium.lint(code, userConfig); 22 | 23 | errors.should.be.Array(); 24 | errors.should.be.empty(); 25 | 26 | Solium.reset(); 27 | done(); 28 | }); 29 | 30 | it("should accept functions that access 'msg.value' and have the 'private' modifier", function(done) { 31 | const code = toContract("function pay() private { require(msg.value >= MIN_PRICE); }"), 32 | errors = Solium.lint(code, userConfig); 33 | 34 | errors.should.be.Array(); 35 | errors.should.be.empty(); 36 | 37 | Solium.reset(); 38 | done(); 39 | }); 40 | 41 | it("should accept functions that access 'msg.value' and have the 'internal' modifier", function(done) { 42 | const code = toContract("function pay() internal { require(msg.value >= MIN_PRICE); }"), 43 | errors = Solium.lint(code, userConfig); 44 | 45 | errors.should.be.Array(); 46 | errors.should.be.empty(); 47 | 48 | Solium.reset(); 49 | done(); 50 | }); 51 | 52 | it("should accept code that accesses 'msg.value' outside a function", function(done) { 53 | const code = toContract(`function foo() { } 54 | unit foo1 = msg.value;`); 55 | const errors = Solium.lint(code, userConfig); 56 | 57 | errors.should.be.Array(); 58 | errors.should.be.empty(); 59 | 60 | Solium.reset(); 61 | done(); 62 | }); 63 | 64 | }); 65 | 66 | describe("[RULE] value-in-payable: Rejections", function() { 67 | 68 | it("should reject all functions that access 'msg.value' and don't have the 'payable' modifier", function(done) { 69 | let code = [ 70 | "function pay() public { require(msg.value >= MIN_PRICE); }" 71 | ]; 72 | let errors; 73 | 74 | code = code.map(function(item){return toContract(item);}); 75 | 76 | errors = Solium.lint(code[0], userConfig); 77 | errors.should.be.Array(); 78 | errors.should.have.size(1); 79 | 80 | Solium.reset(); 81 | done(); 82 | }); 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /test/lib/rules/visibility-first/visibility-first.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for visibility-first rule. 3 | * @author Harrison Beckerich 4 | */ 5 | 6 | "use strict"; 7 | 8 | const Solium = require("../../../../lib/solium"), 9 | { toContract } = require("../../../utils/wrappers"); 10 | 11 | const userConfig = { 12 | "rules": { 13 | "visibility-first": "warning" 14 | } 15 | }; 16 | 17 | describe("[RULE] visibility-first: Acceptances", () => { 18 | it("accepts valid contract names", done => { 19 | let code = [ 20 | "function test() public onlyOwner modA modB modC modD private modE {}", 21 | "function test() public onlyOwner {}", 22 | "function test() external onlyOwner {}", 23 | "function test() internal onlyOwner {}", 24 | "function test() private onlyOwner {}", 25 | "function test() onlyOwner {}", 26 | "function test() public {}", 27 | "function test() external {}", 28 | "function test() internal {}", 29 | "function test() private {}", 30 | "function test() {}" 31 | ]; 32 | let errors; 33 | 34 | code = code.map(item => toContract(item)); 35 | 36 | code.forEach(snip => { 37 | errors = Solium.lint(snip, userConfig); 38 | errors.should.be.Array(); 39 | errors.should.be.empty(); 40 | }); 41 | 42 | Solium.reset(); 43 | done(); 44 | }); 45 | }); 46 | 47 | 48 | describe("[RULE] visibility-first: Rejections", () => { 49 | it("rejects invalid struct names", done => { 50 | let code = [ 51 | "function test() onlyOwner modA modB modC public {}", 52 | "function test() onlyOwner modA modB modC external modD modE {}", 53 | "function test() onlyOwner modA modB modC internal modD modE private {}", 54 | "function test() onlyOwner public {}", 55 | "function test() onlyOwner external {}", 56 | "function test() onlyOwner internal {}", 57 | "function test() onlyOwner private {}" 58 | ]; 59 | let errors; 60 | 61 | code = code.map(item => toContract(item)); 62 | 63 | code.forEach(snip => { 64 | errors = Solium.lint(snip, userConfig); 65 | errors.should.be.Array(); 66 | errors.should.have.size(1); 67 | }); 68 | 69 | Solium.reset(); 70 | done(); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/lib/utils/js-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for lib/utils/js-utils.js 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let jsUtils = require("../../../lib/utils/js-utils"); 9 | 10 | describe("Test jsUtils functions", function() { 11 | 12 | it("should have a set of functions exposed as API", function(done) { 13 | jsUtils.should.have.ownProperty("isStrictlyObject"); 14 | jsUtils.isStrictlyObject.should.be.type("function"); 15 | 16 | done(); 17 | }); 18 | 19 | it("isStrictlyObject: should correctly classify whether argument is a non-array, non-null object", function(done) { 20 | let iso = jsUtils.isStrictlyObject; 21 | 22 | iso().should.equal(false); 23 | iso(100).should.equal(false); 24 | iso(null).should.equal(false); 25 | iso("foo").should.equal(false); 26 | iso([1, 2]).should.equal(false); 27 | 28 | iso({}).should.equal(true); 29 | 30 | done(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/lib/utils/node-event-generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for lib/utils/node-event-generator.js 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | let EventGenerator = require("../../../lib/utils/node-event-generator"), 9 | Solium = require("../../../lib/solium"); 10 | 11 | describe("Testing EventGenerator instance for exposed functionality", function() { 12 | 13 | it("should create a new instance of EventGenerator and expose a set of functions", function(done) { 14 | let generator = new EventGenerator(Solium); 15 | 16 | generator.should.be.type("object"); 17 | generator.should.be.instanceof(EventGenerator); 18 | 19 | generator.should.have.ownProperty("emitter"); 20 | generator.emitter.constructor.name.should.equal("EventEmitter"); 21 | 22 | generator.should.have.property("enterNode"); 23 | generator.enterNode.should.be.type("function"); 24 | 25 | generator.should.have.property("leaveNode"); 26 | generator.leaveNode.should.be.type("function"); 27 | 28 | done(); 29 | }); 30 | 31 | it("should behave as expected upon calling enterNode ()", function(done) { 32 | let successCountDown = 2; 33 | let generator = new EventGenerator(Solium); 34 | 35 | Solium.on("TestNodeEnter", function(emitted) { 36 | emitted.should.have.ownProperty("node"); 37 | emitted.node.should.have.ownProperty("type"); 38 | emitted.node.type.should.equal("TestNodeEnter"); 39 | emitted.should.have.ownProperty("exit"); 40 | emitted.exit.should.equal(false); 41 | 42 | successCountDown--; 43 | !successCountDown && (Solium.reset() || done()); 44 | }); 45 | 46 | Solium.on("TestNodeLeave", function(emitted) { 47 | emitted.should.have.ownProperty("node"); 48 | emitted.node.should.have.ownProperty("type"); 49 | emitted.node.type.should.equal("TestNodeLeave"); 50 | emitted.should.have.ownProperty("exit"); 51 | emitted.exit.should.equal(true); 52 | 53 | successCountDown--; 54 | !successCountDown && (Solium.reset() || done()); 55 | }); 56 | 57 | generator.enterNode({ type: "TestNodeEnter" }); 58 | generator.leaveNode({ type: "TestNodeLeave" }); 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /test/lib/utils/wrappers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for test/utils/wrappers.js 3 | * @author cgewecke 4 | */ 5 | 6 | "use strict"; 7 | 8 | const wrappers = require("../../utils/wrappers"), 9 | Solium = require("../../../lib/solium"), 10 | { EOL } = require("os"); 11 | 12 | let userConfig = { 13 | "custom-rules-filename": null, 14 | rules: {} 15 | }; 16 | 17 | describe("Test wrappers", function() { 18 | 19 | it("should have a set of functions exposed as API", function(done) { 20 | 21 | wrappers.should.have.ownProperty("toContract"); 22 | wrappers.toContract.should.be.type("function"); 23 | 24 | wrappers.should.have.ownProperty("toFunction"); 25 | wrappers.toFunction.should.be.type("function"); 26 | 27 | wrappers.should.have.ownProperty("addPragma"); 28 | wrappers.toFunction.should.be.type("function"); 29 | 30 | done(); 31 | }); 32 | 33 | it("toContract: should correctly wrap a solidity statement in contract code", function(done) { 34 | let toContract = wrappers.toContract; 35 | let statement = "uint x = 1;"; 36 | let expected = 37 | `pragma solidity ^0.4.3;${EOL.repeat(3)}` + 38 | `contract Wrap {${EOL}` + 39 | "\t" + statement + EOL + 40 | "}"; 41 | 42 | let errors = Solium.lint(expected, userConfig); 43 | errors.constructor.name.should.equal("Array"); 44 | errors.length.should.equal(0); 45 | toContract(statement).should.equal(expected); 46 | 47 | Solium.reset(); 48 | done(); 49 | }); 50 | 51 | it("toFunction: should correctly wrap a solidity statement in contract/function code", function(done) { 52 | let toFunction = wrappers.toFunction; 53 | let statement = "uint x = 1;"; 54 | let expected = 55 | `pragma solidity ^0.4.3;${EOL.repeat(3)}` + 56 | `contract Wrap {${EOL}` + 57 | `\tfunction wrap() {${EOL}` + 58 | "\t\t" + statement + EOL + 59 | `\t}${EOL}` + 60 | "}"; 61 | 62 | let errors = Solium.lint(expected, userConfig); 63 | errors.constructor.name.should.equal("Array"); 64 | errors.length.should.equal(0); 65 | toFunction(statement).should.equal(expected); 66 | 67 | Solium.reset(); 68 | done(); 69 | }); 70 | 71 | it("addPragma: should correctly pre-pend a pragma statement to a solidity contract or library", function(done) { 72 | let addPragma = wrappers.addPragma; 73 | let contract = "contract Abc { }"; 74 | let expected = `pragma solidity ^0.4.3;${EOL.repeat(3)}` + contract; 75 | 76 | let errors = Solium.lint(expected, userConfig); 77 | errors.constructor.name.should.equal("Array"); 78 | errors.length.should.equal(0); 79 | addPragma(contract).should.equal(expected); 80 | 81 | Solium.reset(); 82 | done(); 83 | }); 84 | }); -------------------------------------------------------------------------------- /test/packagejson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for package.json 3 | * @author Raghav Dua 4 | */ 5 | 6 | "use strict"; 7 | 8 | const packageJSON = require("../package.json"); 9 | 10 | 11 | describe("Checking package.json", () => { 12 | 13 | it("should enforce fixed versions on certain dependencies", done => { 14 | const fixedDeps = ["solparse", "sol-digger", "sol-explore", "solium-plugin-security"]; 15 | const fixedDevDeps = ["solium-config-test", "solium-config-test-invalid-schema", 16 | "solium-config-test-invalid-syntax", "solium-plugin-test", "solium-plugin-test-invalid-schema"]; 17 | const deps = packageJSON.dependencies, devDeps = packageJSON.devDependencies; 18 | 19 | 20 | fixedDeps.forEach(fd => { 21 | /^[0-9]$/.test(deps[fd][0]).should.be.true(); 22 | }); 23 | 24 | fixedDevDeps.forEach(fdd => { 25 | /^[0-9]$/.test(devDeps[fdd][0]).should.be.true(); 26 | }); 27 | 28 | done(); 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /test/utils/wrappers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions to wrap solidity snippets with pragma, contract and/or 3 | * function code so they can be passed to solidity-parser without erroring. These allow 4 | * the test cases to be presented in an easily legible form. 5 | * @author cgewecke 6 | */ 7 | 8 | "use strict"; 9 | 10 | const { EOL } = require("os"); 11 | 12 | module.exports = { 13 | /** 14 | * Wrap a solidity statement in valid contract boilerplate. 15 | * @param {String} code Solidity snippet to wrap 16 | * @return {String} wrapped snippet 17 | */ 18 | toContract: function(code) { 19 | let pre = `pragma solidity ^0.4.3;${EOL.repeat(3)}contract Wrap {${EOL}\t`; 20 | let post = `${EOL}}`; 21 | return pre + code + post; 22 | }, 23 | 24 | /** 25 | * Wrap a solidity statement in valid contract and function boilerplate. 26 | * @param {String} code Solidity snippet 27 | * @return {String} wrapped snippet 28 | */ 29 | toFunction: function(code) { 30 | let pre = `pragma solidity ^0.4.3;${EOL.repeat(3)}contract Wrap {${EOL}\tfunction wrap() {${EOL}\t\t`; 31 | let post = `${EOL}\t}${EOL}}`; 32 | return pre + code + post; 33 | }, 34 | 35 | /** 36 | * Prepend solidity contract / library with a pragma statement 37 | * @param {String} code Solidity snippet 38 | * @return {String} snippet with pragma statement. 39 | */ 40 | addPragma: function(code) { 41 | let pre = `pragma solidity ^0.4.3;${EOL.repeat(3)}`; 42 | return pre + code; 43 | } 44 | }; --------------------------------------------------------------------------------