Coverage for tests/test_docstrings/test_sphinx.py: 100.00%

435 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-11 13:44 +0200

1"""Tests for the [Sphinx-style parser][griffe.docstrings.sphinx].""" 

2 

3from __future__ import annotations 

4 

5import inspect 

6from typing import TYPE_CHECKING, Any 

7 

8import pytest 

9 

10from griffe import ( 

11 Attribute, 

12 Class, 

13 DocstringAttribute, 

14 DocstringParameter, 

15 DocstringRaise, 

16 DocstringReturn, 

17 DocstringSectionKind, 

18 Expr, 

19 ExprAttribute, 

20 ExprBinOp, 

21 ExprName, 

22 ExprSubscript, 

23 ExprTuple, 

24 Function, 

25 Module, 

26 Parameter, 

27 Parameters, 

28) 

29 

30if TYPE_CHECKING: 

31 from tests.test_docstrings.helpers import ParserType 

32 

33SOME_NAME = "foo" 

34SOME_TEXT = "descriptive test text" 

35SOME_EXTRA_TEXT = "more test text" 

36SOME_EXCEPTION_NAME = "SomeException" 

37SOME_OTHER_EXCEPTION_NAME = "SomeOtherException" 

38 

39 

40@pytest.mark.parametrize( 

41 "docstring", 

42 [ 

43 "One line docstring description", 

44 """ 

45 Multiple line docstring description. 

46 

47 With more text. 

48 """, 

49 ], 

50) 

51def test_parse__description_only_docstring__single_markdown_section(parse_sphinx: ParserType, docstring: str) -> None: 

52 """Parse a single or multiline docstring. 

53 

54 Parameters: 

55 parse_sphinx: Fixture parser. 

56 docstring: A parametrized docstring. 

57 """ 

58 sections, warnings = parse_sphinx(docstring) 

59 

60 assert len(sections) == 1 

61 assert sections[0].kind is DocstringSectionKind.text 

62 assert sections[0].value == inspect.cleandoc(docstring) 

63 assert not warnings 

64 

65 

66def test_parse__no_description__single_markdown_section(parse_sphinx: ParserType) -> None: 

67 """Parse an empty docstring. 

68 

69 Parameters: 

70 parse_sphinx: Fixture parser. 

71 """ 

72 sections, warnings = parse_sphinx("") 

73 

74 assert len(sections) == 1 

75 assert sections[0].kind is DocstringSectionKind.text 

76 assert sections[0].value == "" 

77 assert not warnings 

78 

79 

80def test_parse__multiple_blank_lines_before_description__single_markdown_section(parse_sphinx: ParserType) -> None: 

81 """Parse a docstring with initial blank lines. 

82 

83 Parameters: 

84 parse_sphinx: Fixture parser. 

85 """ 

86 sections, warnings = parse_sphinx( 

87 """ 

88 

89 

90 Now text""", 

91 ) 

92 

93 assert len(sections) == 1 

94 assert sections[0].kind is DocstringSectionKind.text 

95 assert sections[0].value == "Now text" 

96 assert not warnings 

97 

98 

99def test_parse__param_field__param_section(parse_sphinx: ParserType) -> None: 

100 """Parse a parameter section. 

101 

102 Parameters: 

103 parse_sphinx: Fixture parser. 

104 """ 

105 sections, _ = parse_sphinx( 

106 f""" 

107 Docstring with one line param. 

108 

109 :param {SOME_NAME}: {SOME_TEXT} 

110 """, 

111 ) 

112 assert len(sections) == 2 

113 assert sections[1].kind is DocstringSectionKind.parameters 

114 actual = sections[1].value[0] 

115 expected = DocstringParameter(SOME_NAME, description=SOME_TEXT) 

116 assert isinstance(actual, type(expected)) 

117 assert actual.as_dict() == expected.as_dict() 

118 

119 

120def test_parse__only_param_field__empty_markdown(parse_sphinx: ParserType) -> None: 

121 """Parse only a parameter section. 

122 

123 Parameters: 

124 parse_sphinx: Fixture parser. 

125 """ 

126 sections, _ = parse_sphinx(":param foo: text") 

127 assert len(sections) == 2 

128 assert sections[0].kind is DocstringSectionKind.text 

129 assert sections[0].value == "" 

130 

131 

132@pytest.mark.parametrize( 

133 "param_directive_name", 

134 [ 

135 "param", 

136 "parameter", 

137 "arg", 

138 "arguments", 

139 "key", 

140 "keyword", 

141 ], 

142) 

143def test_parse__all_param_names__param_section(parse_sphinx: ParserType, param_directive_name: str) -> None: 

144 """Parse all parameters directives. 

145 

146 Parameters: 

147 parse_sphinx: Fixture parser. 

148 param_directive_name: A parametrized directive name. 

149 """ 

150 sections, _ = parse_sphinx( 

151 f""" 

152 Docstring with one line param. 

153 

154 :{param_directive_name} {SOME_NAME}: {SOME_TEXT} 

155 """, 

156 ) 

157 assert len(sections) == 2 

158 assert sections[1].kind is DocstringSectionKind.parameters 

159 actual = sections[1].value[0] 

160 expected = DocstringParameter(SOME_NAME, description=SOME_TEXT) 

161 assert isinstance(actual, type(expected)) 

162 assert actual.as_dict() == expected.as_dict() 

163 

164 

165@pytest.mark.parametrize( 

166 "docstring", 

167 [ 

168 f""" 

169 Docstring with param with continuation, no indent. 

170 

171 :param {SOME_NAME}: {SOME_TEXT} 

172 {SOME_EXTRA_TEXT} 

173 """, 

174 f""" 

175 Docstring with param with continuation, with indent. 

176 

177 :param {SOME_NAME}: {SOME_TEXT} 

178 {SOME_EXTRA_TEXT} 

179 """, 

180 ], 

181) 

