First Commit
This commit is contained in:
263
apps/codestyle/codestyle-cpp.py
Normal file
263
apps/codestyle/codestyle-cpp.py
Normal file
@@ -0,0 +1,263 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
# Get the src directory of the project
|
||||
src_directory = os.path.join(os.getcwd(), 'src')
|
||||
|
||||
# Global variables
|
||||
error_handler = False
|
||||
results = {
|
||||
"Multiple blank lines check": "Passed",
|
||||
"Trailing whitespace check": "Passed",
|
||||
"GetCounter() check": "Passed",
|
||||
"Misc codestyle check": "Passed",
|
||||
"GetTypeId() check": "Passed",
|
||||
"NpcFlagHelpers check": "Passed",
|
||||
"ItemFlagHelpers check": "Passed",
|
||||
"ItemTemplateFlagHelpers check": "Passed"
|
||||
}
|
||||
|
||||
# Main function to parse all the files of the project
|
||||
def parsing_file(directory: str) -> None:
|
||||
print("Starting AzerothCore CPP Codestyle check...")
|
||||
print(" ")
|
||||
print("Please read the C++ Code Standards for AzerothCore:")
|
||||
print("https://www.azerothcore.org/wiki/cpp-code-standards")
|
||||
print(" ")
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
if not file.endswith('.ico'): # Skip .ico files that cannot be read
|
||||
file_path = os.path.join(root, file)
|
||||
file_name = file
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
multiple_blank_lines_check(file, file_path)
|
||||
trailing_whitespace_check(file, file_path)
|
||||
get_counter_check(file, file_path)
|
||||
if not file_name.endswith('.cmake') and file_name != 'CMakeLists.txt':
|
||||
misc_codestyle_check(file, file_path)
|
||||
if file_name != 'Object.h':
|
||||
get_typeid_check(file, file_path)
|
||||
if file_name != 'Unit.h':
|
||||
npcflags_helpers_check(file, file_path)
|
||||
if file_name != 'Item.h':
|
||||
itemflag_helpers_check(file, file_path)
|
||||
if file_name != 'ItemTemplate.h':
|
||||
itemtemplateflag_helpers_check(file, file_path)
|
||||
except UnicodeDecodeError:
|
||||
print(f"\nCould not decode file {file_path}")
|
||||
sys.exit(1)
|
||||
# Output the results
|
||||
print("")
|
||||
for check, result in results.items():
|
||||
print(f"{check} : {result}")
|
||||
if error_handler:
|
||||
print("\nPlease fix the codestyle issues above.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"\nEverything looks good")
|
||||
|
||||
# Codestyle patterns checking for multiple blank lines
|
||||
def multiple_blank_lines_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
consecutive_blank_lines = 0
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if line.strip() == '':
|
||||
consecutive_blank_lines += 1
|
||||
if consecutive_blank_lines > 1:
|
||||
print(f"Multiple blank lines found in {file_path} at line {line_number - 1}")
|
||||
check_failed = True
|
||||
else:
|
||||
consecutive_blank_lines = 0
|
||||
# Additional check for the end of the file
|
||||
if consecutive_blank_lines >= 1:
|
||||
print(f"Multiple blank lines found at the end of: {file_path}")
|
||||
check_failed = True
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["Multiple blank lines check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for whitespace at the end of the lines
|
||||
def trailing_whitespace_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if line.endswith(' \n'):
|
||||
print(f"Trailing whitespace found: {file_path} at line {line_number}")
|
||||
if not error_handler:
|
||||
error_handler = True
|
||||
results["Trailing whitespace check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for ObjectGuid::GetCounter()
|
||||
def get_counter_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if 'ObjectGuid::GetCounter()' in line:
|
||||
print(f"Please use ObjectGuid::ToString().c_str() instead ObjectGuid::GetCounter(): {file_path} at line {line_number}")
|
||||
if not error_handler:
|
||||
error_handler = True
|
||||
results["GetCounter() check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for GetTypeId()
|
||||
def get_typeid_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if 'GetTypeId() == TYPEID_ITEM' in line or 'GetTypeId() != TYPEID_ITEM' in line:
|
||||
print(f"Please use IsItem() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'GetTypeId() == TYPEID_UNIT' in line or 'GetTypeId() != TYPEID_UNIT' in line:
|
||||
print(f"Please use IsCreature() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'GetTypeId() == TYPEID_PLAYER' in line or 'GetTypeId() != TYPEID_PLAYER' in line:
|
||||
print(f"Please use IsPlayer() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'GetTypeId() == TYPEID_GAMEOBJECT' in line or 'GetTypeId() != TYPEID_GAMEOBJECT' in line:
|
||||
print(f"Please use IsGameObject() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'GetTypeId() == TYPEID_DYNOBJECT' in line or 'GetTypeId() != TYPEID_DYNOBJECT' in line:
|
||||
print(f"Please use IsDynamicObject() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["GetTypeId() check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for NpcFlag helpers
|
||||
def npcflags_helpers_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if 'GetUInt32Value(UNIT_NPC_FLAGS)' in line:
|
||||
print(
|
||||
f"Please use GetNpcFlags() instead of GetUInt32Value(UNIT_NPC_FLAGS): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'HasFlag(UNIT_NPC_FLAGS,' in line:
|
||||
print(
|
||||
f"Please use HasNpcFlag() instead of HasFlag(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'SetUInt32Value(UNIT_NPC_FLAGS,' in line:
|
||||
print(
|
||||
f"Please use ReplaceAllNpcFlags() instead of SetUInt32Value(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'SetFlag(UNIT_NPC_FLAGS,' in line:
|
||||
print(
|
||||
f"Please use SetNpcFlag() instead of SetFlag(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'RemoveFlag(UNIT_NPC_FLAGS,' in line:
|
||||
print(
|
||||
f"Please use RemoveNpcFlag() instead of RemoveFlag(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["NpcFlagHelpers check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for ItemFlag helpers
|
||||
def itemflag_helpers_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if 'HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE)' in line:
|
||||
print(
|
||||
f"Please use IsRefundable() instead of HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE)' in line:
|
||||
print(
|
||||
f"Please use IsBOPTradable() instead of HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_WRAPPED)' in line:
|
||||
print(
|
||||
f"Please use IsWrapped() instead of HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_WRAPPED): {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["ItemFlagHelpers check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for ItemTemplate helpers
|
||||
def itemtemplateflag_helpers_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if 'Flags & ITEM_FLAG' in line:
|
||||
print(
|
||||
f"Please use HasFlag(ItemFlag) instead of 'Flags & ITEM_FLAG_': {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'Flags2 & ITEM_FLAG2' in line:
|
||||
print(
|
||||
f"Please use HasFlag2(ItemFlag2) instead of 'Flags2 & ITEM_FLAG2_': {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if 'FlagsCu & ITEM_FLAGS_CU' in line:
|
||||
print(
|
||||
f"Please use HasFlagCu(ItemFlagsCustom) instead of 'FlagsCu & ITEM_FLAGS_CU_': {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["ItemTemplateFlagHelpers check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for various codestyle issues
|
||||
def misc_codestyle_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
|
||||
# used to check for "if/else (...) {" "} else" ignores "if/else (...) {...}" "#define ... if/else (...) {"
|
||||
ifelse_curlyregex = r"^[^#define].*\s+(if|else)(\s*\(.*\))?\s*{[^}]*$|}\s*else(\s*{[^}]*$)"
|
||||
# used to catch double semicolons ";;" ignores "(;;)"
|
||||
double_semiregex = r"(?<!\()\s*;;(?!\))"
|
||||
# used to catch tabs
|
||||
tab_regex = r"\t"
|
||||
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if 'const auto&' in line:
|
||||
print(
|
||||
f"Please use the 'auto const&' syntax instead of 'const auto&': {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if re.search(r'\bconst\s+\w+\s*\*\b', line):
|
||||
print(
|
||||
f"Please use the 'Class/ObjectType const*' syntax instead of 'const Class/ObjectType*': {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if [match for match in [' if(', ' if ( '] if match in line]:
|
||||
print(
|
||||
f"Please use the 'if (XXXX)' syntax instead of 'if(XXXX)': {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if re.match(ifelse_curlyregex, line):
|
||||
print(
|
||||
f"Curly brackets are not allowed to be leading or trailing if/else statements. Place it on a new line: {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if re.search(double_semiregex, line):
|
||||
print(
|
||||
f"Double semicolon (;;) found in {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if re.match(tab_regex, line):
|
||||
print(
|
||||
f"Tab found! Replace it to 4 spaces: {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["Misc codestyle check"] = "Failed"
|
||||
|
||||
# Main function
|
||||
parsing_file(src_directory)
|
||||
330
apps/codestyle/codestyle-sql.py
Normal file
330
apps/codestyle/codestyle-sql.py
Normal file
@@ -0,0 +1,330 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import glob
|
||||
|
||||
# Get the pending directory of the project
|
||||
base_dir = os.getcwd()
|
||||
pattern = os.path.join(base_dir, 'data/sql/updates/pending_db_*')
|
||||
src_directory = glob.glob(pattern)
|
||||
|
||||
# Global variables
|
||||
error_handler = False
|
||||
results = {
|
||||
"Multiple blank lines check": "Passed",
|
||||
"Trailing whitespace check": "Passed",
|
||||
"SQL codestyle check": "Passed",
|
||||
"INSERT & DELETE safety usage check": "Passed",
|
||||
"Missing semicolon check": "Passed",
|
||||
"Backtick check": "Passed"
|
||||
}
|
||||
|
||||
# Collect all files in all directories
|
||||
def collect_files_from_directories(directories: list) -> list:
|
||||
all_files = []
|
||||
for directory in directories:
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
if not file.endswith('.sh'): # Skip .sh files
|
||||
all_files.append(os.path.join(root, file))
|
||||
return all_files
|
||||
|
||||
# Main function to parse all the files of the project
|
||||
def parsing_file(files: list) -> None:
|
||||
print("Starting AzerothCore SQL Codestyle check...")
|
||||
print(" ")
|
||||
print("Please read the SQL Standards for AzerothCore:")
|
||||
print("https://www.azerothcore.org/wiki/sql-standards")
|
||||
print(" ")
|
||||
|
||||
# Iterate over all files
|
||||
for file_path in files:
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
multiple_blank_lines_check(file, file_path)
|
||||
trailing_whitespace_check(file, file_path)
|
||||
sql_check(file, file_path)
|
||||
insert_delete_safety_check(file, file_path)
|
||||
semicolon_check(file, file_path)
|
||||
backtick_check(file, file_path)
|
||||
except UnicodeDecodeError:
|
||||
print(f"\n❌ Could not decode file {file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Output the results
|
||||
print("\n ")
|
||||
for check, result in results.items():
|
||||
print(f"{check} : {result}")
|
||||
if error_handler:
|
||||
print("\n ")
|
||||
print("\n❌ Please fix the codestyle issues above.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n ")
|
||||
print(f"\n✅ Everything looks good")
|
||||
|
||||
# Codestyle patterns checking for multiple blank lines
|
||||
def multiple_blank_lines_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
consecutive_blank_lines = 0
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if line.strip() == '':
|
||||
consecutive_blank_lines += 1
|
||||
if consecutive_blank_lines > 1:
|
||||
print(f"❌ Multiple blank lines found in {file_path} at line {line_number - 1}")
|
||||
check_failed = True
|
||||
else:
|
||||
consecutive_blank_lines = 0
|
||||
# Additional check for the end of the file
|
||||
if consecutive_blank_lines >= 1:
|
||||
print(f"❌ Multiple blank lines found at the end of: {file_path}")
|
||||
check_failed = True
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["Multiple blank lines check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for whitespace at the end of the lines
|
||||
def trailing_whitespace_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if line.endswith(' \n'):
|
||||
print(f"❌ Trailing whitespace found: {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["Trailing whitespace check"] = "Failed"
|
||||
|
||||
# Codestyle patterns checking for various codestyle issues
|
||||
def sql_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
check_failed = False
|
||||
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if [match for match in ['broadcast_text'] if match in line]:
|
||||
print(
|
||||
f"❌ DON'T EDIT broadcast_text TABLE UNLESS YOU KNOW WHAT YOU ARE DOING!\nThis error can safely be ignored if the changes are approved to be sniffed: {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if "EntryOrGuid" in line:
|
||||
print(
|
||||
f"❌ Please use entryorguid syntax instead of EntryOrGuid in {file_path} at line {line_number}\nWe recommend to use keira to have the right syntax in auto-query generation")
|
||||
check_failed = True
|
||||
if [match for match in [';;'] if match in line]:
|
||||
print(
|
||||
f"❌ Double semicolon (;;) found in {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
if re.match(r"\t", line):
|
||||
print(
|
||||
f"❌ Tab found! Replace it to 4 spaces: {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
|
||||
last_line = line[-1].strip()
|
||||
if last_line:
|
||||
print(
|
||||
f"❌ The last line is not a newline. Please add a newline: {file_path}")
|
||||
check_failed = True
|
||||
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["SQL codestyle check"] = "Failed"
|
||||
|
||||
def insert_delete_safety_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0) # Reset file pointer to the beginning
|
||||
not_delete = ["creature_template", "gameobject_template", "item_template", "quest_template"]
|
||||
check_failed = False
|
||||
previous_line = ""
|
||||
|
||||
# Parse all the file
|
||||
for line_number, line in enumerate(file, start = 1):
|
||||
if line.startswith("--"):
|
||||
continue
|
||||
if "INSERT" in line and "DELETE" not in previous_line:
|
||||
print(f"❌ No DELETE keyword found before the INSERT in {file_path} at line {line_number}\nIf this error is intended, please notify a maintainer")
|
||||
check_failed = True
|
||||
previous_line = line
|
||||
match = re.match(r"DELETE FROM\s+`([^`]+)`", line, re.IGNORECASE)
|
||||
if match:
|
||||
table_name = match.group(1)
|
||||
if table_name in not_delete:
|
||||
print(
|
||||
f"❌ Entries from {table_name} should not be deleted! {file_path} at line {line_number}\nIf this error is intended, please notify a maintainer")
|
||||
check_failed = True
|
||||
|
||||
# Handle the script error and update the result output
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["INSERT & DELETE safety usage check"] = "Failed"
|
||||
|
||||
def semicolon_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
|
||||
file.seek(0) # Reset file pointer to the start
|
||||
check_failed = False
|
||||
|
||||
sql_statement_regex = re.compile(r'^\s*(SELECT|INSERT|UPDATE|DELETE|REPLACE|SET)\b', re.IGNORECASE)
|
||||
block_comment_start = re.compile(r'/\*')
|
||||
block_comment_end = re.compile(r'\*/')
|
||||
inline_comment = re.compile(r'--.*')
|
||||
|
||||
query_open = False
|
||||
in_block_comment = False
|
||||
inside_values_block = False
|
||||
|
||||
lines = file.readlines()
|
||||
total_lines = len(lines)
|
||||
|
||||
def get_next_non_blank_line(start):
|
||||
""" Get the next non-blank, non-comment line starting from `start` """
|
||||
for idx in range(start, total_lines):
|
||||
next_line = lines[idx].strip()
|
||||
if next_line and not next_line.startswith('--') and not next_line.startswith('/*'):
|
||||
return next_line
|
||||
return None
|
||||
|
||||
for line_number, line in enumerate(lines, start=1):
|
||||
stripped_line = line.strip()
|
||||
|
||||
# Skip single-line comments
|
||||
if stripped_line.startswith('--'):
|
||||
continue
|
||||
|
||||
# Handle block comments
|
||||
if in_block_comment:
|
||||
if '*/' in stripped_line:
|
||||
in_block_comment = False
|
||||
stripped_line = stripped_line.split('*/', 1)[1].strip()
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
if '/*' in stripped_line:
|
||||
query_open = False # Reset query state at start of block comment
|
||||
in_block_comment = True
|
||||
stripped_line = stripped_line.split('/*', 1)[0].strip()
|
||||
|
||||
# Skip empty lines (unless inside values block)
|
||||
if not stripped_line and not inside_values_block:
|
||||
continue
|
||||
|
||||
# Remove inline comments after SQL
|
||||
stripped_line = stripped_line.split('--', 1)[0].strip()
|
||||
|
||||
if stripped_line.upper().startswith("SET") and not stripped_line.endswith(";"):
|
||||
print(f"❌ Missing semicolon in {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
|
||||
# Detect query start
|
||||
if not query_open and any(keyword in stripped_line.upper() for keyword in ["SELECT", "INSERT", "UPDATE", "DELETE", "REPLACE"]):
|
||||
query_open = True
|
||||
|
||||
# Detect start of multi-line VALUES block
|
||||
if any(kw in stripped_line.upper() for kw in ["INSERT", "REPLACE"]) and "VALUES" in stripped_line.upper():
|
||||
inside_values_block = True
|
||||
query_open = True # Ensure query is marked open too
|
||||
|
||||
if inside_values_block:
|
||||
if not stripped_line:
|
||||
continue # Allow blank lines inside VALUES block
|
||||
|
||||
if stripped_line.startswith('('):
|
||||
# Get next non-blank line to detect if we're at the last row
|
||||
next_line = get_next_non_blank_line(line_number)
|
||||
|
||||
if next_line and next_line.startswith('('):
|
||||
# Expect comma if another row follows
|
||||
if not stripped_line.endswith(','):
|
||||
print(f"❌ Missing comma in {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
else:
|
||||
# Expect semicolon if this is the final row
|
||||
if not stripped_line.endswith(';'):
|
||||
print(f"❌ Missing semicolon in {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
inside_values_block = False
|
||||
query_open = False
|
||||
else:
|
||||
inside_values_block = False # Close block if semicolon was found
|
||||
|
||||
elif query_open and not inside_values_block:
|
||||
# Normal query handling (outside multi-row VALUES block)
|
||||
if line_number == total_lines and not stripped_line.endswith(';'):
|
||||
print(f"❌ Missing semicolon in {file_path} at the last line {line_number}")
|
||||
check_failed = True
|
||||
query_open = False
|
||||
elif stripped_line.endswith(';'):
|
||||
query_open = False
|
||||
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["Missing semicolon check"] = "Failed"
|
||||
|
||||
def backtick_check(file: io, file_path: str) -> None:
|
||||
global error_handler, results
|
||||
file.seek(0)
|
||||
check_failed = False
|
||||
|
||||
# Find SQL clauses
|
||||
pattern = re.compile(
|
||||
r'\b(SELECT|FROM|JOIN|WHERE|GROUP BY|ORDER BY|DELETE FROM|UPDATE|INSERT INTO|SET|REPLACE|REPLACE INTO)\s+(.*?)(?=;$|(?=\b(?:WHERE|SET|VALUES)\b)|$)',
|
||||
re.IGNORECASE | re.DOTALL
|
||||
)
|
||||
|
||||
# Make sure to ignore values enclosed in single- and doublequotes
|
||||
quote_pattern = re.compile(r"'(?:\\'|[^'])*'|\"(?:\\\"|[^\"])*\"")
|
||||
|
||||
for line_number, line in enumerate(file, start=1):
|
||||
# Ignore comments
|
||||
if line.startswith('--'):
|
||||
continue
|
||||
|
||||
# Sanitize single- and doublequotes to prevent false positives
|
||||
sanitized_line = quote_pattern.sub('', line)
|
||||
matches = pattern.findall(sanitized_line)
|
||||
|
||||
for clause, content in matches:
|
||||
# Find all words and exclude @variables
|
||||
words = re.findall(r'\b(?<!@)([a-zA-Z_][a-zA-Z0-9_]*)\b', content)
|
||||
|
||||
for word in words:
|
||||
# Skip MySQL keywords
|
||||
if word.upper() in {"SELECT", "FROM", "JOIN", "WHERE", "GROUP", "BY", "ORDER",
|
||||
"DELETE", "UPDATE", "INSERT", "INTO", "SET", "VALUES", "AND",
|
||||
"IN", "OR", "REPLACE", "NOT", "BETWEEN",
|
||||
"DISTINCT", "HAVING", "LIMIT", "OFFSET", "AS", "ON", "INNER",
|
||||
"LEFT", "RIGHT", "FULL", "OUTER", "CROSS", "NATURAL",
|
||||
"EXISTS", "LIKE", "IS", "NULL", "UNION", "ALL", "ASC", "DESC",
|
||||
"CASE", "WHEN", "THEN", "ELSE", "END", "CREATE", "TABLE",
|
||||
"ALTER", "DROP", "DATABASE", "INDEX", "VIEW", "TRIGGER",
|
||||
"PROCEDURE", "FUNCTION", "PRIMARY", "KEY", "FOREIGN", "REFERENCES",
|
||||
"CONSTRAINT", "DEFAULT", "AUTO_INCREMENT", "UNIQUE", "CHECK",
|
||||
"SHOW", "DESCRIBE", "EXPLAIN", "USE", "GRANT", "REVOKE",
|
||||
"BEGIN", "COMMIT", "ROLLBACK", "SAVEPOINT", "LOCK", "UNLOCK",
|
||||
"WITH", "RECURSIVE", "COLUMN", "ENGINE", "CHARSET", "COLLATE",
|
||||
"IF", "ELSEIF", "LOOP", "WHILE", "DO", "HANDLER", "LEAVE",
|
||||
"ITERATE", "DECLARE", "CURSOR", "FETCH", "OPEN", "CLOSE"}:
|
||||
continue
|
||||
|
||||
# Make sure the word is enclosed in backticks
|
||||
if not re.search(rf'`{re.escape(word)}`', content):
|
||||
print(f"❌ Missing backticks around ({word}). {file_path} at line {line_number}")
|
||||
check_failed = True
|
||||
|
||||
if check_failed:
|
||||
error_handler = True
|
||||
results["Backtick check"] = "Failed"
|
||||
|
||||
# Collect all files from matching directories
|
||||
all_files = collect_files_from_directories(src_directory)
|
||||
|
||||
# Main function
|
||||
parsing_file(all_files)
|
||||
Reference in New Issue
Block a user