Esempio n. 1
0
    def check_good_name(name):
        # good name
        good_name = filename

        # remove path from filename (if needed)
        if good_name.count("/") > 0:
            good_name = good_name[good_name.rfind("/") + 1:]
        if good_name.count("\\") > 0:
            good_name = good_name[good_name.rfind("\\") + 1:]

        # remove .h extension
        good_name = good_name[:good_name.find(".h")]

        # good name is all uppercase
        good_name = good_name.upper()

        # macros must not include '-' character, while files can
        # convert '-' to '_', which is valid for C++ preprocessor
        good_name = good_name.replace('-', '_')

        # a good name has format [PREFIX_]FILENAME_H
        good_name_re = re.compile("RAMSES_" + "([a-zA-Z0-9][a-zA-Z0-9]*_)*" + good_name + "_H$")

        valid = re.match(good_name_re, name) is not None

        if not valid:
            common.log_warning("check_header_guards", filename, 1,
                               f"header guard '{name}' has invalid format, expected format: 'RAMSES_[PREFIX_]{good_name}_H'")

        return valid
def check_last_line_newline(filename, file_lines):
    """
    Checks if file ends in newline
    """
    if len(file_lines) > 0 and file_lines[-1] != "\n":
        common.log_warning("check_last_line_newline", filename,
                           len(file_lines), "no newline at end of file")
def check_no_spacing_line_end(filename, file_lines):
    """
    Checks that lines do not end with unnecessary white space characters

    """
    for i in range(len(file_lines)):
        if re.search(" $", file_lines[i]):
            common.log_warning("check_no_spacing_line_end", filename, i + 1, "unneeded space(s) at end of line")
def check_tabs_no_spaces(filename, file_lines):
    """
    Checks if any tab character "\t" exists in the file

    """
    for i in range(len(file_lines)):
        if file_lines[i].count("\t") > 0:
            common.log_warning("check_tabs_no_spaces", filename, i + 1, "tab character (\\t) found, use 4 spaces instead", file_lines[i].strip(" "))
Esempio n. 5
0
def check_doxygen(filename, file_contents, file_lines):
    """ Check doxygen files """

    for line_number, line in enumerate(file_lines):
        if g_re_header_tag.search(line):
            cc.log_warning("check_doxygen", filename, line_number + 1,
                           "usage html tag in doxygen header line",
                           file_lines[line_number].strip(" \t\r\n"))
Esempio n. 6
0
def check_license_for_file(file_name, file_contents, solution_path):
    """
    Check license for given file name.
    """

    license_re = G_RE_LICENSE_TEMPLATE_OPEN

    if not check_specific_license_in_file(file_name, file_contents, license_re):
        common.log_warning("check_license_in_file", file_name, 1, "no valid license found")
Esempio n. 7
0
def check_file_attributes(filename):
    """ Check for unwanted file attributes """

    mode = os.stat(filename).st_mode
    if bool(mode
            & stat.S_IXUSR) or bool(mode
                                    & stat.S_IXGRP) or bool(mode
                                                            & stat.S_IXOTH):
        cc.log_warning("check_file_attributes", filename, 0,
                       "may not have file executable bits set", "")