182def test_parse__param_field_multi_line__param_section(parse_sphinx: ParserType, docstring: str) -> None: 

183 """Parse multiline directives. 

184 

185 Parameters: 

186 parse_sphinx: Fixture parser. 

187 docstring: A parametrized docstring. 

188 """ 

189 sections, _ = parse_sphinx(docstring) 

190 assert len(sections) == 2 

191 assert sections[1].kind is DocstringSectionKind.parameters 

192 actual = sections[1].value[0] 

193 expected = DocstringParameter(SOME_NAME, description=f"{SOME_TEXT} {SOME_EXTRA_TEXT}") 

194 assert isinstance(actual, type(expected)) 

195 assert actual.as_dict() == expected.as_dict() 

196 

197 

198def test_parse__param_field_for_function__param_section_with_kind(parse_sphinx: ParserType) -> None: 

199 """Parse parameters. 

200 

201 Parameters: 

202 parse_sphinx: Fixture parser. 

203 """ 

204 docstring = f""" 

205 Docstring with line continuation. 

206 

207 :param foo: {SOME_TEXT} 

208 """ 

209 

210 sections, _ = parse_sphinx(docstring) 

211 assert len(sections) == 2 

212 assert sections[1].kind is DocstringSectionKind.parameters 

213 actual = sections[1].value[0] 

214 expected = DocstringParameter(SOME_NAME, description=SOME_TEXT) 

215 assert isinstance(actual, type(expected)) 

216 assert actual.as_dict() == expected.as_dict() 

217 

218 

219def test_parse__param_field_docs_type__param_section_with_type(parse_sphinx: ParserType) -> None: 

220 """Parse parameters with types. 

221 

222 Parameters: 

223 parse_sphinx: Fixture parser. 

224 """ 

225 docstring = f""" 

226 Docstring with line continuation. 

227 

228 :param str foo: {SOME_TEXT} 

229 """ 

230 

231 sections, _ = parse_sphinx(docstring) 

232 assert len(sections) == 2 

233 assert sections[1].kind is DocstringSectionKind.parameters 

234 actual = sections[1].value[0] 

235 expected = DocstringParameter(SOME_NAME, annotation="str", description=SOME_TEXT) 

236 assert isinstance(actual, type(expected)) 

237 assert actual.as_dict() == expected.as_dict() 

238 

239 

240@pytest.mark.parametrize("type_", ["str", "int"]) 

241def test_parse__param_field_type_field__param_section_with_type(parse_sphinx: ParserType, type_: str) -> None: 

242 """Parse parameters with separated types. 

243 

244 Parameters: 

245 parse_sphinx: Fixture parser. 

246 type_: The type to use in the type directive. 

247 """ 

248 docstring = f""" 

249 Docstring with line continuation. 

250 

251 :param {SOME_NAME}: {SOME_TEXT} 

252 :type {SOME_NAME}: {type_} 

253 """ 

254 

255 sections, _ = parse_sphinx(docstring) 

256 assert len(sections) == 2 

257 assert sections[1].kind is DocstringSectionKind.parameters 

258 actual = sections[1].value[0] 

259 expected = DocstringParameter(SOME_NAME, annotation=f"{type_}", description=SOME_TEXT) 

260 assert isinstance(actual, type(expected)) 

261 assert actual.as_dict() == expected.as_dict() 

262 

263 

264@pytest.mark.parametrize("type_", ["str", "int"]) 

265def test_parse__param_field_type_field__param_section_with_type_with_parent( 

266 parse_sphinx: ParserType, 

267 type_: str, 

268) -> None: 

269 """Parse parameters with separated types. 

270 

271 Parameters: 

272 parse_sphinx: Fixture parser. 

273 type_: The type to use in the type directive. 

274 """ 

275 docstring = f""" 

276 Docstring with line continuation. 

277 

278 :param {SOME_NAME}: {SOME_TEXT} 

279 :type {SOME_NAME}: {type_} 

280 """ 

281 parent_fn = Function("func", parameters=Parameters(Parameter(SOME_NAME))) 

282 sections, _ = parse_sphinx(docstring, parent=parent_fn) 

283 assert len(sections) == 2 

284 assert sections[1].kind is DocstringSectionKind.parameters 

285 actual = sections[1].value[0] 

286 expected_annotation = ExprName(name=f"{type_}") 

287 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT) 

288 assert isinstance(actual, type(expected)) 

289 assert actual.as_dict() == expected.as_dict() 

290 assert isinstance(actual.annotation, type(expected.annotation)) 

291 assert isinstance(actual.annotation, ExprName) 

292 assert isinstance(actual.annotation, Expr) 

293 assert actual.annotation.as_dict() == expected_annotation.as_dict() 

294 

295 

296def test_parse__param_field_type_field_first__param_section_with_type(parse_sphinx: ParserType) -> None: 

297 """Parse parameters with separated types first. 

298 

299 Parameters: 

300 parse_sphinx: Fixture parser. 

301 """ 

302 docstring = f""" 

303 Docstring with line continuation. 

304 

305 :type foo: str 

306 :param foo: {SOME_TEXT} 

307 """ 

308 

309 sections, _ = parse_sphinx(docstring) 

310 assert len(sections) == 2 

311 assert sections[1].kind is DocstringSectionKind.parameters 

312 actual = sections[1].value[0] 

313 expected = DocstringParameter(SOME_NAME, annotation="str", description=SOME_TEXT) 

314 assert isinstance(actual, type(expected)) 

315 assert actual.as_dict() == expected.as_dict() 

316 

317 

