diff --git a/doc/commenting.rst b/doc/commenting.rst index cb1ec68a9..572bbe5e4 100644 --- a/doc/commenting.rst +++ b/doc/commenting.rst @@ -288,45 +288,21 @@ The rule definition must follow the syntax which is defined by a regex search wh .. code-block:: python - 'is_greater_than': r'\b(is_greater_than)\b: \d+(\.\d+)?' # is_greater_than: 1 - 'is_greater_than_or_equal_to': r'\b(is_greater_than_or_equal_to)\b: \d+(\.\d+)?' # is_greater_than_or_equal_to: 1 - 'is_less_than_or_equal_to': r'\b(is_less_than_or_equal_to)\b: \d+(\.\d+)?' # is_less_than_or_equal_to: 10 - 'is_less_than': r'\b(is_less_than)\b: \d+(\.\d+)?' # is_less_than: 2 - 'is_equal': r'\b(is_equal)\b: \d+(\.\d+)?' # is_equal: 1 - 'is_different': r'\b(is_different)\b: \d+(\.\d+)?' # is_different: 2 - 'is_global_unique': r'\b(is_global_unique)\b' # is_global_unique - 'refers': r'\b(refers)\b' # refers - 'is_iso_country_code': r'\b(is_iso_country_code)\b' # is_iso_country_code - 'first_element': r'\b(first_element)\b: \{.*: \d+\.\d+\}' # first_element: {is_equal: 0.13, is_greater_than: 0.13} - 'last_element': r'\b(last_element)\b: \{.*: \d+\.\d+\}' # last_element: {is_equal: 0.13, is_greater_than: 0.13} - 'is_optional': r'\b(is_optional)\b' # is_optional - 'check_if': r'\b(check_if)\b: \[\{.*: \d+(\.\d+)?, target: .*}, \{do_check: \{.*: \d+(\.\d+)?}}]' # check_if: [{is_equal: 2, is_greater_than: 3, target: this.y}, {do_check: {is_equal: 1, is_less_than: 3}}] - -You can check the correctness of these regular expression on `regex101 `_. - - -.. is_greater_than: 2 -.. is_greater_than: 2.23 -.. is_greater_than_or_equal_to: 1 -.. is_greater_than_or_equal_to: 1.12 -.. is_less_than_or_equal_to: 10 -.. is_less_than_or_equal_to: 10.123 -.. is_less_than: 2 -.. is_less_than: 2.321 -.. is_equal: 1 -.. is_equal: 1.312 -.. is_different: 2 -.. is_different: 2.2122 -.. is_global_unique -.. refers -.. is_iso_country_code -.. first_element: {is_equal: 3, is_greater: 2} -.. first_element: {is_equal: 0.13, is_greater: 0.13} -.. last_element: {is_equal: 3, is_greater: 2} -.. last_element: {is_equal: 0.13, is_greater: 0.13} -.. check_if: [{is_equal: 2, is_greater_than: 3, target: this.y}, {do_check: {is_equal: 1, is_less_than: 3}}] -.. is_set - + 'is_greater_than': r'^[ ]\b(is_greater_than)\b: ([\s\d]+)$' # is_greater_than: 1 + 'is_greater_than_or_equal_to': r'^[ ]\b(is_greater_than_or_equal_to)\b: ([\s\d]+)$' # is_greater_than_or_equal_to: 1 + 'is_less_than_or_equal_to': r'^[ ]\b(is_less_than_or_equal_to)\b: ([\s\d]+)$' # is_less_than_or_equal_to: 10 + 'is_less_than': r'^[ ]\b(is_less_than)\b: ([\s\d]+)$' # is_less_than: 2 + 'is_equal': r'^[ ]\b(is_equal_to)\b: ([\s\d]+)$' # is_equal_to: 1 + 'is_different': r'^[ ]\b(is_different_to)\b: ([\s\d]+)$' # is_different_to: 2 + 'is_global_unique': r'^[ ]\b(is_globally_unique)\b' # is_globally_unique + 'refers': r'^[ ]\b(refers_to)\b' # refers_to: DetectedObject + 'is_iso_country_code': r'^[ ]\b(is_iso_country_code)\b' # is_iso_country_code + 'first_element': r'^[ ]\b(first_element)\b' # first_element height is_equal_to 0.13 + 'last_element': r'^[ ]\b(last_element)\b' # last_element width is_equal_to 0.13 + 'check_if': r'^[ ](\bcheck_if\b)(.*\belse do_check\b)' # check_if this.type is_equal_to 2 else do_check is_set + +You can check the correctness of these regular expression on `regex101 `_. + Commenting with doxygen references ------------------------------------ diff --git a/rules.yml b/rules.yml index 99c8ff5be..473bf51b2 100644 --- a/rules.yml +++ b/rules.yml @@ -1,12 +1,12 @@ -is_greater_than: '\b(is_greater_than)\b: ' -is_greater_than_or_equal_to: '\b(is_greater_than_or_equal_to)\b: ' -is_less_than_or_equal_to: '\b(is_less_than_or_equal_to)\b: ' -is_less_than: '\b(is_less_than)\b: ' -is_equal_to: '\b(is_equal_to)\b: ' -is_different_to: '\b(is_different_to)\b: ' -is_globally_unique: '\b(is_globally_unique)\b' -refers_to: '\b(refers_to)\b' -is_iso_country_code: '\b(is_iso_country_code)\b' -first_element: '\b(first_element)\b' -last_element: '\b(last_element)\b' -check_if: '(\bcheck_if\b)(.*\belse do_check\b)' \ No newline at end of file +is_greater_than: '^[ ]\b(is_greater_than)\b: ([\s\d]+)$' +is_greater_than_or_equal_to: '^[ ]\b(is_greater_than_or_equal_to)\b: ([\s\d]+)$' +is_less_than_or_equal_to: '^[ ]\b(is_less_than_or_equal_to)\b: ([\s\d]+)$' +is_less_than: '^[ ]\b(is_less_than)\b: ([\s\d]+)$' +is_equal_to: '^[ ]\b(is_equal_to)\b: ([\s\d]+)$' +is_different_to: '^[ ]\b(is_different_to)\b: ([\s\d]+)$' +is_globally_unique: '^[ ]\b(is_globally_unique)\b' +refers_to: '^[ ]\b(refers_to)\b' +is_iso_country_code: '^[ ]\b(is_iso_country_code)\b' +first_element: '^[ ]\b(first_element)\b' +last_element: '^[ ]\b(last_element)\b' +check_if: '^[ ](\bcheck_if\b)(.*\belse do_check\b)' \ No newline at end of file diff --git a/tests/test_rules.py b/tests/test_rules.py new file mode 100644 index 000000000..c5c0036f7 --- /dev/null +++ b/tests/test_rules.py @@ -0,0 +1,133 @@ +import re +import glob +import unittest +import yaml + +PROTO_FILES = glob.glob("*.proto") + +class TestRules(unittest.TestCase): + """ Test class for units documentation. """ + + def test_rules_compliance(self): + ''' Test rule compliance syntax of proto files. ''' + + with open(r'rules.yml') as rules_file: + RULES_DICT = yaml.load(rules_file, Loader=yaml.BaseLoader) + + for file in PROTO_FILES: + with open(file, "rt") as fin, self.subTest(file=file): + line_number = 0 + numMessage = 0 + lineruleCount = 0 + foundruleCount = 0 + saveStatement = "" + isEnum = False + + for line in fin: + line_number += 1 + + # Divide statement and comment. Concatenate multi line statements. + # Search for comment ("//"). + matchComment = re.search("//", line) + if matchComment is not None: + statement = line[:matchComment.start()] + comment = line[matchComment.end():] + else: + statement = line + comment = "" + + # Add part of the statement from last line. + statement = saveStatement + " " + statement + saveStatement = "" + + # New line is not necessary. Remove for a better output. + statement = statement.replace("\n", "") + comment = comment.replace("\n", "") + + # Is statement complete + matchSep = re.search(r"[{};]", statement) + if matchSep is None: + saveStatement = statement + statement = "" + else: + saveStatement = statement[matchSep.end():] + statement = statement[:matchSep.end()] + + # Search for "message". + matchMessage = re.search(r"\bmessage\b", statement) + if matchMessage is not None: + # a new message or a new nested message + numMessage += 1 + self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: message should not have rules for '{statement.strip()}'") + endOfLine = statement[matchMessage.end():] + matchName = re.search(r"\b\w[\S]*\b", endOfLine) + + elif re.search(r"\bextend\b", statement) is not None: + # treat extend as message + numMessage += 1 + else: + # Check field names + if numMessage > 0: + matchName = re.search(r"\b\w[\S]*\b\s*=", statement) + if matchName is not None: + checkName = statement[matchName.start():matchName.end()-1] + # Check field message type (remove field name) + type = statement.replace(checkName, "") + matchName = re.search(r"\b\w[\S\.]*\s*=", type) + + if isEnum: + matchName = re.search(r"\b\w[\S:]+\b", statement) + if matchName is not None: + checkName = statement[matchName.start():matchName.end()] + self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: enum field should not have rules for '{statement.strip()}'") + + # Search for "enum". + matchEnum = re.search(r"\benum\b", statement) + if matchEnum is not None: + isEnum = True + endOfLine = statement[matchEnum.end():] + matchName = re.search(r"\b\w[\S]*\b", endOfLine) + if matchName is not None: + self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: enum should not have rules for '{statement.strip()}'") + + # Search for a closing brace. + matchClosingBrace = re.search("}", statement) + if numMessage > 0 and matchClosingBrace is not None: + numMessage -= 1 + + if isEnum is True and matchClosingBrace is not None: + isEnum = False + + if matchComment is not None: + if re.search(r"^[ ]\\\bendrules\b$", comment) is not None: + endRule = True + + if re.search(r"^[ ]\\\brules\b$", comment) is not None: + hasRule = True + lineruleCount = 0 + foundruleCount = 0 + + if not endRule and comment != '': + for rulename, ruleregex in RULES_DICT.items(): + if re.search(ruleregex, comment): + foundruleCount += 1 + + elif len(saveStatement) == 0: + if numMessage > 0: + if statement.find(";") != -1: + statement = statement.strip() + self.assertFalse(hasRule and lineruleCount != foundruleCount and endRule and lineruleCount-foundruleCount-1>0, file + " in line " + str(line_number) + ": "+str(lineruleCount-foundruleCount-1)+" defined rule(s) does not exists for: '"+statement+"'") + self.assertFalse(hasRule and not endRule, file + " in line " + str(line_number) + ": \\endrules statement does not exists for: '"+statement+"'. Check spacing and rule syntax.") + self.assertFalse(not hasRule and endRule, file + " in line " + str(line_number) + ": \\rules statement does not exists for: '"+statement+"'. Check spacing and rule syntax.") + self.assertFalse(not hasRule and not endRule and lineruleCount < foundruleCount, file + " in line " + str(line_number) + ": rules found but no statements (\\rules and \\endrules) around it for: '"+statement+"'") + lineruleCount = 0 + foundruleCount = 0 + + hasRule = False + endRule = False + + if hasRule and not endRule or not hasRule and endRule: + lineruleCount += 1 + +if __name__ == '__main__': + unittest.main() \ No newline at end of file