def check_curly_braces_alone_on_line(filename, file_contents,
                                     clean_file_contents, file_lines,
                                     clean_file_lines):
    """
    Checks that there is no unnecessary code on same line with curly braces.
    """

    # if: not at end
    re_if = re.compile(r'\W+if\s*\(.*\)\s*(?:\{|.*\}|.*;)$')

    # else: neither before nor after
    re_else = re.compile(r'}\s*else|else\s*{')

    # switch: not at end
    re_switch = re.compile(r'\W+switch\s*\(.*\)\s*(?:\{|.*\}|.*;)$')

    # while: before ok, not after
    re_while = re.compile(r'\W+while\s*\(.*\)\s*{')

    # for: not at end
    re_for = re.compile(r'\W+for\s*\(.*\)\s*(?:\{|.*\}|.*\)\s*;)$')

    # NOTE(tobias) not checking function definitions because too many false positives with lambdas, enums ...

    for i in range(len(clean_file_lines)):
        line = clean_file_lines[i]

        if re_if.search(line):
            common.log_warning(
                "check_curly_braces_alone_on_line", filename, i + 1,
                "curly brace or statement on same line with if",
                file_lines[i].strip(" "))
        elif re_else.search(line):
            common.log_warning("check_curly_braces_alone_on_line", filename,
                               i + 1, "curly brace on same line with else",
                               file_lines[i].strip(" "))
        elif re_switch.search(line):
            common.log_warning(
                "check_curly_braces_alone_on_line", filename, i + 1,
                "curly brace or statement on same line with switch",
                file_lines[i].strip(" "))
        elif re_while.search(line):
            common.log_warning("check_curly_braces_alone_on_line", filename,
                               i + 1, "curly brace after while",
                               file_lines[i].strip(" "))
        elif re_for.search(line):
            common.log_warning(
                "check_curly_braces_alone_on_line", filename, i + 1,
                "curly brace or statement on same line with for",
                file_lines[i].strip(" "))