318def test_parse__param_field_type_field_first__param_section_with_type_with_parent(parse_sphinx: ParserType) -> None: 

319 """Parse parameters with separated types first. 

320 

321 Parameters: 

322 parse_sphinx: Fixture parser. 

323 """ 

324 docstring = f""" 

325 Docstring with line continuation. 

326 

327 :type {SOME_NAME}: str 

328 :param {SOME_NAME}: {SOME_TEXT} 

329 """ 

330 parent_fn = Function("func", parameters=Parameters(Parameter(SOME_NAME))) 

331 sections, _ = parse_sphinx(docstring, parent=parent_fn) 

332 assert len(sections) == 2 

333 assert sections[1].kind is DocstringSectionKind.parameters 

334 actual = sections[1].value[0] 

335 expected_annotation = ExprName("str", parent=Class("C")) 

336 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT) 

337 assert isinstance(actual, type(expected)) 

338 assert actual.as_dict() == expected.as_dict() 

339 assert isinstance(actual.annotation, type(expected.annotation)) 

340 assert isinstance(actual.annotation, ExprName) 

341 assert isinstance(actual.annotation, Expr) 

342 assert actual.annotation.as_dict() == expected_annotation.as_dict() 

343 

344 

345@pytest.mark.parametrize("union", ["str or None", "None or str", "str or int", "str or int or float"]) 

346def test_parse__param_field_type_field_or_none__param_section_with_optional( 

347 parse_sphinx: ParserType, 

348 union: str, 

349) -> None: 

350 """Parse parameters with separated union types. 

351 

352 Parameters: 

353 parse_sphinx: Fixture parser. 

354 union: A parametrized union type. 

355 """ 

356 docstring = f""" 

357 Docstring with line continuation. 

358 

359 :param foo: {SOME_TEXT} 

360 :type foo: {union} 

361 """ 

362 

363 sections, _ = parse_sphinx(docstring) 

364 assert len(sections) == 2 

365 assert sections[1].kind is DocstringSectionKind.parameters 

366 actual = sections[1].value[0] 

367 expected = DocstringParameter(SOME_NAME, annotation=union.replace(" or ", " | "), description=SOME_TEXT) 

368 assert isinstance(actual, type(expected)) 

369 assert actual.as_dict() == expected.as_dict() 

370 

371 

372@pytest.mark.parametrize( 

373 ("union", "expected_annotation"), 

374 [ 

375 ("str or None", ExprBinOp(ExprName("str"), "|", "None")), 

376 ("None or str", ExprBinOp("None", "|", ExprName("str"))), 

377 ("str or int", ExprBinOp(ExprName("str"), "|", ExprName("int"))), 

378 ("str or int or float", ExprBinOp(ExprBinOp(ExprName("str"), "|", ExprName("int")), "|", ExprName("float"))), 

379 ], 

380) 

381def test_parse__param_field_type_field_or_none__param_section_with_optional_with_parent( 

382 parse_sphinx: ParserType, 

383 union: str, 

384 expected_annotation: Expr, 

385) -> None: 

386 """Parse parameters with separated union types. 

387 

388 Parameters: 

389 parse_sphinx: Fixture parser. 

390 union: A parametrized union type. 

391 expected_annotation: The expected annotation as an expression 

392 """ 

393 docstring = f""" 

394 Docstring with line continuation. 

395 

396 :param {SOME_NAME}: {SOME_TEXT} 

397 :type {SOME_NAME}: {union} 

398 """ 

399 

400 parent_fn = Function("func", parameters=Parameters(Parameter(SOME_NAME))) 

401 sections, _ = parse_sphinx(docstring, parent=parent_fn) 

402 assert len(sections) == 2 

403 assert sections[1].kind is DocstringSectionKind.parameters 

404 actual = sections[1].value[0] 

405 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT) 

406 assert isinstance(actual, type(expected)) 

407 assert actual.as_dict() == expected.as_dict() 

408 assert isinstance(actual.annotation, type(expected.annotation)) 

409 assert isinstance(actual.annotation, Expr) 

410 assert actual.annotation.as_dict() == expected_annotation.as_dict() 

411 

412 

413def test_parse__param_field_annotate_type__param_section_with_type(parse_sphinx: ParserType) -> None: 

414 """Parse a simple docstring. 

415 

416 Parameters: 

417 parse_sphinx: Fixture parser. 

418 """ 

419 docstring = f""" 

420 Docstring with line continuation. 

421 

422 :param foo: {SOME_TEXT} 

423 """ 

424 

425 sections, warnings = parse_sphinx( 

426 docstring, 

427 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))), 

428 ) 

429 assert len(sections) == 2 

430 assert sections[1].kind is DocstringSectionKind.parameters 

431 actual = sections[1].value[0] 

432 expected = DocstringParameter(SOME_NAME, annotation="str", description=SOME_TEXT) 

433 assert isinstance(actual, type(expected)) 

434 assert actual.as_dict() == expected.as_dict() 

435 assert not warnings 

436 

437 

438def test_parse__param_field_no_matching_param__result_from_docstring(parse_sphinx: ParserType) -> None: 

439 """Parse a simple docstring. 

440 

441 Parameters: 

442 parse_sphinx: Fixture parser. 

443 """ 

444 docstring = f""" 

445 Docstring with line continuation. 

446 

447 :param other: {SOME_TEXT} 

448 """ 

449 

450 sections, _ = parse_sphinx(docstring) 

451 assert len(sections) == 2 

452 assert sections[1].kind is DocstringSectionKind.parameters 

453 actual = sections[1].value[0] 

454 expected = DocstringParameter("other", description=SOME_TEXT) 

455 assert isinstance(actual, type(expected)) 

456 assert actual.as_dict() == expected.as_dict() 

457 

458 

