1717
1818from collections import defaultdict
1919import json
20- import logging
2120from pathlib import Path
2221
2322from docutils .utils import get_source_line
24- from docutils import nodes
2523from sphinx .util import logging as sphinx_logging
2624
2725import matplotlib
2826
2927logger = sphinx_logging .getLogger (__name__ )
3028
3129
32- class MissingReferenceFilter (logging .Filter ):
33- """
34- A logging filter designed to record missing reference warning messages
35- for use by this extension
36- """
37- def __init__ (self , app ):
38- self .app = app
39- super ().__init__ ()
40-
41- def _record_reference (self , record ):
42- if not (getattr (record , 'type' , '' ) == 'ref' and
43- isinstance (getattr (record , 'location' , None ), nodes .Node )):
44- return
45-
46- if not hasattr (self .app .env , "missing_references_warnings" ):
47- self .app .env .missing_references_warnings = defaultdict (set )
48-
49- record_missing_reference (self .app ,
50- self .app .env .missing_references_warnings ,
51- record .location )
52-
53- def filter (self , record ):
54- self ._record_reference (record )
55- return True
56-
57-
58- def record_missing_reference (app , record , node ):
59- domain = node ["refdomain" ]
60- typ = node ["reftype" ]
61- target = node ["reftarget" ]
62- location = get_location (node , app )
63-
64- domain_type = f"{ domain } :{ typ } "
65-
66- record [(domain_type , target )].add (location )
67-
68-
69- def record_missing_reference_handler (app , env , node , contnode ):
70- """
71- When the sphinx app notices a missing reference, it emits an
72- event which calls this function. This function records the missing
73- references for analysis at the end of the sphinx build.
74- """
75- if not app .config .missing_references_enabled :
76- # no-op when we are disabled.
77- return
78-
79- if not hasattr (env , "missing_references_events" ):
80- env .missing_references_events = defaultdict (set )
81-
82- record_missing_reference (app , env .missing_references_events , node )
83-
84-
8530def get_location (node , app ):
8631 """
8732 Given a docutils node and a sphinx application, return a string
@@ -146,10 +91,34 @@ def _truncate_location(location):
14691 return location .split (":" , 1 )[0 ]
14792
14893
149- def _warn_unused_missing_references (app ):
150- if not app .config .missing_references_warn_unused_ignores :
151- return
94+ def handle_missing_reference (app , domain , node ):
95+ """
96+ Handle the warn-missing-reference Sphinx event.
97+
98+ This function will:
15299
100+ #. record missing references for saving/comparing with ignored list.
101+ #. prevent Sphinx from raising a warning on ignored references.
102+ """
103+ typ = node ["reftype" ]
104+ target = node ["reftarget" ]
105+ location = get_location (node , app )
106+ domain_type = f"{ domain .name } :{ typ } "
107+
108+ app .env .missing_references_events [(domain_type , target )].add (location )
109+
110+ # If we're ignoring this event, return True so that Sphinx thinks we handled it,
111+ # even though we didn't print or warn. If we aren't ignoring it, Sphinx will print a
112+ # warning about the missing reference.
113+ if location in app .env .missing_references_ignored_references .get (
114+ (domain_type , target ), []):
115+ return True
116+
117+
118+ def warn_unused_missing_references (app , exc ):
119+ """
120+ Check that all lines of the existing JSON file are still necessary.
121+ """
153122 # We can only warn if we are building from a source install
154123 # otherwise, we just have to skip this step.
155124 basepath = Path (matplotlib .__file__ ).parent .parent .parent .resolve ()
@@ -159,9 +128,8 @@ def _warn_unused_missing_references(app):
159128 return
160129
161130 # This is a dictionary of {(domain_type, target): locations}
162- references_ignored = getattr (
163- app .env , 'missing_references_ignored_references' , {})
164- references_events = getattr (app .env , 'missing_references_events' , {})
131+ references_ignored = app .env .missing_references_ignored_references
132+ references_events = app .env .missing_references_events
165133
166134 # Warn about any reference which is no longer missing.
167135 for (domain_type , target ), locations in references_ignored .items ():
@@ -184,26 +152,13 @@ def _warn_unused_missing_references(app):
184152 subtype = domain_type )
185153
186154
187- def save_missing_references_handler (app , exc ):
155+ def save_missing_references (app , exc ):
188156 """
189- At the end of the sphinx build, check that all lines of the existing JSON
190- file are still necessary.
191-
192- If the configuration value ``missing_references_write_json`` is set
193- then write a new JSON file containing missing references.
157+ Write a new JSON file containing missing references.
194158 """
195- if not app .config .missing_references_enabled :
196- # no-op when we are disabled.
197- return
198-
199- _warn_unused_missing_references (app )
200-
201159 json_path = Path (app .confdir ) / app .config .missing_references_filename
202-
203- references_warnings = getattr (app .env , 'missing_references_warnings' , {})
204-
205- if app .config .missing_references_write_json :
206- _write_missing_references_json (references_warnings , json_path )
160+ references_warnings = app .env .missing_references_events
161+ _write_missing_references_json (references_warnings , json_path )
207162
208163
209164def _write_missing_references_json (records , json_path ):
@@ -220,6 +175,7 @@ def _write_missing_references_json(records, json_path):
220175 transformed_records [domain_type ][target ] = sorted (paths )
221176 with json_path .open ("w" ) as stream :
222177 json .dump (transformed_records , stream , sort_keys = True , indent = 2 )
178+ stream .write ("\n " ) # Silence pre-commit no-newline-at-end-of-file warning.
223179
224180
225181def _read_missing_references_json (json_path ):
@@ -242,49 +198,25 @@ def _read_missing_references_json(json_path):
242198 return ignored_references
243199
244200
245- def prepare_missing_references_handler (app ):
201+ def prepare_missing_references_setup (app ):
246202 """
247- Handler called to initialize this extension once the configuration
248- is ready.
249-
250- Reads the missing references file and populates ``nitpick_ignore`` if
251- appropriate.
203+ Initialize this extension once the configuration is ready.
252204 """
253205 if not app .config .missing_references_enabled :
254206 # no-op when we are disabled.
255207 return
256208
257- sphinx_logger = logging .getLogger ('sphinx' )
258- missing_reference_filter = MissingReferenceFilter (app )
259- for handler in sphinx_logger .handlers :
260- if (isinstance (handler , sphinx_logging .WarningStreamHandler )
261- and missing_reference_filter not in handler .filters ):
262-
263- # This *must* be the first filter, because subsequent filters
264- # throw away the node information and then we can't identify
265- # the reference uniquely.
266- handler .filters .insert (0 , missing_reference_filter )
267-
268- app .env .missing_references_ignored_references = {}
209+ app .connect ("warn-missing-reference" , handle_missing_reference )
210+ if app .config .missing_references_warn_unused_ignores :
211+ app .connect ("build-finished" , warn_unused_missing_references )
212+ if app .config .missing_references_write_json :
213+ app .connect ("build-finished" , save_missing_references )
269214
270215 json_path = Path (app .confdir ) / app .config .missing_references_filename
271- if not json_path .exists ():
272- return
273-
274- ignored_references = _read_missing_references_json (json_path )
275-
276- app .env .missing_references_ignored_references = ignored_references
277-
278- # If we are going to re-write the JSON file, then don't suppress missing
279- # reference warnings. We want to record a full list of missing references
280- # for use later. Otherwise, add all known missing references to
281- # ``nitpick_ignore```
282- if not app .config .missing_references_write_json :
283- # Since Sphinx v6.2, nitpick_ignore may be a list, set or tuple, and
284- # defaults to set. Previously it was always a list. Cast to list for
285- # consistency across versions.
286- app .config .nitpick_ignore = list (app .config .nitpick_ignore )
287- app .config .nitpick_ignore .extend (ignored_references .keys ())
216+ app .env .missing_references_ignored_references = (
217+ _read_missing_references_json (json_path ) if json_path .exists () else {}
218+ )
219+ app .env .missing_references_events = defaultdict (set )
288220
289221
290222def setup (app ):
@@ -294,8 +226,6 @@ def setup(app):
294226 app .add_config_value ("missing_references_filename" ,
295227 "missing-references.json" , "env" )
296228
297- app .connect ("builder-inited" , prepare_missing_references_handler )
298- app .connect ("missing-reference" , record_missing_reference_handler )
299- app .connect ("build-finished" , save_missing_references_handler )
229+ app .connect ("builder-inited" , prepare_missing_references_setup )
300230
301231 return {'parallel_read_safe' : True }
0 commit comments