@@ -263,6 +263,8 @@ class DSLParser:
263263 critical_section : bool
264264 target_critical_section : list [str ]
265265 disable_fastcall : bool
266+ # Line of the file which is being parsed.
267+ line_number : int | None
266268 from_version_re = re .compile (r'([*/]) +\[from +(.+)\]' )
267269 permit_long_summary = False
268270 permit_long_docstring_body = False
@@ -286,6 +288,7 @@ def __init__(self, clinic: Clinic) -> None:
286288
287289 def reset (self ) -> None :
288290 self .function = None
291+ self .line_number = None
289292 self .state = self .state_dsl_start
290293 self .expecting_parameters = True
291294 self .keyword_only = False
@@ -509,6 +512,7 @@ def parse(self, block: Block) -> None:
509512 if '\t ' in line :
510513 fail (f'Tab characters are illegal in the Clinic DSL: { line !r} ' ,
511514 line_number = block_start )
515+ self .line_number = line_number
512516 try :
513517 self .state (line )
514518 except ClinicError as exc :
@@ -517,7 +521,14 @@ def parse(self, block: Block) -> None:
517521 raise
518522
519523 self .do_post_block_processing_cleanup (line_number )
520- block .output .extend (self .clinic .language .render (self .clinic , block .signatures ))
524+ try :
525+ block .output .extend (
526+ self .clinic .language .render (self .clinic , block .signatures ))
527+ except ClinicError as exc :
528+ if exc .lineno is None :
529+ exc .lineno = line_number
530+ exc .filename = self .clinic .filename
531+ raise
521532
522533 if self .preserve_output :
523534 if block .output :
@@ -666,6 +677,8 @@ def parse_cloned_function(self, names: FunctionNames, existing: str) -> None:
666677 "cls" : cls ,
667678 "c_basename" : c_basename ,
668679 "docstring" : "" ,
680+ "docstring_line_number" : None ,
681+ "line_number" : self .line_number ,
669682 }
670683 if not (existing_function .kind is self .kind and
671684 existing_function .coexist == self .coexist ):
@@ -735,7 +748,8 @@ def state_modulename_name(self, line: str) -> None:
735748 critical_section = self .critical_section ,
736749 disable_fastcall = self .disable_fastcall ,
737750 target_critical_section = self .target_critical_section ,
738- forced_text_signature = self .forced_text_signature
751+ forced_text_signature = self .forced_text_signature ,
752+ line_number = self .line_number ,
739753 )
740754 self .add_function (func )
741755
@@ -1141,7 +1155,8 @@ def bad_node(self, node: ast.AST) -> None:
11411155 converter = converter , default = value ,
11421156 group = self .group_stack [- 1 ] if self .group_stack else 0 ,
11431157 group_depth = len (self .group_stack ),
1144- deprecated_positional = self .deprecated_positional )
1158+ deprecated_positional = self .deprecated_positional ,
1159+ line_number = self .line_number )
11451160
11461161 names = [k .name for k in self .function .parameters .values ()]
11471162 if parameter_name in names [1 :]:
@@ -1338,6 +1353,8 @@ def docstring_append(self, obj: Function | Parameter, line: str) -> None:
13381353 docstring = obj .docstring
13391354 if docstring :
13401355 docstring += "\n "
1356+ elif isinstance (obj , Function ) and line .rstrip ():
1357+ obj .docstring_line_number = self .line_number
13411358 if stripped := line .rstrip ():
13421359 docstring += self .indent .dedent (stripped )
13431360 obj .docstring = docstring
@@ -1581,12 +1598,19 @@ def format_docstring(self) -> str:
15811598 # Guido said Clinic should enforce this:
15821599 # http://mail.python.org/pipermail/python-dev/2013-June/127110.html
15831600
1601+ def docstring_line (index : int ) -> int | None :
1602+ """Return the line of the file which holds the index-th line."""
1603+ if f .docstring_line_number is None :
1604+ return None
1605+ return f .docstring_line_number + index
1606+
15841607 lines = f .docstring .split ('\n ' )
15851608 if len (lines ) >= 2 :
15861609 if lines [1 ]:
15871610 fail (f"Docstring for { f .full_name !r} does not have a summary line!\n "
15881611 "Every non-blank function docstring must start with "
1589- "a single line summary followed by an empty line." )
1612+ "a single line summary followed by an empty line." ,
1613+ line_number = docstring_line (1 ))
15901614 elif len (lines ) == 1 :
15911615 # the docstring is only one line right now--the summary line.
15921616 # add an empty line after the summary line so we have space
@@ -1598,28 +1622,36 @@ def format_docstring(self) -> str:
15981622 # Existing violations are recorded in OVERLONG_{SUMMARY,BODY}.
15991623 max_width = f .docstring_line_width
16001624 summary_len = len (lines [0 ])
1601- max_body = max (map (len , lines [1 :]))
1625+ long_body = [i for i , line in enumerate (lines )
1626+ if i and len (line ) > max_width ]
16021627 if summary_len > max_width :
16031628 if not self .permit_long_summary :
16041629 fail (f"Summary line for { f .full_name !r} is too long!\n "
1605- f"The summary line must be no longer than { max_width } characters." )
1630+ f"The summary line must be no longer than { max_width } characters." ,
1631+ line_number = docstring_line (0 ))
16061632 else :
16071633 if self .permit_long_summary :
16081634 warn ("Remove the @permit_long_summary decorator from "
1609- f"{ f .full_name !r} !\n " )
1635+ f"{ f .full_name !r} !\n " , filename = self .clinic .filename ,
1636+ line_number = f .line_number )
16101637
1611- if max_body > max_width :
1638+ if long_body :
16121639 if not self .permit_long_docstring_body :
16131640 warn (f"Docstring lines for { f .full_name !r} are too long!\n "
1614- f"Lines should be no longer than { max_width } characters." )
1641+ f"Lines should be no longer than { max_width } characters." ,
1642+ filename = self .clinic .filename ,
1643+ line_number = docstring_line (long_body [0 ]))
16151644 else :
16161645 if self .permit_long_docstring_body :
16171646 warn ("Remove the @permit_long_docstring_body decorator from "
1618- f"{ f .full_name !r} !\n " )
1647+ f"{ f .full_name !r} !\n " , filename = self .clinic .filename ,
1648+ line_number = f .line_number )
16191649
1650+ markers = [i for i , line in enumerate (lines ) if '{parameters}' in line ]
16201651 parameters_marker_count = len (f .docstring .split ('{parameters}' )) - 1
16211652 if parameters_marker_count > 1 :
1622- fail ('You may not specify {parameters} more than once in a docstring!' )
1653+ fail ('You may not specify {parameters} more than once in a docstring!' ,
1654+ line_number = docstring_line (markers [- 1 ]))
16231655
16241656 # insert signature at front and params after the summary line
16251657 if not parameters_marker_count :
@@ -1679,6 +1711,7 @@ def do_post_block_processing_cleanup(self, lineno: int) -> None:
16791711 try :
16801712 self .function .docstring = self .format_docstring ()
16811713 except ClinicError as exc :
1682- exc .lineno = lineno
1714+ if exc .lineno is None :
1715+ exc .lineno = lineno
16831716 exc .filename = self .clinic .filename
16841717 raise
0 commit comments