459def test_parse__param_field_with_default__result_from_docstring(parse_sphinx: ParserType) -> None: 

460 """Parse a simple docstring. 

461 

462 Parameters: 

463 parse_sphinx: Fixture parser. 

464 """ 

465 docstring = f""" 

466 Docstring with line continuation. 

467 

468 :param foo: {SOME_TEXT} 

469 """ 

470 

471 sections, warnings = parse_sphinx( 

472 docstring, 

473 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None, default=repr("")))), 

474 ) 

475 assert len(sections) == 2 

476 assert sections[1].kind is DocstringSectionKind.parameters 

477 actual = sections[1].value[0] 

478 expected = DocstringParameter("foo", description=SOME_TEXT, value=repr("")) 

479 assert isinstance(actual, type(expected)) 

480 assert actual.as_dict() == expected.as_dict() 

481 assert not warnings 

482 

483 

484def test_parse__param_field_no_matching_param__error_message(parse_sphinx: ParserType) -> None: 

485 """Parse a simple docstring. 

486 

487 Parameters: 

488 parse_sphinx: Fixture parser. 

489 """ 

490 docstring = f""" 

491 Docstring with line continuation. 

492 

493 :param other: {SOME_TEXT} 

494 """ 

495 

496 _, warnings = parse_sphinx(docstring) 

497 assert "No matching parameter for 'other'" in warnings[0] 

498 

499 

500def test_parse__invalid_param_field_only_initial_marker__error_message(parse_sphinx: ParserType) -> None: 

501 """Parse a simple docstring. 

502 

503 Parameters: 

504 parse_sphinx: Fixture parser. 

505 """ 

506 docstring = f""" 

507 Docstring with line continuation. 

508 

509 :param foo {SOME_TEXT} 

510 """ 

511 

512 _, warnings = parse_sphinx(docstring) 

513 assert "Failed to get ':directive: value' pair" in warnings[0] 

514 

515 

516def test_parse__invalid_param_field_wrong_part_count__error_message(parse_sphinx: ParserType) -> None: 

517 """Parse a simple docstring. 

518 

519 Parameters: 

520 parse_sphinx: Fixture parser. 

521 """ 

522 docstring = f""" 

523 Docstring with line continuation. 

524 

525 :param: {SOME_TEXT} 

526 """ 

527 

528 _, warnings = parse_sphinx(docstring) 

529 assert "Failed to parse field directive" in warnings[0] 

530 

531 

532def test_parse__invalid_param_field_wrong_part_count_spaces_4__error_message(parse_sphinx: ParserType) -> None: 

533 """Parse a simple docstring. 

534 

535 Parameters: 

536 parse_sphinx: Fixture parser. 

537 """ 

538 docstring = f""" 

539 Docstring with line continuation. 

540 

541 :param typing.Union[str, int] {SOME_NAME}: {SOME_TEXT} 

542 """ 

543 

544 sections, warnings = parse_sphinx(docstring) 

545 

546 # Assert that the warning is shown 

547 assert "Failed to parse field directive" in warnings[0] 

548 

549 # Assert that the parameter is still collected, but ignores the invalid type 

550 assert len(sections) == 2 

551 assert sections[1].kind is DocstringSectionKind.parameters 

552 actual = sections[1].value[0] 

553 expected = DocstringParameter(SOME_NAME, annotation=None, description=SOME_TEXT) 

554 assert isinstance(actual, type(expected)) 

555 assert actual.as_dict() == expected.as_dict() 

556 

557 

558def test_parse__valid_param_field_part_count_3(parse_sphinx: ParserType) -> None: 

559 """Parse a simple docstring. 

560 

561 Parameters: 

562 parse_sphinx: Fixture parser. 

563 """ 

564 docstring = f""" 

565 Docstring with line continuation. 

566 

567 :param typing.Union[str,int] {SOME_NAME}: {SOME_TEXT} 

568 """ 

569 

570 sections, _ = parse_sphinx(docstring) 

571 assert len(sections) == 2 

572 assert sections[1].kind is DocstringSectionKind.parameters 

573 actual = sections[1].value[0] 

574 expected = DocstringParameter(SOME_NAME, annotation="typing.Union[str,int]", description=SOME_TEXT) 

575 assert isinstance(actual, type(expected)) 

576 assert actual.as_dict() == expected.as_dict() 

577 

578 

579def test_parse__valid_param_field_part_count_3_with_parent(parse_sphinx: ParserType) -> None: 

580 """Parse a simple docstring. 

581 

582 Parameters: 

583 parse_sphinx: Fixture parser. 

584 """ 

585 docstring = f""" 

586 Docstring with line continuation. 

587 

588 :param typing.Union[str,int] {SOME_NAME}: {SOME_TEXT} 

589 """ 

590 

591 parent_fn = Function("func3", parameters=Parameters(Parameter(name=SOME_NAME))) 

592 sections, _ = parse_sphinx(docstring, parent=parent_fn) 

593 assert len(sections) == 2 

594 assert sections[1].kind is DocstringSectionKind.parameters 

595 actual = sections[1].value[0] 

596 typing_expr = ExprName("typing", parent=parent_fn) 

597 expected_annotation = ExprSubscript( 

598 left=ExprAttribute(values=[typing_expr, ExprName("Union", parent=typing_expr)]), 

599 slice=ExprTuple([ExprName("str", parent=parent_fn), ExprName("int", parent=parent_fn)], implicit=True), 

600 ) 

601 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT) 

602 assert isinstance(actual, type(expected)) 

603 assert actual.as_dict() == expected.as_dict() 

604 

605 

606def test_parse__param_twice__error_message(parse_sphinx: ParserType) -> None: 

607 """Parse a simple docstring. 

608 

609 Parameters: 

610 parse_sphinx: Fixture parser. 

611 """ 