def check_single_definition_on_line(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
    """
    Checks if a line contains several variable definitions

    """
    # remove arrays
    # an array regex starts with "{", ends with "}" and contains anything in between
    # an array of arrays is removed recursively inside out
    array_re = re.compile(r'\{[^ {};]*\}')
    clean_file_contents = common.clean_string_from_regex(clean_file_contents, array_re, '')

    # remove brackets
    array_brackets_re = re.compile(r'\[((?!\[|\]|;).)*\]')
    clean_file_contents = common.clean_string_from_regex(clean_file_contents, array_brackets_re, '')

    # remove angle brackets
    template_re = re.compile(r'<((?![<>;]).)*>')
    clean_file_contents = common.clean_string_from_regex(clean_file_contents, template_re, '')

    # variable name can be preceeded by * for pointers or & for references, can be followed by an assignment
    var_decl_re_text = r"""(
                                (?:(?:&|\*+)\s*)?               # can start by & (for reference defs) and/or any number of * (for pointer defs)
                                \w+                             # followed by identifier
                                (?:\s*\([\w\s,]*\))?            # can be followed by constructor call
                                (?:\s*=\w+\s*|\s*\{[\w\s,]*\})? # or can be followed by an assignment of simple values, or value lists (for arrays)
                            )"""

    # types can have qualifiers, be nested or in namespaces
    type_re_text = r"(?:const\s+|volatile\s+)?\w+(?:::\w+)*(?:&?|\**)"

    # it is enough to have
    several_defs_re_text = r"""
                                \b\s*{0}
                                \s+{1}
                                (?:\s*,\s*{1})+;
                            """.format(type_re_text, var_decl_re_text)

    several_defs_re = re.compile(several_defs_re_text, re.MULTILINE | re.VERBOSE)

    inside_for_header_re = re.compile(r"^\s*for \s*\(")

    for match in re.finditer(several_defs_re, clean_file_contents):
        line_number = clean_file_contents.count("\n", 0, match.end())
        if re.search(inside_for_header_re, file_lines[line_number]) is None:
            common.log_warning("check_single_definition_on_line", filename, line_number + 1,
                               "several definitions on same line", file_lines[line_number].strip(" \t\r\n"))
Esempio n. 10
0
def check_license_for_file(file_name, file_contents, solution_path):
    """
    Check license for given file name. Default ot open except it is in
    G_PROP_FILES list.
    """

    license_re = G_RE_LICENSE_TEMPLATE_OPEN

    for pattern in G_PROP_FILES:
        full_pattern = os.path.join(solution_path, pattern)
        if file_name.startswith(full_pattern):
            license_re = G_RE_LICENSE_TEMPLATE_PROP
            break

    if not check_specific_license_in_file(file_name, file_contents,
                                          license_re):
        common.log_warning("check_license_in_file", file_name, 1,
                           "no valid license found")
def check_single_statement_on_line(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
    """
    Checks if there are several statements on the same line

    """
    open_parantheses_count = 0
    semicolon_inside_oneline_lambda_re = re.compile(r'\[.*\].*\{.*;.*\}')

    for i in range(len(file_lines)):
        line = clean_file_lines[i]

        open_parantheses_count += line.count("(") - line.count(")")

        # check if there are several semicolons on the line and that is not in the middle of a for statement
        if line.count(";") > 1 and open_parantheses_count == 0:
            # check that there is NO right parentheses after semicolon
            if line.rfind(";") > line.rfind(")") and not re.search(semicolon_inside_oneline_lambda_re, line):
                common.log_warning("check_single_statement_on_line", filename, i + 1, "several statements on same line", file_lines[i].strip(" "))
Esempio n. 12
0
def check_header_guards(filename, file_contents):
    """
    Check if a header files contains valid header guards:
        1- #ifndef GUARDNAME directly after license that has a guard name identical to file name in uppercase letters,
           underscores at beginnings and end are tollerable
        2- directly followed by #define that matches the guard name of #ifndef exactly
        3- the file has to end with an #endif

    """
    is_h_file = filename[-2:] == ".h"
    is_hpp_file = filename[-4:] == ".hpp"
    if not (is_h_file or is_hpp_file):
        return

    # remove strings
    string_re = re.compile(r'"((?!((?<!((?<!\\)\\))")).)*"', re.DOTALL)
    file_contents = common.clean_string_from_regex(file_contents, string_re, 'x')

    # remove multi-line comments
    ml_comment_re = re.compile(r'(\s*)/\*((?!\*/).)*\*/', re.DOTALL)
    file_contents = common.clean_string_from_regex(file_contents, ml_comment_re, '')

    # remove single line comments
    sl_comment_re = re.compile("//.*$", re.MULTILINE)
    file_contents = re.sub(sl_comment_re, '', file_contents)

    # remove any padding white space characters
    file_contents = file_contents.strip(" \t\n\f\r")

    # find the pragma once guard
    pragma_re = re.compile(r"(\s*)#pragma(\s+)once(\s*)", re.MULTILINE)
    pragma_match = re.findall(pragma_re, file_contents)
    if len(pragma_match) == 0:
        common.log_warning("check_header_guards", filename, 1, "header guard missing")

    if len(pragma_match) > 1:
        common.log_warning("check_header_guards", filename, 1, "more than one occurance of header guard found")

    return None
Esempio n. 13
0
def check_enum_style(filename, clean_file_contents):
    """
    Check that enums have
    - E prefix on name if old enum
    - name is CamelCase
    - enumerators have name prefix if old enum
    - enumerators have no name prefix if enum class
    """

    # enum [class] <name> [: maybe type] { <values> }
    enum_re = re.compile(
        r'(?<!\w)enum\s+(class\s+)?(\w+)\s*(?::\s*\w+\s*])?{((\s|\S)*?)}')

    for enum_match in re.finditer(enum_re, clean_file_contents):
        line_number = clean_file_contents[:enum_match.start()].count("\n")

        g = enum_match.groups()
        is_enum_class = g[0] is not None
        enum_name = g[1]
        enum_values = [
            m for m in [n.strip() for n in g[2].split('\n')] if m != ''
        ]

        # old enum must start with an 'E'
        if not is_enum_class and not enum_name.startswith('E'):
            common.log_warning("check_enum_style", filename, line_number,
                               "enum must begin with 'E': " + enum_name)
        # must be camel case
        if enum_name.upper() == enum_name:
            common.log_warning("check_enum_style", filename, line_number,
                               "enum must be CamelCase: " + enum_name)

        # old enum has prefix on values, enum class does NOT have name prefix
        for v in enum_values:
            if is_enum_class:
                if v.startswith(enum_name):
                    common.log_warning(
                        "check_enum_style", filename, line_number,
                        "enum class value may not begin with " + enum_name +
                        ": " + v)
            else:
                if not v.startswith(enum_name):
                    common.log_warning(
                        "check_enum_style", filename, line_number,
                        "enum value must begin with " + enum_name + "_ : " + v)
Esempio n. 14
0
def check_deprecated(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
    """ Check for usage of deprecated constructs """

    # check for old mock syntax
    for line_number, line in enumerate(clean_file_lines):
        if g_re_deprecated_mock_syntax.search(line):
            common.log_warning("check_deprecated", filename, line_number + 1,
                               "usage of old googletest mock syntax", file_lines[line_number].strip(" \t\r\n"))

    # check for unwanted includes includes
    for line_number, line in enumerate(file_lines):
        if g_re_unwanted_gtest_include.search(line):
            common.log_warning("check_deprecated", filename, line_number + 1,
                               "usage of non-standard gtest include, use '#include \"gtest/gtest.h\"' instead", file_lines[line_number].strip(" \t\r\n"))
        if g_re_unwanted_gmock_include.search(line):
            common.log_warning("check_deprecated", filename, line_number + 1,
                               "usage of non-standard gmock include, use '#include \"gmock/gmock.h\"' instead", file_lines[line_number].strip(" \t\r\n"))
        if g_re_unwanted_fmt_include.search(line):
            common.log_warning("check_deprecated", filename, line_number + 1,
                               "usage of unwanted fmt include, use '#include \"fmt/format.h\"' instead", file_lines[line_number].strip(" \t\r\n"))
Esempio n. 15
0
def check_header_guards(filename, file_contents):
    """
    Check if a header files contains valid header guards:
        1- #ifndef GUARDNAME directly after license that has a guard name identical to file name in uppercase letters,
           underscores at beginnings and end are tollerable
        2- directly followed by #define that matches the guard name of #ifndef exactly
        3- the file has to end with an #endif

    """
    is_h_file = filename[-2:] == ".h"
    is_hpp_file = filename[-4:] == ".hpp"
    if not (is_h_file or is_hpp_file):
        return

    # remove strings
    string_re = re.compile(r'"((?!((?<!((?<!\\)\\))")).)*"', re.DOTALL)
    file_contents = common.clean_string_from_regex(file_contents, string_re, 'x')

    # remove multi-line comments
    ml_comment_re = re.compile(r'(\s*)/\*((?!\*/).)*\*/', re.DOTALL)
    file_contents = common.clean_string_from_regex(file_contents, ml_comment_re, '')

    # remove single line comments
    sl_comment_re = re.compile("//.*$", re.MULTILINE)
    file_contents = re.sub(sl_comment_re, '', file_contents)

    # remove any padding white space characters
    file_contents = file_contents.strip(" \t\n\f\r")

    def check_good_name(name):
        # good name
        good_name = filename

        # remove path from filename (if needed)
        if good_name.count("/") > 0:
            good_name = good_name[good_name.rfind("/") + 1:]
        if good_name.count("\\") > 0:
            good_name = good_name[good_name.rfind("\\") + 1:]

        # remove .h extension
        good_name = good_name[:good_name.find(".h")]

        # good name is all uppercase
        good_name = good_name.upper()

        # macros must not include '-' character, while files can
        # convert '-' to '_', which is valid for C++ preprocessor
        good_name = good_name.replace('-', '_')

        # a good name has format [PREFIX_]FILENAME_H
        good_name_re = re.compile("RAMSES_" + "([a-zA-Z0-9][a-zA-Z0-9]*_)*" + good_name + "_H$")

        valid = re.match(good_name_re, name) is not None

        if not valid:
            common.log_warning("check_header_guards", filename, 1,
                               f"header guard '{name}' has invalid format, expected format: 'RAMSES_[PREFIX_]{good_name}_H'")

        return valid

    # try pragma once and early out if found
    pragma_once_re = re.compile(r"(\s*)#pragma once\n", re.MULTILINE)
    pragma_once_match = re.match(pragma_once_re, file_contents)
    if pragma_once_match is not None:
        return None

    # find the ifndef guard
    ifndef_re = re.compile(r"(\s*)#ifndef .*", re.MULTILINE)
    ifndef_match = re.match(ifndef_re, file_contents)
    if ifndef_match is None:
        common.log_warning("check_header_guards", filename, 1, "header guard missing")
        return None

    # get guard name
    ifndef_match_text = file_contents[:ifndef_match.end()]
    ifndef_match_text = ifndef_match_text.strip(" \n\r\f\t")
    splitted_match_text = ifndef_match_text.split(" ")
    ifndef_guard_name = splitted_match_text[1] if len(splitted_match_text) > 1 else ""

    if not check_good_name(ifndef_guard_name):
        return None

    # check uniqueness
    if ifndef_guard_name in g_header_guard_string_map:
        common.log_warning("check_header_guards", filename, 1,
                           f"Header guard name '{ifndef_guard_name}' already used in file {g_header_guard_string_map[ifndef_guard_name]}")
        return None
    g_header_guard_string_map[ifndef_guard_name] = filename

    file_contents = file_contents[ifndef_match.end():]

    # find the define guard
    define_re = re.compile(r"(\s*)#define .*")
    define_match = re.match(define_re, file_contents)
    if define_match is None:
        common.log_warning("check_header_guards", filename, 1,
                           f"header file does not contain a #define guard at the beginning that matches #ifndef {ifndef_guard_name}")
        return None

    # get guard name
    define_match_text = file_contents[:define_match.end()]
    define_match_text = define_match_text.strip(" \n\r\f\t")
    splitted_match_text = define_match_text.split(" ")
    define_guard_name = splitted_match_text[1] if len(splitted_match_text) > 1 else ""

    # check if #ifndef and #define have identical guard names
    if define_guard_name != ifndef_guard_name:
        common.log_warning("check_header_guards", filename, 1,
                           f"header guard names do not match :(#ifndef {ifndef_guard_name}) and (#define {define_guard_name})")

    # find the endif guard
    endif_re = re.compile(r"#endif.*(\s*)$")
    endif_match = re.search(endif_re, file_contents)
    if endif_match is None:
        common.log_warning("check_header_guards", filename, 1, "header file does not end with #endif at last line")
        return None
Esempio n. 16
0
def check_deprecated(filename, file_contents, clean_file_contents, file_lines,
                     clean_file_lines):
    """ Check for usage of deprecated constructs """

    # check for old mock syntax
    for line_number, line in enumerate(clean_file_lines):
        if g_re_deprecated_mock_syntax.search(line):
            common.log_warning("check_deprecated", filename, line_number + 1,
                               "usage of old googletest mock syntax",
                               file_lines[line_number].strip(" \t\r\n"))

    # check for unwanted includes includes
    for line_number, line in enumerate(file_lines):
        if g_re_unwanted_gtest_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                "usage of non-standard gtest include, use '#include \"gtest/gtest.h\"' instead",
                file_lines[line_number].strip(" \t\r\n"))
        if g_re_unwanted_gmock_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                "usage of non-standard gmock include, use '#include \"gmock/gmock.h\"' instead",
                file_lines[line_number].strip(" \t\r\n"))
        if g_re_unwanted_fmt_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                "usage of unwanted fmt include, use '#include \"fmt/format.h\"' instead",
                file_lines[line_number].strip(" \t\r\n"))

        if g_re_unwanted_system_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                'found system include with "...", use <...> instead"',
                file_lines[line_number].strip(" \t\r\n"))

        if filename.endswith('.h') and g_re_unwanted_force_thread_local.search(
                line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                'found disallowed #include "Utils/ThreadLocalLogForced.h" in header"',
                file_lines[line_number].strip(" \t\r\n"))

        # TODO: Fix offenders and reduce limit until some reasonable length is reached
        if len(line) > g_max_line_length:
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                f'line of {len(line)} characters too long (max allowed {g_max_line_length}), add some linebreaks ',
                file_lines[line_number].strip(" \t\r\n"))
