Repository: crytic/slither

Issue:
Detector: Check that variables follow the Solidity naming convention
Add a detector checking that the variables follow the [Solidity naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html?highlight=naming%20convention#naming-conventions)

- Severity Informational


Discussion:
Issue Status: 1. **Open** 2. Started 3. Submitted 4. Done 

<hr>

__This issue now has a funding of 100.0 DAI (100.0 USD @ $1.0/DAI)  attached to it as part of the Ethereum Community Fund via ECF Web 3.0 Infrastructure Fund fund.__

 * If you would like to work on this issue you can 'start work' [on the Gitcoin Issue Details page](https://gitcoin.co/issue/trailofbits/slither/24/1513).
* Want to chip in? Add your own contribution [here](https://gitcoin.co/issue/trailofbits/slither/24/1513).
* Questions? Checkout <a href='https://gitcoin.co/help'>Gitcoin Help</a> or the <a href='https://gitcoin.co/slack'>Gitcoin Slack</a>
* $47,907.31 more funded OSS Work available on the [Gitcoin Issue Explorer](https://gitcoin.co/explorer)


Resolving patch:
diff --git a/slither/__main__.py b/slither/__main__.py
--- a/slither/__main__.py
+++ b/slither/__main__.py
@@ -101,6 +101,7 @@ def main():
     from slither.detectors.variables.unused_state_variables import UnusedStateVars
     from slither.detectors.statements.tx_origin import TxOrigin
     from slither.detectors.statements.assembly import Assembly
+    from slither.detectors.naming_convention.naming_convention import NamingConvention
 
     detectors = [Backdoor,
                  UninitializedStateVarsDetection,
@@ -113,7 +114,8 @@ def main():
                  Suicidal,
                  UnusedStateVars,
                  TxOrigin,
-                 Assembly]
+                 Assembly,
+                 NamingConvention]
 
     from slither.printers.summary.summary import PrinterSummary
     from slither.printers.summary.quick_summary import PrinterQuickSummary
diff --git a/slither/detectors/naming_convention/__init__.py b/slither/detectors/naming_convention/__init__.py
new file mode 100644
diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py
new file mode 100644
--- /dev/null
+++ b/slither/detectors/naming_convention/naming_convention.py
@@ -0,0 +1,154 @@
+from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
+import re
+
+
+class NamingConvention(AbstractDetector):
+    """
+    Check if naming conventions are followed
+    https://solidity.readthedocs.io/en/v0.4.25/style-guide.html?highlight=naming_convention%20convention#naming_convention-conventions
+    """
+
+    ARGUMENT = 'naming-convention'
+    HELP = 'conformance to Solidity naming conventions'
+    IMPACT = DetectorClassification.INFORMATIONAL
+    CONFIDENCE = DetectorClassification.HIGH
+
+    @staticmethod
+    def is_cap_words(name):
+        return re.search('^[A-Z]([A-Za-z0-9]+)?_?$', name) is not None
+
+    @staticmethod
+    def is_mixed_case(name):
+        return re.search('^[a-z]([A-Za-z0-9]+)?_?$', name) is not None
+
+    @staticmethod
+    def is_upper_case_with_underscores(name):
+        return re.search('^[A-Z0-9_]+_?$', name) is not None
+
+    @staticmethod
+    def should_avoid_name(name):
+        return re.search('^[lOI]$', name) is not None
+
+    def detect(self):
+
+        results = []
+        for contract in self.contracts:
+
+            if self.is_cap_words(contract.name) is False:
+                info = " Contract '{}' is not in CapWords".format(contract.name)
+                self.log(info)
+
+                results.append({'vuln': 'NamingConvention',
+                                'filename': self.filename,
+                                'contract': contract.name,
+                                'sourceMapping': contract.source_mapping})
+
+            for struct in contract.structures:
+
+                if self.is_cap_words(struct.name) is False:
+                    info = " Struct '{}' is not in CapWords, Contract: '{}' ".format(struct.name, contract.name)
+                    self.log(info)
+
+                    results.append({'vuln': 'NamingConvention',
+                                    'filename': self.filename,
+                                    'contract': contract.name,
+                                    'struct': struct.name,
+                                    'sourceMapping': struct.source_mapping})
+
+            for event in contract.events:
+
+                if self.is_cap_words(event.name) is False:
+                    info = " Event '{}' is not in CapWords, Contract: '{}' ".format(event.name, contract.name)
+                    self.log(info)
+
+                    results.append({'vuln': 'NamingConvention',
+                                    'filename': self.filename,
+                                    'contract': contract.name,
+                                    'event': event.name,
+                                    'sourceMapping': event.source_mapping})
+
+            for func in contract.functions:
+
+                if self.is_mixed_case(func.name) is False:
+                    info = " Function '{}' is not in mixedCase, Contract: '{}' ".format(func.name, contract.name)
+                    self.log(info)
+
+                    results.append({'vuln': 'NamingConvention',
+                                    'filename': self.filename,
+                                    'contract': contract.name,
+                                    'function': func.name,
+                                    'sourceMapping': func.source_mapping})
+
+                for argument in func.parameters:
+
+                    if self.is_mixed_case(argument.name) is False:
+                        info = " Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \
+                            .format(argument.name, argument.name, contract.name)
+                        self.log(info)
+
+                        results.append({'vuln': 'NamingConvention',
+                                        'filename': self.filename,
+                                        'contract': contract.name,
+                                        'function': func.name,
+                                        'argument': argument.name,
+                                        'sourceMapping': argument.source_mapping})
+
+            for var in contract.state_variables:
+
+                if self.should_avoid_name(var.name):
+                    if self.is_upper_case_with_underscores(var.name) is False:
+                        info = " Variable '{}' l, O, I should not be used, Contract: '{}' " \
+                            .format(var.name, contract.name)
+                        self.log(info)
+
+                        results.append({'vuln': 'NamingConvention',
+                                        'filename': self.filename,
+                                        'contract': contract.name,
+                                        'constant': var.name,
+                                        'sourceMapping': var.source_mapping})
+
+                if var.is_constant is True:
+                    if self.is_upper_case_with_underscores(var.name) is False:
+                        info = " Constant '{}' is not in UPPER_CASE_WITH_UNDERSCORES, Contract: '{}' " \
+                            .format(var.name, contract.name)
+                        self.log(info)
+
+                        results.append({'vuln': 'NamingConvention',
+                                        'filename': self.filename,
+                                        'contract': contract.name,
+                                        'constant': var.name,
+                                        'sourceMapping': var.source_mapping})
+                else:
+                    if self.is_mixed_case(var.name) is False:
+                        info = " Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name)
+                        self.log(info)
+
+                        results.append({'vuln': 'NamingConvention',
+                                        'filename': self.filename,
+                                        'contract': contract.name,
+                                        'variable': var.name,
+                                        'sourceMapping': var.source_mapping})
+
+            for enum in contract.enums:
+                if self.is_cap_words(enum.name) is False:
+                    info = " Enum '{}' is not in CapWords, Contract: '{}' ".format(enum.name, contract.name)
+                    self.log(info)
+
+                    results.append({'vuln': 'NamingConvention',
+                                    'filename': self.filename,
+                                    'contract': contract.name,
+                                    'enum': enum.name,
+                                    'sourceMapping': enum.source_mapping})
+
+            for modifier in contract.modifiers:
+                if self.is_mixed_case(modifier.name) is False:
+                    info = " Modifier '{}' is not in mixedCase, Contract: '{}' ".format(modifier.name, contract.name)
+                    self.log(info)
+
+                    results.append({'vuln': 'NamingConvention',
+                                    'filename': self.filename,
+                                    'contract': contract.name,
+                                    'modifier': modifier.name,
+                                    'sourceMapping': modifier.source_mapping})
+
+        return results


Test patch:
diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh
--- a/scripts/travis_test.sh
+++ b/scripts/travis_test.sh
@@ -9,7 +9,7 @@ fi
 
 # contains also the test for the suicidal detector
 slither tests/backdoor.sol --disable-solc-warnings
-if [ $? -ne 2 ]; then
+if [ $? -ne 3 ]; then
     exit 1
 fi
 
@@ -24,7 +24,7 @@ if [ $? -ne 1 ]; then
 fi
 
 slither tests/reentrancy.sol --disable-solc-warnings
-if [ $? -ne 1 ]; then
+if [ $? -ne 4 ]; then
     exit 1
 fi
 
@@ -53,13 +53,19 @@ if [ $? -ne 2 ]; then
     exit 1
 fi
 
+
 slither tests/inline_assembly_contract.sol --disable-solc-warnings
-if [ $? -ne 2 ]; then
+if [ $? -ne 3 ]; then
     exit 1
 fi
 
 slither tests/inline_assembly_library.sol --disable-solc-warnings
-if [ $? -ne 3 ]; then
+if [ $? -ne 6 ]; then
+    exit 1
+fi
+
+slither tests/naming_convention.sol --disable-solc-warnings
+if [ $? -ne 10 ]; then
     exit 1
 fi
 
@@ -80,4 +86,4 @@ python examples/scripts/variable_in_condition.py examples/scripts/variable_in_co
 if [ $? -ne 0 ]; then
     exit 1
 fi
-exit 0
+exit 0
\ No newline at end of file
diff --git a/tests/naming_convention.sol b/tests/naming_convention.sol
new file mode 100644
--- /dev/null
+++ b/tests/naming_convention.sol
@@ -0,0 +1,60 @@
+pragma solidity ^0.4.24;
+
+contract naming {
+
+    enum Numbers {ONE, TWO}
+    enum numbers {ONE, TWO}
+
+    uint constant MY_CONSTANT = 1;
+    uint constant MY_other_CONSTANT = 2;
+
+    uint Var_One = 1;
+    uint varTwo = 2;
+
+    struct test {
+
+    }
+
+    struct Test {
+
+    }
+
+    event Event_(uint);
+    event event_(uint);
+
+    function getOne() constant returns (uint)
+    {
+        return 1;
+    }
+
+    function GetOne() constant returns (uint)
+    {
+        return 1;
+    }
+
+    function setInt(uint number1, uint Number2)
+    {
+
+    }
+
+
+    modifier CantDo() {
+        _;
+    }
+
+    modifier canDo() {
+        _;
+    }
+}
+
+contract Test {
+
+}
+
+contract T {
+    uint k = 1;
+
+    uint constant M = 1;
+
+    uint l = 1;
+}