612 docstring = f""" 

613 Docstring with line continuation. 

614 

615 :param foo: {SOME_TEXT} 

616 :param foo: {SOME_TEXT} again 

617 """ 

618 

619 _, warnings = parse_sphinx( 

620 docstring, 

621 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None))), 

622 ) 

623 assert "Duplicate parameter entry for 'foo'" in warnings[0] 

624 

625 

626def test_parse__param_type_twice_doc__error_message(parse_sphinx: ParserType) -> None: 

627 """Parse a simple docstring. 

628 

629 Parameters: 

630 parse_sphinx: Fixture parser. 

631 """ 

632 docstring = f""" 

633 Docstring with line continuation. 

634 

635 :param str foo: {SOME_TEXT} 

636 :type foo: str 

637 """ 

638 

639 _, warnings = parse_sphinx( 

640 docstring, 

641 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None))), 

642 ) 

643 assert "Duplicate parameter information for 'foo'" in warnings[0] 

644 

645 

646def test_parse__param_type_twice_type_directive_first__error_message(parse_sphinx: ParserType) -> None: 

647 """Parse a simple docstring. 

648 

649 Parameters: 

650 parse_sphinx: Fixture parser. 

651 """ 

652 docstring = f""" 

653 Docstring with line continuation. 

654 

655 :type foo: str 

656 :param str foo: {SOME_TEXT} 

657 """ 

658 

659 _, warnings = parse_sphinx( 

660 docstring, 

661 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None))), 

662 ) 

663 assert "Duplicate parameter information for 'foo'" in warnings[0] 

664 

665 

666def test_parse__param_type_twice_annotated__error_message(parse_sphinx: ParserType) -> None: 

667 """Parse a simple docstring. 

668 

669 Parameters: 

670 parse_sphinx: Fixture parser. 

671 """ 

672 docstring = f""" 

673 Docstring with line continuation. 

674 

675 :param str foo: {SOME_TEXT} 

676 :type foo: str 

677 """ 

678 

679 _, warnings = parse_sphinx( 

680 docstring, 

681 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))), 

682 ) 

683 assert "Duplicate parameter information for 'foo'" in warnings[0] 

684 

685 

686def test_warn_about_unknown_parameters(parse_sphinx: ParserType) -> None: 

687 """Warn about unknown parameters in "Parameters" sections. 

688 

689 Parameters: 

690 parse_sphinx: Fixture parser. 

691 """ 

692 docstring = """ 

693 

694 :param str a: {SOME_TEXT} 

695 """ 

696 

697 _, warnings = parse_sphinx( 

698 docstring, 

699 parent=Function( 

700 "func", 

701 parameters=Parameters( 

702 Parameter("b"), 

703 ), 

704 ), 

705 ) 

706 assert len(warnings) == 1 

707 assert "Parameter 'a' does not appear in the function signature" in warnings[0] 

708 

709 

710def test_parse__param_type_no_type__error_message(parse_sphinx: ParserType) -> None: 

711 """Parse a simple docstring. 

712 

713 Parameters: 

714 parse_sphinx: Fixture parser. 

715 """ 

716 docstring = f""" 

717 Docstring with line continuation. 

718 

719 :param str foo: {SOME_TEXT} 

720 :type str 

721 """ 

722 

723 _, warnings = parse_sphinx( 

724 docstring, 

725 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))), 

726 ) 

727 assert "Failed to get ':directive: value' pair from" in warnings[0] 

728 

729 

730def test_parse__param_type_no_name__error_message(parse_sphinx: ParserType) -> None: 

731 """Parse a simple docstring. 

732 

733 Parameters: 

734 parse_sphinx: Fixture parser. 

735 """ 

736 docstring = f""" 

737 Docstring with line continuation. 

738 

739 :param str foo: {SOME_TEXT} 

740 :type: str 

741 """ 

742 

743 _, warnings = parse_sphinx( 

744 docstring, 

745 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))), 

746 ) 

747 assert "Failed to get parameter name from" in warnings[0] 

748 

749 

750@pytest.mark.parametrize( 

751 "docstring", 

752 [ 

753 f""" 

754 Docstring with param with continuation, no indent. 

755 

756 :var {SOME_NAME}: {SOME_TEXT} 

757 {SOME_EXTRA_TEXT} 

758 """, 

759 f""" 

760 Docstring with param with continuation, with indent. 

761 

762 :var {SOME_NAME}: {SOME_TEXT} 

763 {SOME_EXTRA_TEXT} 

764 """, 

765 ], 

766) 

767def test_parse__attribute_field_multi_line__param_section(parse_sphinx: ParserType, docstring: str) -> None: 

768 """Parse multiline attributes. 

769 

770 Parameters: 

771 parse_sphinx: Fixture parser. 

772 docstring: A parametrized docstring. 

773 """ 

774 sections, warnings = parse_sphinx(docstring) 

775 assert len(sections) == 2 

776 assert sections[1].kind is DocstringSectionKind.attributes 

777 actual = sections[1].value[0] 

778 expected = DocstringAttribute(SOME_NAME, description=f"{SOME_TEXT} {SOME_EXTRA_TEXT}") 

779 assert isinstance(actual, type(expected)) 

780 assert actual.as_dict() == expected.as_dict() 

781 assert not warnings 

782 

783 

784@pytest.mark.parametrize( 

785 "attribute_directive_name", 

786 [ 

787 "var", 

788 "ivar", 

789 "cvar", 

790 ], 

791) 

792def test_parse__all_attribute_names__param_section(parse_sphinx: ParserType, attribute_directive_name: str) -> None: 

793 """Parse all attributes directives. 

794 

795 Parameters: 

796 parse_sphinx: Fixture parser. 

797 attribute_directive_name: A parametrized directive name. 

798 """ 

