diff --git a/tools/sqf_validator.py b/tools/sqf_validator.py deleted file mode 100644 index 2d67252..0000000 --- a/tools/sqf_validator.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env python3 - -import fnmatch -import os -import re -import sys - -def valid_keyword_after_code(content, index): - for word in ["for", "do", "count", "each", "forEach", "else", "and", "not", "isEqualTo", "isNotEqualTo", "in", "call", "spawn", "execVM", "catch", "param", "select", "apply", "findIf", "remoteExec"]: - if content.find(word, index, index + len(word)) != -1: - return True - - return False - -def check_sqf(filepath): - errors = [] - - with open(filepath, "r", encoding = "utf-8", errors = "ignore") as file: - content = file.read() - - # Store all brackets we find in this file, so we can validate everything on the end - brackets = [] - - # Used in case we are in a line comment (//) - ignore_till_eol = False - - # To check if we are in a comment block - in_comment_block = False - check_if_comment = False - - # Used in case we are in a comment block (/* */) - # This is true if we detect a * inside a comment block - # If the next character is a /, it means we end our comment block - check_if_closing = False - - # We ignore everything inside a string - in_string = False - - # Used to store the starting type of a string, so we can match that to the end of a string - string_type = "" - - # Used to check for semicolon after code blocks - last_is_curly_brace = False - check_for_semicolon = False - - # Extra information so we know what line we find errors at - line_number = 1 - - char_index = 0 - - for c in content: - if last_is_curly_brace: - last_is_curly_brace = False - - # Test generates false positives with binary commands that take CODE as 2nd arg (e.g. findIf) - check_for_semicolon = not re.search("findIf", content, re.IGNORECASE) - - # Keep track of current line number - if c == "\n": - line_number += 1 - - # While we are in a string, we can ignore everything else, except the end of the string - if in_string: - if c == string_type: - in_string = False - - # Look for the end of this comment block - elif in_comment_block: - if c == "*": - check_if_closing = True - elif check_if_closing: - if c == "/": - in_comment_block = False - elif c != "*": - check_if_closing = False - - # If we are not in a comment block, we will check if we are at the start of one or count the () {} and [] - else: - # This means we have encountered a /, so we are now checking if this is an inline comment or a comment block - if check_if_comment: - check_if_comment = False - - # If the next character after / is a *, we are at the start of a comment block - if c == "*": - in_comment_block = True - - # Otherwise, check if we are in an line comment, / followed by another / (//) - elif c == "/": - ignore_till_eol = True - - if not in_comment_block: - if ignore_till_eol: - # We are in a line comment, just continue going through the characters until we find an end of line - if c == "\n": - ignore_till_eol = False - else: - if c == '"' or c == "'": - in_string = True - string_type = c - elif c == "/": - check_if_comment = True - elif c == "\t": - errors.append(" ERROR: Found a tab on line {}.".format(line_number)) - elif c in ["(", "[", "{"]: - brackets.append(c) - elif c == ")": - if not brackets or brackets[-1] in ["[", "{"]: - errors.append(" ERROR: Missing parenthesis '(' on line {}.".format(line_number)) - brackets.append(c) - elif c == "]": - if not brackets or brackets[-1] in ["(", "{"]: - errors.append(" ERROR: Missing square bracket '[' on line {}.".format(line_number)) - brackets.append(c) - elif c == "}": - last_is_curly_brace = True - - if not brackets or brackets[-1] in ["(", "["]: - errors.append(" ERROR: Missing curly brace '{{' on line {}.".format(line_number)) - brackets.append(c) - - if check_for_semicolon: - # Keep reading until no white space or comments - if c not in [" ", "\t", "\n", "/"]: - check_for_semicolon = False - if c not in ["]", ")", "}", ";", ",", "&", "!", "|", "="] and not valid_keyword_after_code(content, char_index): - errors.append(" ERROR: Possible missing semicolon ';' on line {}.".format(line_number)) - - char_index += 1 - - # Compare opening and closing bracket counts - if brackets.count("(") != brackets.count(")"): - errors.append(" ERROR: Unequal number of parentheses, '(' = {}, ')' = {}.".format(brackets.count("("), brackets.count(")"))) - - if brackets.count("[") != brackets.count("]"): - errors.append(" ERROR: Unequal number of square brackets, '[' = {}, ']' = {}.".format(brackets.count("["), brackets.count("]"))) - - if brackets.count("{") != brackets.count("}"): - errors.append(" ERROR: Unequal number of curly braces, '{{' = {}, '}}' = {}.".format(brackets.count("{"), brackets.count("}"))) - - # Ensure includes are before block comments - if re.compile('\s*(/\*[\s\S]+?\*/)\s*#include').match(content): - errors.append(" ERROR: Found an #include after a block comment.") - - return errors - -def main(): - print("Validating SQF") - print("--------------") - - # Allow running from root directory and tools directory - root_dir = ".." - if os.path.exists("addons"): - root_dir = "." - - # Check all SQF files in the project directory - sqf_files = [] - - for root, _, files in os.walk(root_dir): - for file in fnmatch.filter(files, "*.sqf"): - sqf_files.append(os.path.join(root, file)) - - sqf_files.sort() - - bad_count = 0 - - for filepath in sqf_files: - errors = check_sqf(filepath) - - if errors: - print("\nFound {} error(s) in {}:".format(len(errors), os.path.relpath(filepath, root_dir))) - - for error in errors: - print(error) - - bad_count += 1 - - print("\nChecked {} files, found errors in {}.".format(len(sqf_files), bad_count)) - - if bad_count == 0: - print("SQF Validation PASSED") - else: - print("SQF Validation FAILED") - - return bad_count - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file