Esempio n. 17
0
def check_api_export_symbols(filename, clean_file_contents):
    """
    Check that public API symbols are exported
    - A class has RLOGIC_API prefix before the name, structs and enums don't have it
    """

    is_api = ("include/public" in filename)
    is_header = (filename[-2:] == ".h") or (filename[-4:] == ".hpp")
    is_api_header = is_api and is_header

    if is_api_header:
        # symbol_def_re matches following patterns:
        #   (correct case) class|struct RLOGIC_API SymbolName { [...]
        #   (correct case) class|struct RLOGIC_API SymbolName : [...]
        #   (correct case) enum|enum class SymbolName : [...]
        #   (wrong case)   class|struct SymbolName { [...]
        #   (wrong case)   class|struct SymbolName : [...]
        symbol_def_re = re.compile(
            r'(template [^;]+?)?\s+(enum|class|struct|enum\s+class)(\s+)(\w+)(\s+)(\w*)(\s*)(\{|\:)'
        )

        for symbol_match in re.finditer(symbol_def_re, clean_file_contents):
            line_number = clean_file_contents[:symbol_match.start()].count(
                "\n")

            symbolNameGroups = symbol_match.groups()
            isTemplate = symbolNameGroups[0] is not None
            isEnum = symbolNameGroups[1].strip() == "enum"
            isStruct = symbolNameGroups[1].strip() == "struct"
            isEnumClass = "enum " in symbolNameGroups[1].strip()
            firstWord = symbolNameGroups[3].strip()
            secondWord = symbolNameGroups[5].strip()

            # check special cases that should NOT have RLOGIC_API
            if isEnum:
                if firstWord == "RLOGIC_API":
                    common.log_warning(
                        "check_api_export_symbols", filename, line_number + 1,
                        "Enum exported as RLOGIC_API: " + secondWord)
            elif isEnumClass:
                if firstWord == "RLOGIC_API":
                    common.log_warning(
                        "check_api_export_symbols", filename, line_number + 1,
                        "Enum class exported as RLOGIC_API: " + secondWord)
            elif isStruct:
                if firstWord == "RLOGIC_API":
                    common.log_warning(
                        "check_api_export_symbols", filename, line_number + 1,
                        "Struct exported as RLOGIC_API: " + secondWord)
            elif isTemplate:
                if firstWord == "RLOGIC_API":
                    common.log_warning(
                        "check_api_export_symbols", filename, line_number + 1,
                        "Template exported as RLOGIC_API: " + secondWord)
            else:
                # remaining cases should have RLOGIC_API
                if firstWord != "RLOGIC_API":
                    common.log_warning(
                        "check_api_export_symbols", filename, line_number + 1,
                        "Public symbol not exported as RLOGIC_API: " +
                        firstWord)
    else:
        # Will find occurances of RLOGIC_API surrounded by space(s)
        RLOGIC_API_re = re.compile(r'(?<!\w)(RLOGIC_API)\s')
        for symbol_match in re.finditer(RLOGIC_API_re, clean_file_contents):
            line_number = clean_file_contents[:symbol_match.start()].count(
                "\n")
            common.log_warning(
                "check_api_export_symbols", filename, line_number + 1,
                "Exporting API symbol in a non-API-header file! This symbol will be unusable."
            )