799 sections, warnings = parse_sphinx( 

800 f""" 

801 Docstring with one line attribute. 

802 

803 :{attribute_directive_name} {SOME_NAME}: {SOME_TEXT} 

804 """, 

805 ) 

806 assert len(sections) == 2 

807 assert sections[1].kind is DocstringSectionKind.attributes 

808 actual = sections[1].value[0] 

809 expected = DocstringAttribute(SOME_NAME, description=SOME_TEXT) 

810 assert isinstance(actual, type(expected)) 

811 assert actual.as_dict() == expected.as_dict() 

812 assert not warnings 

813 

814 

815def test_parse__class_attributes__attributes_section(parse_sphinx: ParserType) -> None: 

816 """Parse class attributes. 

817 

818 Parameters: 

819 parse_sphinx: Fixture parser. 

820 """ 

821 docstring = f""" 

822 Class docstring with attributes 

823 

824 :var foo: {SOME_TEXT} 

825 """ 

826 

827 sections, _ = parse_sphinx(docstring, parent=Class("klass")) 

828 assert len(sections) == 2 

829 assert sections[1].kind is DocstringSectionKind.attributes 

830 actual = sections[1].value[0] 

831 expected = DocstringAttribute(SOME_NAME, description=SOME_TEXT) 

832 assert isinstance(actual, type(expected)) 

833 assert actual.as_dict() == expected.as_dict() 

834 

835 

836def test_parse__class_attributes_with_type__annotation_in_attributes_section(parse_sphinx: ParserType) -> None: 

837 """Parse typed class attributes. 

838 

839 Parameters: 

840 parse_sphinx: Fixture parser. 

841 """ 

842 docstring = f""" 

843 Class docstring with attributes 

844 

845 :vartype foo: str 

846 :var foo: {SOME_TEXT} 

847 """ 

848 

849 sections, _ = parse_sphinx(docstring, parent=Class("klass")) 

850 assert len(sections) == 2 

851 assert sections[1].kind is DocstringSectionKind.attributes 

852 actual = sections[1].value[0] 

853 expected = DocstringAttribute(SOME_NAME, annotation="str", description=SOME_TEXT) 

854 assert isinstance(actual, type(expected)) 

855 assert actual.as_dict() == expected.as_dict() 

856 

857 

858def test_parse__attribute_invalid_directive___error(parse_sphinx: ParserType) -> None: 

859 """Warn on invalid attribute directive. 

860 

861 Parameters: 

862 parse_sphinx: Fixture parser. 

863 """ 

864 docstring = f""" 

865 Class docstring with attributes 

866 

867 :var {SOME_TEXT} 

868 """ 

869 

870 _, warnings = parse_sphinx(docstring) 

871 assert "Failed to get ':directive: value' pair from" in warnings[0] 

872 

873 

874def test_parse__attribute_no_name__error(parse_sphinx: ParserType) -> None: 

875 """Warn on invalid attribute directive. 

876 

877 Parameters: 

878 parse_sphinx: Fixture parser. 

879 """ 

880 docstring = f""" 

881 Class docstring with attributes 

882 

883 :var: {SOME_TEXT} 

884 """ 

885 

886 _, warnings = parse_sphinx(docstring) 

887 assert "Failed to parse field directive from" in warnings[0] 

888 

889 

890def test_parse__attribute_duplicate__error(parse_sphinx: ParserType) -> None: 

891 """Warn on duplicate attribute directive. 

892 

893 Parameters: 

894 parse_sphinx: Fixture parser. 

895 """ 

896 docstring = f""" 

897 Class docstring with attributes 

898 

899 :var foo: {SOME_TEXT} 

900 :var foo: {SOME_TEXT} 

901 """ 

902 

903 _, warnings = parse_sphinx(docstring) 

904 assert "Duplicate attribute entry for 'foo'" in warnings[0] 

905 

906 

907def test_parse__class_attributes_type_invalid__error(parse_sphinx: ParserType) -> None: 

908 """Warn on invalid attribute type directive. 

909 

910 Parameters: 

911 parse_sphinx: Fixture parser. 

912 """ 

913 docstring = f""" 

914 Class docstring with attributes 

915 

916 :vartype str 

917 :var foo: {SOME_TEXT} 

918 """ 

919 

920 _, warnings = parse_sphinx(docstring) 

921 assert "Failed to get ':directive: value' pair from " in warnings[0] 

922 

923 

924def test_parse__class_attributes_type_no_name__error(parse_sphinx: ParserType) -> None: 

925 """Warn on invalid attribute directive. 

926 

927 Parameters: 

928 parse_sphinx: Fixture parser. 

929 """ 

930 docstring = f""" 

931 Class docstring with attributes 

932 

933 :vartype: str 

934 :var foo: {SOME_TEXT} 

935 """ 

936 

937 _, warnings = parse_sphinx(docstring) 

938 assert "Failed to get attribute name from" in warnings[0] 

939 

940 

941def test_parse__return_directive__return_section_no_type(parse_sphinx: ParserType) -> None: 

942 """Parse return directives. 

943 

944 Parameters: 

945 parse_sphinx: Fixture parser. 

946 """ 

947 docstring = f""" 

948 Function with only return directive 

949 

950 :return: {SOME_TEXT} 

951 """ 

952 

953 sections, _ = parse_sphinx(docstring) 

954 assert len(sections) == 2 

955 assert sections[1].kind is DocstringSectionKind.returns 

956 actual = sections[1].value[0] 

957 expected = DocstringReturn(name="", annotation=None, description=SOME_TEXT) 

958 assert isinstance(actual, type(expected)) 

959 assert actual.as_dict() == expected.as_dict() 

960 

