18
18
import inspect
19
19
import logging
20
20
import re
21
+ from ast import literal_eval
21
22
from pathlib import Path
23
+ from textwrap import dedent
22
24
from typing import Callable , Optional
23
25
24
26
import pytest
25
- from griffe import Class , Docstring , Function , Module , Object
27
+ from griffe import Class , Docstring , Function , Module , Object , LinesCollection
26
28
27
29
# noinspection PyProtectedMember
28
30
from mkdocstrings_handlers .python_xref .crossref import (
29
31
_RE_CROSSREF ,
30
32
_RE_REL_CROSSREF ,
31
33
_RelativeCrossrefProcessor ,
32
- substitute_relative_crossrefs ,
34
+ substitute_relative_crossrefs , doc_value_offset_to_location ,
33
35
)
34
36
35
37
def test_RelativeCrossrefProcessor (caplog : pytest .LogCaptureFixture ) -> None :
@@ -153,6 +155,7 @@ def test_substitute_relative_crossrefs(caplog: pytest.LogCaptureFixture) -> None
153
155
""" ,
154
156
parent = meth1 ,
155
157
lineno = 42 ,
158
+ endlineno = 45 ,
156
159
)
157
160
158
161
mod1 .docstring = Docstring (
@@ -161,6 +164,7 @@ def test_substitute_relative_crossrefs(caplog: pytest.LogCaptureFixture) -> None
161
164
""" ,
162
165
parent = mod1 ,
163
166
lineno = 23 ,
167
+ endlineno = 25 ,
164
168
)
165
169
166
170
substitute_relative_crossrefs (mod1 )
@@ -173,3 +177,88 @@ def test_substitute_relative_crossrefs(caplog: pytest.LogCaptureFixture) -> None
173
177
)
174
178
175
179
assert len (caplog .records ) == 0
180
+
181
+ def make_docstring_from_source (
182
+ source : str ,
183
+ * ,
184
+ lineno : int = 1 ,
185
+ mod_name : str = "mod" ,
186
+ mod_dir : Path = Path ("" ),
187
+ ) -> Docstring :
188
+ """
189
+ Create a docstring object from source code.
190
+
191
+ Args:
192
+ source: raw source code containing docstring source lines
193
+ lineno: line number of docstring starting quotes
194
+ mod_name: name of module
195
+ mod_dir: module directory
196
+ """
197
+ filepath = mod_dir .joinpath (mod_name ).with_suffix (".py" )
198
+ parent = Object ("" , lines_collection = LinesCollection ())
199
+ mod = Module (name = mod_name , filepath = filepath , parent = parent )
200
+ lines = source .splitlines (keepends = False )
201
+ if lineno > 1 :
202
+ # Insert empty lines to pad to the desired line number
203
+ lines = ["" ] * (lineno - 1 ) + lines
204
+ mod .lines_collection [filepath ] = lines
205
+ doc = Docstring (
206
+ parent = mod ,
207
+ value = inspect .cleandoc (literal_eval (source )),
208
+ lineno = lineno ,
209
+ endlineno = len (lines )
210
+ )
211
+ return doc
212
+
213
+ def test_doc_value_offset_to_location () -> None :
214
+ """Unit test for _doc_value_offset_to_location."""
215
+ doc1 = make_docstring_from_source (
216
+ dedent (
217
+ '''
218
+ """first
219
+ second
220
+ third
221
+ """
222
+ '''
223
+ ).lstrip ("\n " ),
224
+ )
225
+
226
+ assert doc_value_offset_to_location (doc1 , 0 ) == (1 , 3 )
227
+ assert doc_value_offset_to_location (doc1 , 3 ) == (1 , 6 )
228
+ assert doc_value_offset_to_location (doc1 , 7 ) == (2 , 1 )
229
+ assert doc_value_offset_to_location (doc1 , 15 ) == (3 , 2 )
230
+
231
+ doc2 = make_docstring_from_source (
232
+ dedent (
233
+ '''
234
+ """ first
235
+ second
236
+ third
237
+ """ # a comment
238
+ # another comment
239
+ '''
240
+ ).lstrip ("\n " ),
241
+ lineno = 3 ,
242
+ )
243
+
244
+ assert doc_value_offset_to_location (doc2 , 0 ) == (3 , 9 )
245
+ assert doc_value_offset_to_location (doc2 , 6 ) == (4 , 6 )
246
+ assert doc_value_offset_to_location (doc2 , 15 ) == (5 , 8 )
247
+
248
+ # Remove parent so that source is not available
249
+ doc2 .parent = None
250
+ assert doc_value_offset_to_location (doc2 , 0 ) == (3 , - 1 )
251
+
252
+ doc3 = make_docstring_from_source (
253
+ dedent (
254
+ """
255
+ '''
256
+ first
257
+ second
258
+ '''
259
+ """
260
+ ).lstrip ("\n " ),
261
+ )
262
+
263
+ assert doc_value_offset_to_location (doc3 , 0 ) == (2 , 4 )
264
+ assert doc_value_offset_to_location (doc3 , 6 ) == (3 , 2 )
0 commit comments