Esempio n. 18
0
def check_deprecated(filename, file_contents, clean_file_contents, file_lines,
                     clean_file_lines):
    """ Check for usage of deprecated constructs """

    # check for old mock syntax
    for line_number, line in enumerate(clean_file_lines):
        if g_re_deprecated_mock_syntax.search(line):
            common.log_warning("check_deprecated", filename, line_number + 1,
                               "usage of old googletest mock syntax",
                               file_lines[line_number].strip(" \t\r\n"))

    # check for unwanted includes includes
    for line_number, line in enumerate(file_lines):
        if g_re_unwanted_gtest_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                "usage of non-standard gtest include, use '#include \"gtest/gtest.h\"' instead",
                file_lines[line_number].strip(" \t\r\n"))
        if g_re_unwanted_gmock_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                "usage of non-standard gmock include, use '#include \"gmock/gmock.h\"' instead",
                file_lines[line_number].strip(" \t\r\n"))
        if g_re_unwanted_fmt_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                "usage of unwanted fmt include, use '#include \"fmt/format.h\"' instead",
                file_lines[line_number].strip(" \t\r\n"))

        if g_re_unwanted_system_include.search(line):
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                'found system include with "...", use <...> instead"',
                file_lines[line_number].strip(" \t\r\n"))

        if len(line) > g_max_line_length:
            common.log_warning(
                "check_deprecated", filename, line_number + 1,
                f'line of {len(line)} characters too long (max allowed {g_max_line_length}), add some linebreaks ',
                file_lines[line_number].strip(" \t\r\n"))