961 

962def test_parse__return_directive_rtype__return_section_with_type(parse_sphinx: ParserType) -> None: 

963 """Parse typed return directives. 

964 

965 Parameters: 

966 parse_sphinx: Fixture parser. 

967 """ 

968 docstring = f""" 

969 Function with only return & rtype directive 

970 

971 :return: {SOME_TEXT} 

972 :rtype: str 

973 """ 

974 

975 sections, _ = parse_sphinx(docstring) 

976 assert len(sections) == 2 

977 assert sections[1].kind is DocstringSectionKind.returns 

978 actual = sections[1].value[0] 

979 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT) 

980 assert isinstance(actual, type(expected)) 

981 assert actual.as_dict() == expected.as_dict() 

982 

983 

984def test_parse__return_directive_rtype_first__return_section_with_type(parse_sphinx: ParserType) -> None: 

985 """Parse typed-first return directives. 

986 

987 Parameters: 

988 parse_sphinx: Fixture parser. 

989 """ 

990 docstring = f""" 

991 Function with only return & rtype directive 

992 

993 :rtype: str 

994 :return: {SOME_TEXT} 

995 """ 

996 

997 sections, _ = parse_sphinx(docstring) 

998 assert len(sections) == 2 

999 assert sections[1].kind is DocstringSectionKind.returns 

1000 actual = sections[1].value[0] 

1001 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT) 

1002 assert isinstance(actual, type(expected)) 

1003 assert actual.as_dict() == expected.as_dict() 

1004 

1005 

1006def test_parse__return_directive_annotation__return_section_with_type(parse_sphinx: ParserType) -> None: 

1007 """Parse return directives with return annotation. 

1008 

1009 Parameters: 

1010 parse_sphinx: Fixture parser. 

1011 """ 

1012 docstring = f""" 

1013 Function with return directive, rtype directive, & annotation 

1014 

1015 :return: {SOME_TEXT} 

1016 """ 

1017 

1018 sections, _ = parse_sphinx(docstring, parent=Function("func", returns="str")) 

1019 assert len(sections) == 2 

1020 assert sections[1].kind is DocstringSectionKind.returns 

1021 actual = sections[1].value[0] 

1022 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT) 

1023 assert isinstance(actual, type(expected)) 

1024 assert actual.as_dict() == expected.as_dict() 

1025 

1026 

1027def test_parse__return_directive_annotation__prefer_return_directive(parse_sphinx: ParserType) -> None: 

1028 """Prefer docstring type over return annotation. 

1029 

1030 Parameters: 

1031 parse_sphinx: Fixture parser. 

1032 """ 

1033 docstring = f""" 

1034 Function with return directive, rtype directive, & annotation 

1035 

1036 :return: {SOME_TEXT} 

1037 :rtype: str 

1038 """ 

1039 

1040 sections, _ = parse_sphinx(docstring, parent=Function("func", returns="int")) 

1041 assert len(sections) == 2 

1042 assert sections[1].kind is DocstringSectionKind.returns 

1043 actual = sections[1].value[0] 

1044 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT) 

1045 assert isinstance(actual, type(expected)) 

1046 assert actual.as_dict() == expected.as_dict() 

1047 

1048 

1049def test_parse__return_invalid__error(parse_sphinx: ParserType) -> None: 

1050 """Warn on invalid return directive. 

1051 

1052 Parameters: 

1053 parse_sphinx: Fixture parser. 

1054 """ 

1055 docstring = f""" 

1056 Function with only return directive 

1057 

1058 :return {SOME_TEXT} 

1059 """ 

1060 

1061 _, warnings = parse_sphinx(docstring) 

1062 assert "Failed to get ':directive: value' pair from " in warnings[0] 

1063 

1064 

1065def test_parse__rtype_invalid__error(parse_sphinx: ParserType) -> None: 

1066 """Warn on invalid typed return directive. 

1067 

1068 Parameters: 

1069 parse_sphinx: Fixture parser. 

1070 """ 

1071 docstring = """ 

1072 Function with only return directive 

1073 

1074 :rtype str 

1075 """ 

1076 

1077 _, warnings = parse_sphinx(docstring) 

1078 assert "Failed to get ':directive: value' pair from " in warnings[0] 

1079 

1080 

1081def test_parse__raises_directive__exception_section(parse_sphinx: ParserType) -> None: 

1082 """Parse raise directives. 

1083 

1084 Parameters: 

1085 parse_sphinx: Fixture parser. 

1086 """ 

1087 docstring = f""" 

1088 Function with only return directive 

1089 

1090 :raise SomeException: {SOME_TEXT} 

1091 """ 

1092 

1093 sections, _ = parse_sphinx(docstring) 

1094 assert len(sections) == 2 

1095 assert sections[1].kind is DocstringSectionKind.raises 

1096 actual = sections[1].value[0] 

1097 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT) 

1098 assert isinstance(actual, type(expected)) 

1099 assert actual.as_dict() == expected.as_dict() 

1100 

1101 

1102def test_parse__multiple_raises_directive__exception_section_with_two(parse_sphinx: ParserType) -> None: 

1103 """Parse multiple raise directives. 

1104 

1105 Parameters: 

1106 parse_sphinx: Fixture parser. 

1107 """ 

1108 docstring = f""" 

1109 Function with only return directive 

1110 

1111 :raise SomeException: {SOME_TEXT} 

1112 :raise SomeOtherException: {SOME_TEXT} 

1113 """ 

1114 

1115 sections, _ = parse_sphinx(docstring) 

1116 assert len(sections) == 2 

1117 assert sections[1].kind is DocstringSectionKind.raises 

1118 actual = sections[1].value[0] 

1119 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT) 

1120 assert isinstance(actual, type(expected)) 

