101101 "editor" : "Editor-only" ,
102102 "variant" : "Variant types" ,
103103}
104+
104105CLASS_GROUPS_BASE : dict [str , str ] = {
105106 "node" : "Node" ,
106107 "resource" : "Resource" ,
107108 "object" : "Object" ,
108109 "variant" : "Variant" ,
109110}
110- # Sync with editor\register_editor_types.cpp
111- EDITOR_CLASSES : list [str ] = [
112- "FileSystemDock" ,
113- "ScriptCreateDialog" ,
114- "ScriptEditor" ,
115- "ScriptEditorBase" ,
116- ]
111+
117112# Sync with the types mentioned in https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_differences.html
118113CLASSES_WITH_CSHARP_DIFFERENCES : list [str ] = [
119114 "@GlobalScope" ,
@@ -170,6 +165,7 @@ def __init__(self) -> None:
170165
171166 # Additional content and structure checks and validators.
172167 self .script_language_parity_check : ScriptLanguageParityCheck = ScriptLanguageParityCheck ()
168+ self .reserved_tag_check : ReservedTagCheck = ReservedTagCheck ()
173169
174170 def parse_class (self , class_root : ET .Element , filepath : str ) -> None :
175171 class_name = class_root .attrib ["name" ]
@@ -183,6 +179,8 @@ def parse_class(self, class_root: ET.Element, filepath: str) -> None:
183179 if inherits is not None :
184180 class_def .inherits = inherits
185181
182+ class_def .api_type = class_root .get ("api_type" )
183+
186184 class_def .deprecated = class_root .get ("deprecated" )
187185 class_def .experimental = class_root .get ("experimental" )
188186
@@ -594,8 +592,8 @@ class ClassDef(DefinitionBase):
594592 def __init__ (self , name : str ) -> None :
595593 super ().__init__ ("class" , name )
596594
597- self .class_group = "variant"
598- self .editor_class = self . _is_editor_class ()
595+ self .class_group : str = "variant"
596+ self .api_type : str | None = None
599597
600598 self .constants : OrderedDict [str , ConstantDef ] = OrderedDict ()
601599 self .enums : OrderedDict [str , EnumDef ] = OrderedDict ()
@@ -615,14 +613,6 @@ def __init__(self, name: str) -> None:
615613 # Used to match the class with XML source for output filtering purposes.
616614 self .filepath : str = ""
617615
618- def _is_editor_class (self ) -> bool :
619- if self .name .startswith ("Editor" ):
620- return True
621- if self .name in EDITOR_CLASSES :
622- return True
623-
624- return False
625-
626616 def update_class_group (self , state : State ) -> None :
627617 group_name = "variant"
628618
@@ -675,11 +665,61 @@ def add_hit(self, class_name: str, context: DefinitionBase, error: str, state: S
675665 self .hit_map [class_name ].append ((context , error ))
676666
677667
668+ # Checks if reserved tags have matching opening/closing pairs.
669+ class ReservedTagCheck :
670+ def __init__ (self ) -> None :
671+ self .tag_depth = 0 # Number of opening tags - closing tags.
672+ self .tag_stack : list [
673+ str
674+ ] = [] # List of unmatched opening tags. When tags mismatch, len(tag_stack) may differ from tag_depth.
675+ self .tag_stack_error : str = "" # First occurrence of a mismatched/duplicated opening/closing tag.
676+
677+ def reset (self ) -> None :
678+ self .tag_depth = 0
679+ self .tag_stack .clear ()
680+ self .tag_stack_error = ""
681+
682+ def run_final_check (self ) -> None :
683+ if len (self .tag_stack ) > 0 :
684+ if self .tag_stack_error == "" :
685+ self .tag_stack_error = f"unmatched opening tag(s) [{ '][' .join (self .tag_stack )} ]"
686+
687+ def add_opening_tag (self , tag_state_name : str ) -> None :
688+ self .tag_depth += 1
689+
690+ if tag_state_name in self .tag_stack :
691+ if self .tag_stack_error == "" :
692+ self .tag_stack_error = f"duplicated opening tags [{ '][' .join (self .tag_stack )} ][{ tag_state_name } ]"
693+
694+ self .tag_stack .append (tag_state_name )
695+
696+ def add_closing_tag (self , tag_state_name : str ) -> None :
697+ self .tag_depth -= 1
698+
699+ if len (self .tag_stack ) <= 0 :
700+ if self .tag_stack_error == "" :
701+ self .tag_stack_error = f"extra closing tag [/{ tag_state_name } ]"
702+ elif tag_state_name != self .tag_stack [- 1 ]:
703+ if tag_state_name in self .tag_stack :
704+ if self .tag_stack_error == "" :
705+ self .tag_stack_error = f"mismatched closing tag [{ '][' .join (self .tag_stack )} ][/{ tag_state_name } ]"
706+
707+ self .tag_stack .reverse ()
708+ self .tag_stack .remove (tag_state_name )
709+ self .tag_stack .reverse ()
710+ else :
711+ if self .tag_stack_error == "" :
712+ self .tag_stack_error = f"unmatched closing tag [{ '][' .join (self .tag_stack )} ][/{ tag_state_name } ]"
713+ else :
714+ # Correct closing tag.
715+ self .tag_stack .pop ()
716+
717+
678718def get_engine_url () -> str :
679719 return "https://github.com/godotengine/godot"
680720
681721def get_engine_release () -> str :
682- return "4.6 -stable"
722+ return "4.7 -stable"
683723
684724
685725# Entry point for the RST generator.
@@ -836,7 +876,7 @@ def main() -> None:
836876 grouped_classes [class_def .class_group ] = []
837877 grouped_classes [class_def .class_group ].append (class_name )
838878
839- if class_def .editor_class :
879+ if class_def .api_type == "editor" :
840880 if "editor" not in grouped_classes :
841881 grouped_classes ["editor" ] = []
842882 grouped_classes ["editor" ].append (class_name )
@@ -1902,8 +1942,21 @@ def format_text_block(
19021942 has_codeblocks_csharp = False
19031943
19041944 pos = 0
1905- tag_depth = 0
1945+ state . reserved_tag_check . reset ()
19061946 while True :
1947+ if state .reserved_tag_check .tag_depth > 2 or (
1948+ state .reserved_tag_check .tag_depth == 2
1949+ and not (
1950+ len (state .reserved_tag_check .tag_stack ) == 2
1951+ and state .reserved_tag_check .tag_stack [0 ] == "codeblocks"
1952+ and state .reserved_tag_check .tag_stack [1 ] in ("gdscript" , "csharp" )
1953+ )
1954+ ):
1955+ print_warning (
1956+ f"{ state .current_class } .xml: Found nested tags [{ '][' .join (state .reserved_tag_check .tag_stack )} ] in { context_name } (online doc will contain invalid RST markup)." ,
1957+ state ,
1958+ )
1959+
19071960 pos = text .find ("[" , pos )
19081961 if pos == - 1 :
19091962 break
@@ -1941,7 +1994,7 @@ def format_text_block(
19411994 if tag_state .closing and tag_state .name == inside_code_tag :
19421995 if is_in_tagset (tag_state .name , RESERVED_CODEBLOCK_TAGS ):
19431996 tag_text = ""
1944- tag_depth -= 1
1997+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
19451998 inside_code = False
19461999 ignore_code_warnings = False
19472000 # Strip newline if the tag was alone on one
@@ -1950,7 +2003,7 @@ def format_text_block(
19502003
19512004 elif is_in_tagset (tag_state .name , ["code" ]):
19522005 tag_text = "``"
1953- tag_depth -= 1
2006+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
19542007 inside_code = False
19552008 ignore_code_warnings = False
19562009 escape_post = True
@@ -1979,16 +2032,16 @@ def format_text_block(
19792032 has_codeblocks_gdscript = False
19802033 has_codeblocks_csharp = False
19812034
1982- tag_depth -= 1
2035+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
19832036 tag_text = ""
19842037 inside_code_tabs = False
19852038 else :
1986- tag_depth += 1
2039+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
19872040 tag_text = "\n .. tabs::"
19882041 inside_code_tabs = True
19892042
19902043 elif is_in_tagset (tag_state .name , RESERVED_CODEBLOCK_TAGS ):
1991- tag_depth += 1
2044+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
19922045
19932046 if tag_state .name == "gdscript" :
19942047 if not inside_code_tabs :
@@ -2027,7 +2080,7 @@ def format_text_block(
20272080
20282081 elif is_in_tagset (tag_state .name , ["code" ]):
20292082 tag_text = "``"
2030- tag_depth += 1
2083+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
20312084
20322085 inside_code = True
20332086 inside_code_tag = "code"
@@ -2314,6 +2367,17 @@ def format_text_block(
23142367 )
23152368 break
23162369 link_title = text [endq_pos + 1 : endurl_pos ]
2370+ for rft in RESERVED_FORMATTING_TAGS :
2371+ if link_title .find (f"[{ rft } ]" ) != - 1 :
2372+ print_warning (
2373+ f"{ state .current_class } .xml: Found nested tags [url][{ rft } ] in { context_name } (online doc will contain invalid RST markup)." ,
2374+ state ,
2375+ )
2376+ elif link_title .find (f"[/{ rft } ]" ) != - 1 :
2377+ print_warning (
2378+ f"{ state .current_class } .xml: Found nested tags [url][/{ rft } ] in { context_name } (online doc will contain invalid RST markup)." ,
2379+ state ,
2380+ )
23172381 tag_text = make_link (url_target , link_title )
23182382
23192383 pre_text = text [:pos ]
@@ -2337,35 +2401,35 @@ def format_text_block(
23372401
23382402 elif tag_state .name == "center" :
23392403 if tag_state .closing :
2340- tag_depth -= 1
2404+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
23412405 else :
2342- tag_depth += 1
2406+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
23432407 tag_text = ""
23442408
23452409 elif tag_state .name == "i" :
23462410 if tag_state .closing :
2347- tag_depth -= 1
2411+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
23482412 escape_post = True
23492413 else :
2350- tag_depth += 1
2414+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
23512415 escape_pre = True
23522416 tag_text = "*"
23532417
23542418 elif tag_state .name == "b" :
23552419 if tag_state .closing :
2356- tag_depth -= 1
2420+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
23572421 escape_post = True
23582422 else :
2359- tag_depth += 1
2423+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
23602424 escape_pre = True
23612425 tag_text = "**"
23622426
23632427 elif tag_state .name == "u" :
23642428 if tag_state .closing :
2365- tag_depth -= 1
2429+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
23662430 escape_post = True
23672431 else :
2368- tag_depth += 1
2432+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
23692433 escape_pre = True
23702434 tag_text = ""
23712435
@@ -2378,11 +2442,11 @@ def format_text_block(
23782442 elif tag_state .name == "kbd" :
23792443 tag_text = "`"
23802444 if tag_state .closing :
2381- tag_depth -= 1
2445+ state . reserved_tag_check . add_closing_tag ( tag_state . name )
23822446 escape_post = True
23832447 else :
23842448 tag_text = ":kbd:" + tag_text
2385- tag_depth += 1
2449+ state . reserved_tag_check . add_opening_tag ( tag_state . name )
23862450 escape_pre = True
23872451
23882452 # Invalid syntax.
@@ -2433,12 +2497,20 @@ def format_text_block(
24332497 text = pre_text + tag_text + post_text
24342498 pos = len (pre_text ) + len (tag_text )
24352499
2436- if tag_depth > 0 :
2500+ if state . reserved_tag_check . tag_depth != 0 :
24372501 print_error (
24382502 f"{ state .current_class } .xml: Tag depth mismatch: too many (or too few) open/close tags in { context_name } ." ,
24392503 state ,
24402504 )
24412505
2506+ state .reserved_tag_check .run_final_check ()
2507+
2508+ if state .reserved_tag_check .tag_stack_error != "" :
2509+ print_error (
2510+ f"{ state .current_class } .xml: Tag order mismatch: { state .reserved_tag_check .tag_stack_error } in { context_name } ." ,
2511+ state ,
2512+ )
2513+
24422514 return text
24432515
24442516
0 commit comments