def check_correct_space_count(filename, file_lines):
    """
    Checks that the number of spaces at the beginning of every line is divisible by four

    """
    previous_indent = 0
    in_multiline_comment = False
    valid_indents_stack = []
    for i in range(len(file_lines)):
        if in_multiline_comment:
            # if end of muli-line comment
            if file_lines[i].count("*/") > 0:
                # just make sure there is no other new multi-line comment starting on the same line (after the current mult-line comment is closed)
                in_multiline_comment = file_lines[i].count(
                    "/*") > 0 and file_lines[i].index(
                        "/*") > file_lines[i].index("*/")
        else:
            in_multiline_comment = file_lines[i].count("/*") > 0

            # regex searches for the first character that is not a space character
            found_match = re.search("(?! )", file_lines[i])
            if found_match:
                space_count = found_match.start()

                while space_count > 0 and valid_indents_stack and valid_indents_stack[
                        -1] > space_count:
                    valid_indents_stack.pop()

                # check by indent stack
                if space_count in valid_indents_stack and space_count == valid_indents_stack[
                        -1]:
                    ok_by_stack = True
                else:
                    ok_by_stack = False

                # check divisible by 4 OR same indent as previous line or at least properly +-4*x from previous line
                warned = False
                if not ok_by_stack and (space_count % 4 != 0) and (
                        previous_indent != space_count) and (
                            abs(previous_indent - space_count) % 4 != 0):

                    # allow indent to parenthesis in previous line
                    is_smart_indent = False
                    if (i > 0) and (space_count > 0) and (len(
                            file_lines[i - 1]) > space_count - 1):
                        prev_line = file_lines[i - 1]
                        cur_line_without_spaces = file_lines[i][space_count:]
                        if prev_line[space_count - 1] in [
                                '(', '['
                        ] or prev_line[space_count - 4] in ['(', '[']:
                            is_smart_indent = True
                        elif prev_line.endswith(",") or prev_line.endswith("<<") or prev_line.endswith("||") or prev_line.endswith("&&") or \
                                cur_line_without_spaces.startswith("<<") or cur_line_without_spaces.startswith(">>"):
                            is_smart_indent = True

                    if not is_smart_indent:
                        common.log_warning(
                            "check_correct_space_count", filename, i + 1,
                            "number of spaces at beginning of line must be divisible by 4 (or conform to smart indent)"
                        )
                        warned = True
                # only previous spaces when not zero
                if space_count != 0:
                    # add to stack when valid and not already in
                    if not warned:
                        if not valid_indents_stack:
                            valid_indents_stack.append(space_count)
                        elif valid_indents_stack[-1] != space_count:
                            valid_indents_stack.append(space_count)
                    previous_indent = space_count
            else:
                previous_indent = 0