Files
Paradise/tools/ci/check_user_verb_params.py
warriorstar-orionandGitHub 2a842644d5 port ADMIN_VERB and friends (#30646)
* port ADMIN_VERB and friends

* some renaming

* dumb

* one more rename

* never search and replace this codebase

* fix TM issues, more renaming

* add a static analysis to shore up user verbs

* fix double message on roundstart

* remove macro we're not using yet

* convert remaining playsounds verbs

* convert more verbs i missed somehow

* why is this a completely different signature than everything else

* fix ui_interact arg

* fix logging view and others

* buncha issues caught in TM

* fix mentor tickets ui

* fix bug report viewing

* moron
2025-12-12 19:18:22 +00:00

148 lines
4.6 KiB
Python

import os
import sys
import time
from dataclasses import dataclass
from typing import Any
from avulto import DME, Path as p, ProcDecl
from avulto.ast import NodeKind, SourceLoc
RED = "\033[0;31m"
GREEN = "\033[0;32m"
BLUE = "\033[0;34m"
NC = "\033[0m" # No Color
def make_error(source_loc: SourceLoc, msg) -> str:
if os.getenv("GITHUB_ACTIONS") == "true":
return f"::error file={source_loc.file_path},line={source_loc.line},title=User Verbs::{source_loc.file_path}:{source_loc.line}: {RED}{msg}{NC}"
else:
return f"{source_loc.file_path}:{source_loc.line}: {RED}{msg}{NC}"
PROCS_NEED_CLIENT = (
"alert",
"tgui_input_list",
"tgui_input_number",
"to_chat",
)
BAD_VARS = (
"usr",
"src",
)
class Walker:
def __init__(self):
self.bad_names = []
self.bad_inputs = []
self.bad_calls = []
self.bad_clean_inputs = []
def visit_Identifier(self, node, source_info):
if node.name in BAD_VARS:
self.bad_names.append((node, source_info))
def visit_Input(self, node, source_info):
if node.args[0].kind == NodeKind.CONSTANT:
self.bad_inputs.append((node, source_info))
elif str(node.args[0]) in BAD_VARS:
self.bad_names.append((node.args[0], source_info))
def visit_Call(self, node, source_info):
if (
str(node.name) in PROCS_NEED_CLIENT
and node.args[0].kind == NodeKind.CONSTANT
):
self.bad_calls.append((node, source_info))
if str(node.name) == "clean_input":
last_arg = node.args[-1]
if last_arg.kind == NodeKind.ASSIGN_OP:
if str(last_arg.lhs) != "user" or str(last_arg.rhs) != "client":
self.bad_clean_inputs.append((node, source_info))
else:
self.bad_clean_inputs.append((node, source_info))
for arg in node.args:
if arg.kind == NodeKind.IDENTIFIER:
self.visit_Identifier(arg, source_info)
elif arg.kind == NodeKind.CALL:
self.visit_Call(arg, source_info)
elif arg.kind == NodeKind.INTERP_STRING:
# "[key_name(usr)] blah blah"
#
# hate we have to nest this so deeply but once we have a
# visit_Node for any Node, its tree is not going to get
# visited node by node unless we so do explicitly.
for expr, _ in arg.token_pairs:
if expr.kind == NodeKind.CALL:
self.visit_Call(expr, source_info)
elif expr.kind == NodeKind.IDENTIFIER:
self.visit_Identifier(expr, source_info)
if __name__ == "__main__":
print("check_user_verb_params started")
exit_code = 0
start = time.time()
dme = DME.from_file("paradise.dme", parse_procs=True)
errors = []
for pth in dme.subtypesof("/datum/user_verb"):
walker = Walker()
td = dme.types[pth]
proc = td.proc_decls("__avd_do_verb")[0]
proc.walk(walker)
if walker.bad_names:
exit_code = 1
for name, source_loc in walker.bad_names:
errors.append(
make_error(
source_loc,
f"user verb {td.path} uses `{name}` instead of implicit client/client arg",
)
)
if walker.bad_inputs:
exit_code = 1
for node, source_loc in walker.bad_inputs:
errors.append(
make_error(
source_loc,
f"input() call in {td.path} missing explicit recipient",
)
)
if walker.bad_calls:
exit_code = 1
for node, source_loc in walker.bad_calls:
errors.append(
make_error(
source_loc,
f"call {node.name}() in {td.path} missing explicit recipient",
)
)
if walker.bad_clean_inputs:
exit_code = 1
for node, source_loc in walker.bad_clean_inputs:
errors.append(
make_error(
source_loc,
f"call {node.name}() in {td.path} must have `user = client` as its last arg",
)
)
for error in sorted(errors):
print(error)
end = time.time()
print(f"check_user_verb_params tests completed in {end - start:.2f}s\n")
sys.exit(exit_code)