@@ -314,28 +314,71 @@ def _get_class_prop_comments(clz: type[Component]) -> Mapping[str, tuple[str, ..
314314 """
315315 props_comments : dict [str , tuple [str , ...]] = {}
316316 comments = []
317+ last_prop = ""
318+ in_docstring = False
319+ docstring_lines : list [str ] = []
317320 for line in _get_source (clz ).splitlines ():
318321 reached_functions = re .search (r"def " , line )
319322 if reached_functions :
320323 # We've reached the functions, so stop.
321324 break
322325
326+ stripped = line .strip ()
327+
328+ # Handle triple-quoted docstrings after prop definitions.
329+ if in_docstring :
330+ if '"""' in stripped or "'''" in stripped :
331+ # End of multi-line docstring.
332+ end_text = stripped .partition ('"""' )[0 ] or stripped .partition ("'''" )[0 ]
333+ if end_text :
334+ docstring_lines .append (end_text .strip ())
335+ if last_prop and docstring_lines :
336+ props_comments [last_prop ] = tuple (docstring_lines )
337+ in_docstring = False
338+ docstring_lines = []
339+ last_prop = ""
340+ else :
341+ docstring_lines .append (stripped )
342+ continue
343+
344+ # Check for start of a docstring right after a prop.
345+ if last_prop and (stripped .startswith (('"""' , "'''" ))):
346+ quote = '"""' if stripped .startswith ('"""' ) else "'''"
347+ content_after_open = stripped [3 :]
348+ if quote in content_after_open :
349+ # Single-line docstring: """text"""
350+ doc_text = content_after_open .partition (quote )[0 ].strip ()
351+ if doc_text :
352+ props_comments [last_prop ] = (doc_text ,)
353+ last_prop = ""
354+ else :
355+ # Multi-line docstring starts here.
356+ in_docstring = True
357+ docstring_lines = []
358+ first_line = content_after_open .strip ()
359+ if first_line :
360+ docstring_lines .append (first_line )
361+ continue
362+
323363 if line == "" :
324364 # We hit a blank line, so clear comments to avoid commented out prop appearing in next prop docs.
325365 comments .clear ()
366+ last_prop = ""
326367 continue
327368
328369 # Get comments for prop
329- if line . strip () .startswith ("#" ):
370+ if stripped .startswith ("#" ):
330371 # Remove noqa from the comments.
331372 line = line .partition (" # noqa" )[0 ]
332373 comments .append (line )
374+ last_prop = ""
333375 continue
334376
335377 # Check if this line has a prop.
336378 match = re .search (r"\w+:" , line )
337379 if match is None :
338380 # This line doesn't have a var, so continue.
381+ last_prop = ""
339382 continue
340383
341384 # Get the prop.
@@ -345,6 +388,7 @@ def _get_class_prop_comments(clz: type[Component]) -> Mapping[str, tuple[str, ..
345388 comment .strip ().lstrip ("#" ).strip () for comment in comments
346389 )
347390 comments .clear ()
391+ last_prop = prop
348392
349393 return MappingProxyType (props_comments )
350394
@@ -1286,6 +1330,23 @@ def visit_Assign(self, node: ast.Assign) -> ast.Assign | None:
12861330
12871331 return node
12881332
1333+ def visit_Expr (self , node : ast .Expr ) -> ast .Expr | None :
1334+ """Remove bare string expressions (attribute docstrings) in component classes.
1335+
1336+ Args:
1337+ node: The Expr node to visit.
1338+
1339+ Returns:
1340+ The modified Expr node (or None).
1341+ """
1342+ if (
1343+ self ._current_class_is_component ()
1344+ and isinstance (node .value , ast .Constant )
1345+ and isinstance (node .value .value , str )
1346+ ):
1347+ return None
1348+ return node
1349+
12891350 def visit_AnnAssign (self , node : ast .AnnAssign ) -> ast .AnnAssign | None :
12901351 """Visit an AnnAssign node (Annotated assignment).
12911352
0 commit comments