import glob import re import os import sys import time from collections import namedtuple Failure = namedtuple("Failure", ["lineno", "message"]) RED = "\033[0;31m" GREEN = "\033[0;32m" BLUE = "\033[0;34m" NC = "\033[0m" # No Color IGNORE_515_PROC_MARKER_FILENAME = "__byond_version_compat.dm" CHECK_515_PROC_MARKER_RE = re.compile(r"\.proc/") def check_515_proc_syntax(lines): for idx, line in enumerate(lines): if CHECK_515_PROC_MARKER_RE.search(line): return Failure(idx + 1, "Outdated proc reference use detected in code. Please use proc reference helpers.") CHECK_SPACE_INDENTATION_RE = re.compile(r"^ {2,}[^\*]") def check_space_indentation(lines): """ Check specifically for space-significant indentation. Excludes dmdoc block comment lines so long as there is an asterisk immediately after the leading spaces. >>> bool(check_space_indentation([" foo"])) True >>> bool(check_space_indentation(["\\tfoo"])) False >>> bool(check_space_indentation([" * foo"])) False """ for idx, line in enumerate(lines): if CHECK_SPACE_INDENTATION_RE.match(line): return Failure(idx + 1, "Space indentation detected, please use tab indentation.") CHECK_MIXED_INDENTATION_RE = re.compile(r"^(\t+ | +\t)\s*[^\s\*]") def check_mixed_indentation(lines): """ Check specifically for leading whitespace which contains a mix of tab and space characters. Excludes dmdoc block comment lines so long as there is an asterisk immediately after the leading whitespace. >>> bool(check_mixed_indentation(["\\t\\t foo"])) True >>> bool(check_mixed_indentation(["\\t \\t foo"])) True >>> bool(check_mixed_indentation(["\\t // foo"])) True >>> bool(check_mixed_indentation([" \\tfoo"])) True >>> bool(check_mixed_indentation([" \\t foo"])) True >>> bool(check_mixed_indentation(["\\t * foo"])) False >>> bool(check_mixed_indentation(["\\t\\t* foo"])) False >>> bool(check_mixed_indentation(["\\t \\t * foo"])) False """ for idx, line in enumerate(lines): if CHECK_MIXED_INDENTATION_RE.match(line): return Failure(idx + 1, "Mixed indentation detected, please stick to tab indentation.") def check_trailing_newlines(lines): lines = [x for x in lines] if lines and not lines[-1].endswith("\n"): return Failure(len(lines), "Missing a trailing newline") GLOBAL_VARS_RE = re.compile(r"^/*var/") def check_global_vars(lines): for idx, line in enumerate(lines): if GLOBAL_VARS_RE.match(line): return Failure(idx + 1, "Unmanaged global var use detected in code, please use the helpers.") PROC_ARGS_WITH_VAR_PREFIX_RE = re.compile(r"^/[\w/]\S+\(.*(var/|, ?var/.*).*\)") def check_proc_args_with_var_prefix(lines): for idx, line in enumerate(lines): if PROC_ARGS_WITH_VAR_PREFIX_RE.match(line): return Failure(idx + 1, "Changed files contains a proc argument starting with 'var'.") NANOTRASEN_CAMEL_CASE = re.compile(r"NanoTrasen") def check_for_nanotrasen_camel_case(lines): for idx, line in enumerate(lines): if NANOTRASEN_CAMEL_CASE.search(line): return Failure(idx + 1, "Nanotrasen should not be spelled in the camel case form.") TO_CHAT_WITH_NO_USER_ARG_RE = re.compile(r"to_chat\(\"") def check_to_chats_have_a_user_arguement(lines): for idx, line in enumerate(lines): if TO_CHAT_WITH_NO_USER_ARG_RE.search(line): return Failure(idx + 1, "Changed files contains a to_chat() procedure without a user argument.") CONDITIONAL_LEADING_SPACE = re.compile(r"(if|for|while|switch)\s+(\(.*?\)?)") # checks for "if (thing)", replace with $1$2 CONDITIONAL_BEGINNING_SPACE = re.compile(r"(if|for|while|switch)\((!?) (.+\)?)") # checks for "if( thing)", replace with $1($2$3 CONDITIONAL_ENDING_SPACE = re.compile(r"(if|for|while|switch)(\(.+) \)") # checks for "if(thing )", replace with $1$2) CONDITIONAL_DOUBLE_PARENTHESIS = re.compile(r"(if)\((\([^)]+\))\)$") # checks for if((thing)), replace with $1$2 # To fix any of these, run them as regex in VSCode, with the appropriate replacement # It may be a good idea to turn the replacement into a script someday def check_conditional_spacing(lines): for idx, line in enumerate(lines): if CONDITIONAL_LEADING_SPACE.search(line): return Failure(idx + 1, "Found a conditional statement matching the format \"if (thing)\" (irregular spacing), please use \"if(thing)\" instead.") if CONDITIONAL_BEGINNING_SPACE.search(line): return Failure(idx + 1, "Found a conditional statement matching the format \"if( thing)\" (irregular spacing), please use \"if(thing)\" instead.") if CONDITIONAL_ENDING_SPACE.search(line): return Failure(idx + 1, "Found a conditional statement matching the format \"if(thing )\" (irregular spacing), please use \"if(thing)\" instead.") if CONDITIONAL_DOUBLE_PARENTHESIS.search(line): return Failure(idx + 1, "Found a conditional statement matching the format \"if((thing))\" (unnecessary outer parentheses), please use \"if(thing)\" instead.") # makes sure that no global list inits have an empty list in them without using the helper GLOBAL_LIST_EMPTY = re.compile(r"(?