1121 assert actual.as_dict() == expected.as_dict() 

1122 actual = sections[1].value[1] 

1123 expected = DocstringRaise(annotation=SOME_OTHER_EXCEPTION_NAME, description=SOME_TEXT) 

1124 assert isinstance(actual, type(expected)) 

1125 assert actual.as_dict() == expected.as_dict() 

1126 

1127 

1128@pytest.mark.parametrize( 

1129 "raise_directive_name", 

1130 [ 

1131 "raises", 

1132 "raise", 

1133 "except", 

1134 "exception", 

1135 ], 

1136) 

1137def test_parse__all_exception_names__param_section(parse_sphinx: ParserType, raise_directive_name: str) -> None: 

1138 """Parse all raise directives. 

1139 

1140 Parameters: 

1141 parse_sphinx: Fixture parser. 

1142 raise_directive_name: A parametrized directive name. 

1143 """ 

1144 sections, _ = parse_sphinx( 

1145 f""" 

1146 Docstring with one line attribute. 

1147 

1148 :{raise_directive_name} {SOME_EXCEPTION_NAME}: {SOME_TEXT} 

1149 """, 

1150 ) 

1151 assert len(sections) == 2 

1152 assert sections[1].kind is DocstringSectionKind.raises 

1153 actual = sections[1].value[0] 

1154 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT) 

1155 assert isinstance(actual, type(expected)) 

1156 assert actual.as_dict() == expected.as_dict() 

1157 

1158 

1159def test_parse__raise_invalid__error(parse_sphinx: ParserType) -> None: 

1160 """Warn on invalid raise directives. 

1161 

1162 Parameters: 

1163 parse_sphinx: Fixture parser. 

1164 """ 

1165 docstring = f""" 

1166 Function with only return directive 

1167 

1168 :raise {SOME_TEXT} 

1169 """ 

1170 

1171 _, warnings = parse_sphinx(docstring) 

1172 assert "Failed to get ':directive: value' pair from " in warnings[0] 

1173 

1174 

1175def test_parse__raise_no_name__error(parse_sphinx: ParserType) -> None: 

1176 """Warn on invalid raise directives. 

1177 

1178 Parameters: 

1179 parse_sphinx: Fixture parser. 

1180 """ 

1181 docstring = f""" 

1182 Function with only return directive 

1183 

1184 :raise: {SOME_TEXT} 

1185 """ 

1186 

1187 _, warnings = parse_sphinx(docstring) 

1188 assert "Failed to parse exception directive from" in warnings[0] 

1189 

1190 

1191def test_parse__module_attributes_section__expected_attributes_section(parse_sphinx: ParserType) -> None: 

1192 """Parse attributes section in modules. 

1193 

1194 Parameters: 

1195 parse_sphinx: Fixture parser. 

1196 """ 

1197 docstring = """ 

1198 Let's describe some attributes. 

1199 

1200 :var A: Alpha. 

1201 :vartype B: bytes 

1202 :var B: Beta. 

1203 :var C: Gamma. 

1204 :var D: Delta. 

1205 :var E: Epsilon. 

1206 :vartype E: float 

1207 """ 

1208 

1209 module = Module("mod", filepath=None) 

1210 module["A"] = Attribute("A", annotation="int", value="0") 

1211 module["B"] = Attribute("B", annotation="str", value=repr("ŧ")) 

1212 module["C"] = Attribute("C", annotation="bool", value="True") 

1213 module["D"] = Attribute("D", annotation=None, value="3.0") 

1214 module["E"] = Attribute("E", annotation=None, value="None") 

1215 sections, warnings = parse_sphinx(docstring, parent=module) 

1216 

1217 attr_section = sections[1] 

1218 assert attr_section.kind is DocstringSectionKind.attributes 

1219 assert len(attr_section.value) == 5 

1220 expected_data: list[dict[str, Any]] = [ 

1221 {"name": "A", "annotation": "int", "description": "Alpha."}, 

1222 {"name": "B", "annotation": "bytes", "description": "Beta."}, 

1223 {"name": "C", "annotation": "bool", "description": "Gamma."}, 

1224 {"name": "D", "annotation": None, "description": "Delta."}, 

1225 {"name": "E", "annotation": "float", "description": "Epsilon."}, 

1226 ] 

1227 for index, expected_kwargs in enumerate(expected_data): 

1228 actual = attr_section.value[index] 

1229 expected = DocstringAttribute(**expected_kwargs) 

1230 assert isinstance(actual, type(expected)) 

1231 assert actual.name == expected.name 

1232 assert actual.as_dict() == expected.as_dict() 

1233 assert not warnings 

1234 

1235 

1236def test_parse__properties_return_type(parse_sphinx: ParserType) -> None: 

1237 """Parse attributes section in modules. 

1238 

1239 Parameters: 

1240 parse_sphinx: Fixture parser. 

1241 """ 

1242 docstring = """ 

1243 Property that returns True for explaining the issue. 

1244 

1245 :return: True 

1246 """ 

1247 prop = Attribute("example", annotation="bool") 

1248 sections, warnings = parse_sphinx(docstring, parent=prop) 

1249 assert not warnings 

1250 assert sections[1].value[0].annotation == "bool" 

1251 

1252 

1253# ============================================================================================= 

1254# Warnings 

1255def test_disabled_warnings(parse_sphinx: ParserType) -> None: 

1256 """Assert warnings are disabled. 

1257 

1258 Parameters: 

1259 parse_sphinx: Fixture parser. 

1260 """ 

1261 docstring = ":param x: X value." 

1262 _, warnings = parse_sphinx(docstring, warnings=True) 

1263 assert warnings 

1264 _, warnings = parse_sphinx(docstring, warnings=False) 

1265 assert not warnings