pydocstring 0.3.0

A zero-dependency Rust parser for Python docstrings (Google and NumPy styles) with a unified syntax tree and byte-precise source locations
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.0] - 2026-07-09

**The style-independent CST release** — phase 2 of the v2 roadmap
([#41](https://github.com/ryumasai/pydocstring/issues/41)). The syntax tree
no longer encodes the docstring style in its node kinds: 55 style-prefixed
kinds collapse into 31 reST-neutral ones, style differences are confined to
the `SECTION_HEADER` shape and the parsers, and a new unified typed layer
(`Document` → `Section` → `Entry`) lets one code path handle Google and
NumPy docstrings identically — spec-tested by cross-style parity laws.
Together with formalized missing placeholders (the future insertion anchors)
and now-private tree mutators, this fixes the tree shape and API surface the
upcoming edit API will build on. The release is validated against a new
real-world corpus: **every within-style law allowlist (byte coverage,
idempotence, model stability) is empty across all 247 corpus inputs,
including 85 docstrings taken verbatim from numpy, scipy, scanpy, anndata,
absl and fire**. **This release is deliberately breaking**; the migration
guides below cover every rename.

### Migration guide — Rust

#### Syntax kinds (`SyntaxKind`)

The style is no longer in the kind — recover it with the new
`Parsed::style()`. Old variants are **removed** (compile errors, not
deprecations):

| 0.2.0 kind(s) | 0.3.0 kind | Notes |
|---|---|---|
| `GOOGLE_DOCSTRING`, `NUMPY_DOCSTRING`, `PLAIN_DOCSTRING` | `DOCUMENT` | match on `parsed.style()` instead |
| `GOOGLE_SECTION`, `NUMPY_SECTION` | `SECTION` | |
| `GOOGLE_SECTION_HEADER`, `NUMPY_SECTION_HEADER` | `SECTION_HEADER` | style lives in its shape: `COLON` vs `UNDERLINE` |
| `GOOGLE_ARG`, `NUMPY_PARAMETER`, `*_RETURNS`, `*_YIELDS`, `*_EXCEPTION`, `*_WARNING`, `*_SEE_ALSO_ITEM`, `*_ATTRIBUTE`, `*_METHOD` (16 kinds) | `ENTRY` | the entry's role derives from the enclosing `SECTION`'s kind |
| `GOOGLE_DEPRECATION`, `NUMPY_DEPRECATION` | `DIRECTIVE` | generalized rST directive; `deprecated` is just the directive name |
| `GOOGLE_REFERENCE`, `NUMPY_REFERENCE` | `CITATION` | rST citation/footnote (`.. [label]`) |
| `BODY_TEXT`, `CONTENT` | `DESCRIPTION` | every construct's prose child is now `DESCRIPTION` |
| `STRAY_LINE` (token) | `PARAGRAPH` (node) | stray prose is now a text-block *node* wrapping `TEXT_LINE` tokens; blank lines split paragraphs |
| `WARNING_TYPE` | `TYPE` | was style-divergent (see Fixed) |
| `RETURN_TYPE` | `TYPE` | role comes from the section, as everywhere else |
| `VERSION` | `ARGUMENT` | the directive argument token |
| `NUMBER` | `LABEL` | citation labels aren't always numbers (`CIT2002`, `#f1`) |
| `KEYWORD` | `DIRECTIVE_NAME` | disambiguates from `DEFAULT_KEYWORD` |
|| `DEFAULT` (new node) | wraps one `default …` marker occurrence (see Added) |

#### Typed-wrapper accessors

Renames with a `#[deprecated(since = "0.3.0")]` alias still compile with a
warning; **hard** changes do not.

| 0.2.0 | 0.3.0 | Migration |
|---|---|---|
| `r#type()` (`GoogleArg`, `GoogleException`, `GoogleAttribute`, NumPy counterparts) | `type_annotation()` | deprecated alias kept |
| `return_type()` (`GoogleReturn`/`Yield`, `NumPyReturn`/`Yield`) | `type_annotation()` | deprecated alias kept |
| `warning_type()` (`GoogleWarning`, `NumPyWarning`) | `type_annotation()` | deprecated alias kept |
| `optional()` | `optional_marker()`, plus `is_optional() -> bool` | deprecated alias kept; new `optionals()` iterates every occurrence |
| `number()` (`GoogleReference`, `NumPyReference`) | `label()` | deprecated alias kept |
| `stray_lines()` (yielded `&SyntaxToken`) | `paragraphs()` (yields `TextBlock`) | deprecated alias kept, but it now also yields `TextBlock` — a behavioral change even through the alias |
| `syntax::walk` (untyped tree walk) | `syntax::walk_tree` | deprecated alias kept; resolves the collision with the typed `parse::visitor::walk` |

#### Signatures (hard changes)

The typed views now hold `&Parsed`, so `source: &str` disappears from the
entire typed API — `arg.name()` returns a `TokenRef` and
`arg.name().text()` replaces `arg.name().text(source)`. Raw
`SyntaxToken::text(source)` is unchanged (per the
[#42](https://github.com/ryumasai/pydocstring/issues/42) convention:
synthesized trees never enter `Parsed`, so token text stays source-sliced).

| 0.2.0 | 0.3.0 |
|---|---|
| `GoogleDocstring::cast(node)` (and all typed wrappers) | `cast(&parsed, node)` — uniform two-argument cast |
| `section.section_kind(source)` | `section.section_kind()` |
| accessors returning `&SyntaxToken` | return `TokenRef<'a>` (has `.text()`, `.kind()`, `.range()`, `.is_missing()`) |
| `visitor::walk(source, node, visitor)` | `walk(parsed, node, visitor)` |
| `DocstringVisitor::visit_*(&mut self, source: &str, …)` | `visit_*(&mut self, parsed: &Parsed, …)` |
| `emit_google(doc, base_indent)` / `emit_numpy` / `emit_sphinx` | `emit_google(doc, &EmitOptions)`; use `EmitOptions::default().with_base_indent(n)` |
| `Parsed::new(source, root)` | `Parsed::new(source, root, style)` |

#### Model (`model::`)

| 0.2.0 | 0.3.0 | Migration |
|---|---|---|
| `Docstring.deprecation: Option<Deprecation>` | `Docstring.directives: Vec<Directive>` | `deprecation()` convenience method finds the first directive named `deprecated` |
| `Deprecation { version, description }` (struct removed) | `Directive { name, argument, description }` | `version``argument` of a `Directive` with `name == "deprecated"` |
| `Reference.number` | `Reference.label` | hard field rename |
| `Attribute.name: String` | `Attribute.names: Vec<String>` | hard field rename; NumPy allows multi-name attribute entries (`jac, hess`), and keeping only the first dropped the rest ([#89]https://github.com/ryumasai/pydocstring/issues/89) |

#### Removed / newly non-exhaustive

- `SyntaxToken::extend_range` removed (was dead code).
- Tree mutators `SyntaxNode::children_mut` / `push_child` /
  `extend_range_to` and `TextRange::extend` are now crate-private — user
  code can no longer violate the CI-enforced coverage/ordering invariants
  (`TextRange::extend` could also silently *shrink* a range; fixed on the
  way in).
- `Style` and `model::Section` are now `#[non_exhaustive]`: downstream
  exhaustive `match`es need a wildcard arm. A future `Style::Sphinx` or new
  section variant will no longer be a breaking change.

### Migration guide — Python (`pydocstring-rs`)

| 0.2.0 | 0.3.0 | Migration |
|---|---|---|
| `GoogleWarning.warning_type` | `.type` | hard rename (Rust's three-way split unified; Python keeps `.type` per its own conventions) |
| `GoogleDocstring.stray_lines` (`list[Token]`) | `.paragraphs` (`list[TextBlock]`) | removed, no alias; `NumPyDocstring.paragraphs` added for parity |
| `GoogleReference.number` / `NumPyReference.number` | `.label` | hard rename, matching the Rust side — citation labels aren't always numbers |
| `Reference(number=…)` / `.number` (model) | `Reference(label=…)` / `.label` | hard rename |
| `Attribute(name=…)` / `.name` (model) | `Attribute(names=[…])` / `.names` | hard rename, matching `Parameter.names` ([#89]https://github.com/ryumasai/pydocstring/issues/89) |
| `Deprecation` class | removed | build `Directive("deprecated", argument=version, description=…)`; `Docstring(deprecation=…)``Docstring(directives=[…])` |
| `Docstring.deprecation` (read/write field) | read-only computed property (`Directive \| None`) | edit `Docstring.directives` instead |
| `Style == 1` (int equality) | compares only to `Style` members | `Style` is now hashable (usable in sets / as dict keys) |
| `Section.Parameters(…)` etc. (pyo3 variant class-attrs) | removed at module init | these constructors bypassed `Section.__init__` validation; use `Section(kind=…, parameters=…)` |
| `Section.parameters` etc. typed `list[…]` in the stub | honestly typed `list[…] \| None` | runtime already returned `None` for other-kind sections; the stub now says so |

Other Python surface changes:

- **Missing-value conventions unified**: NumPy parameter/attribute/method
  `type`/`description` fields now return `is_missing()` placeholder objects
  exactly like their Google counterparts; the full per-class
  required / optional / or-missing-placeholder table is documented at the
  top of the type stub (`_pydocstring.pyi`), and Google↔NumPy parity is
  spec-tested.
- New: `NumPyParameter.name` (first of `names`, `None` if empty), matching
  `GoogleArg.name`; `GoogleAttribute.names` / `NumPyAttribute.names` (with
  `.name` kept as a `names[0]` convenience).
- Model property setters now validate like the constructors;
  `Section(kind=SectionKind.UNKNOWN)` without `unknown_name` is rejected at
  construction.
- `repr()` no longer leaks Rust pointer addresses, and multi-name entry
  reprs (`GoogleArg`, `NumPyParameter`, attributes, see-also items) now
  show every name, not just the first.

### Added

- **Unified typed layer** (`parse::unified`, re-exported from `parse`):
  `Document`, `Section`, `Entry`, `Directive`, `Citation`, `DefaultMarker`
  — zero-copy, style-independent views over the neutral kinds. One generic
  function can now extract from Google and NumPy docstrings identically;
  a table-driven cross-style parity law over every entry role
  (params/returns/yields/raises/warns/attributes) pins the guarantee
  (`tests/unified.rs`; mirrored in Python by the missingness parity suite).
- `Parsed::style()` — reports the detected/parsed style now that the root
  kind is always `DOCUMENT`.
- `TokenRef` — a `&Parsed`-holding token handle with source-free `text()`.
- `EmitOptions` (`Default` + `#[non_exhaustive]` + `with_base_indent`) —
  future emitter options become non-breaking field additions.
- **`DEFAULT` marker nodes with repeatable-marker semantics**: every
  `optional` / `default …` occurrence gets its own token/node in source
  order (`x : int, default 1, default 2` produces two `DEFAULT` nodes);
  which occurrence wins is a model-layer rule — **the first**, spec-pinned.
  New accessors `optionals()` / `defaults()` iterate all occurrences
  (fixes [#76]https://github.com/ryumasai/pydocstring/issues/76).
- **`PARAGRAPH` nodes** ([#78]https://github.com/ryumasai/pydocstring/issues/78):
  stray prose between sections becomes first-class `TextBlock` targets;
  newline-joined lines form one paragraph, blank lines split (reST
  semantics). Also on the unified `Document::paragraphs()`.
- **Missing-placeholder formalization**
  ([#78]https://github.com/ryumasai/pydocstring/issues/78): zero-length ⇔
  missing ⇔ edit-API insertion anchor, documented in `src/syntax.rs`,
  rendered as `<missing>` by `pretty_print`, and pinned by an invariant test
  over the whole corpus (placeholder set: `TYPE`, `CLOSE_BRACKET`, `COLON`,
  `DEFAULT_VALUE` tokens; zero-length `DESCRIPTION` nodes; the empty-input
  `DOCUMENT`). Placeholders are only ever *replaced*, never extended.

### Changed

- **Python bindings are now lazy views** over the shared parse result
  ([#43]https://github.com/ryumasai/pydocstring/issues/43): every CST
  class holds a path into the immutable tree and delegates each getter to
  the core Rust accessors on access, eliminating the eager-materialization
  bug class (the source of the released Yields/Warns mislabeling,
  [#50]https://github.com/ryumasai/pydocstring/pull/50). The visible
  surface is frozen — with one nuance: **getters return a fresh object per
  access** (`doc.summary is doc.summary` is `False`); rely on `==` and
  hashing, which are preserved, never on `is`.
- **See-also emit normal form**: both emitters now always write a see-also
  description on the following indented line (`name\n    desc`) instead of
  collapsing to a `name : desc` one-liner — the one-liner is unparseable
  when the name is an rST role (`` :func:`csd` ``), per the
  [#26]https://github.com/ryumasai/pydocstring/issues/26 colon rule
  ([#91]https://github.com/ryumasai/pydocstring/issues/91). Emitted
  output for see-also-bearing docstrings changes accordingly and
  round-trips.

### Fixed

- Repeated `optional` / `default` markers dropped bytes: the second
  `default` in `x : int, default 1, default 2` overwrote the first, whose
  bytes got no tokens — a live violation of the byte-coverage law
  ([#76]https://github.com/ryumasai/pydocstring/issues/76). Structurally
  fixed by the repeatable `DEFAULT` nodes; the byte-coverage law now passes
  the whole corpus with an empty allowlist, repeated-marker inputs included.
- `WARNING_TYPE` was style-divergent: Google warns entries emitted it while
  NumPy warns emitted `TYPE`, so the unified `Entry::type_annotation()`
  returned `Some` for NumPy warns and `None` for Google warns — silently
  breaking the single-code-path guarantee. Both `WARNING_TYPE` and
  `RETURN_TYPE` collapse into `TYPE`
  ([#81]https://github.com/ryumasai/pydocstring/pull/81).
- Marker-like segments in the middle of a type (`int, optional, str`)
  produced overlapping tokens (an `OPTIONAL` token inside the `TYPE`
  token's range). Markers now only count in the trailing suffix of the
  type, per numpydoc convention (markers are trailing annotations).
- NumPy multi-name Attributes entries (`jac, hess : callable`) dropped
  every name after the first — the extra names' bytes got no tokens and
  the names vanished from the model. NumPy attributes now share the
  parameter grammar (Google splits comma-separated attribute names too),
  the CST wrappers gain `names()`, and the model field became
  `Attribute.names` ([#89]https://github.com/ryumasai/pydocstring/issues/89).
- NumPy see-also multi-line descriptions were emitted with continuation
  lines at entry indentation, which re-parsed as fake name-only see-also
  entries ([#90]https://github.com/ryumasai/pydocstring/issues/90).
- The `.. deprecated::` directive body reached the model with its source
  continuation indent attached, so the indentation grew by four spaces on
  every emit/parse cycle; directive bodies are now dedented in the model
  and re-indented exactly once on emit, in both styles
  ([#92]https://github.com/ryumasai/pydocstring/issues/92).
- Google description-only Returns entries were emitted with continuation
  lines at column 0, dedenting them out of the section — the re-parse
  silently kept only the first line
  ([#93]https://github.com/ryumasai/pydocstring/issues/93).
- Typed section accessors are guarded by section role: with all entries
  unified to `ENTRY`, a mismatched accessor (`args()` on a `Raises:`
  section) would have wrapped foreign entries and panicked in
  `required_token`; accessors now return empty for sections outside their
  role, preserving the 0.2.0 kind-filtered behavior.
- `TextRange::extend` could silently shrink a range; fixed (and the method
  is no longer public).

### Deprecated

- The ~20 renamed accessors listed in the Rust migration tables
  (`r#type` / `return_type` / `warning_type``type_annotation`,
  `optional``optional_marker`, `number``label`,
  `stray_lines``paragraphs`, `syntax::walk``walk_tree`) remain as
  `#[deprecated(since = "0.3.0")]` aliases. **They are scheduled for
  removal in 0.4.0** — migrate now while the compiler still points at every
  call site.

### Internal

- Real-world corpus: 85 docstrings ingested verbatim from numpy, scipy,
  scanpy, anndata, absl and fire under `tests/corpus/third_party/` (with
  per-library license notices), bringing the corpus to 247 inputs. The
  five bug clusters it flushed out
  ([#89]https://github.com/ryumasai/pydocstring/issues/89[#93]https://github.com/ryumasai/pydocstring/issues/93)
  are fixed above; the within-style law allowlists (coverage, idempotence,
  model stability) are now empty, and every remaining cross-style
  conversion allowlist entry is re-verified and annotated against its
  documented mechanism.
- Coverage tooling for the test suite (profile-data reuse, lcov export).

## [0.2.0] - 2026-07-07

**The lossless-CST release** — phase 1 of the v2 roadmap
([#48](https://github.com/ryumasai/pydocstring/issues/48)). The syntax tree
now accounts for every byte of the input: three invariants hold for the whole
test corpus and are enforced in CI.

1. Concatenating all tokens in source order reproduces the input
   byte-for-byte — no gaps, no overlaps.
2. No token contains a newline, except the trivia kinds `NEWLINE` and
   `BLANK_LINE`.
3. Trivia never overlaps content and always sits inside its parent's range.

### Changed (breaking)

- The CST now contains **trivia tokens**: `WHITESPACE` (intra-line runs),
  `NEWLINE`, and `BLANK_LINE` (a whitespace-only line including its newline).
  `children()` and token iteration yield them; kind-filtered accessors are
  unaffected. Blank lines between sections live at docstring level, entry
  indentation inside its section (syntactic ownership).
- **Multi-line content is split per line**: `SUMMARY`, `EXTENDED_SUMMARY`,
  `DESCRIPTION`, `BODY_TEXT` and `CONTENT` are now *nodes* wrapping one
  `TEXT_LINE` token per content line (plus interior trivia). Typed accessors
  keep their names but return the new `TextBlock` wrapper: `text(source)`
  yields the same raw slice as before, `lines()` iterates per-line tokens,
  `logical_text(source)` returns the dedented join. Python bindings expose the
  same as a `TextBlock` class (`.text` unchanged in value, plus `.lines` /
  `.logical_text`).
- The root node's range now spans the entire input, including the trailing
  newline.
- NumPy google-style entries store children in source order (`COLON` no
  longer precedes `TYPE`), with missing-type placeholders anchored after the
  open bracket, matching the Google parser.
- `SyntaxKind`, `GoogleSectionKind`, `NumPySectionKind`, `SectionKind` and
  `FreeSectionKind` are now `#[non_exhaustive]`, as announced in 0.1.15 —
  future kind additions will no longer break exhaustive matches.
- New `SyntaxKind` variants: `WHITESPACE`, `NEWLINE`, `BLANK_LINE`,
  `TEXT_LINE`, `COMMA`.

### Fixed

- NumPy `Methods`: inline text after a colon (`reset() : Reset the state.`)
  was silently discarded; it is now the method's description
  ([#39]https://github.com/ryumasai/pydocstring/issues/39).
- NumPy entries with a colon but the description on the next line no longer
  leak a leading newline into the model.
- Separator commas (between names, before `optional` / `default` markers) and
  the brackets of google-style entries inside NumPy sections are now real
  tokens; previously those bytes were unaccounted for.

### Added

- `TextBlock` (Rust and Python): `lines()`, raw `text()`, dedented
  `logical_text()`, `is_missing()`.
- `SyntaxKind::is_trivia()`.
- Test infrastructure: byte-coverage law (`tests/coverage.rs`), trivia
  invariants and lexing spec tests (`tests/trivia.rs`).

## [0.1.15] - 2026-07-07

Bug-fix release: everything flushed out by the new corpus/round-trip test
infrastructure ([#59](https://github.com/ryumasai/pydocstring/issues/59)).

### Fixed

- Python bindings: `to_model()` mislabeled Yields sections as Returns and
  Warns as Raises ([#50]https://github.com/ryumasai/pydocstring/pull/50).
- NumPy parser: recognizes the `Keyword Parameters` / `Keyword Arguments`
  section headers (parsed as parameter entries) and the Google-style
  admonition headers (`Todo`, `Attention`, `Caution`, `Danger`, `Error`,
  `Hint`, `Important`, `Tip`) instead of degrading them to `Unknown`
  ([#52]https://github.com/ryumasai/pydocstring/issues/52,
  [#53]https://github.com/ryumasai/pydocstring/issues/53).
- Google parser/emitter: the `.. deprecated::` directive now round-trips
  (`model.deprecation` was silently dropped); both emitters also write the
  directive before the extended summary, matching the parsers and numpydoc
  convention ([#54]https://github.com/ryumasai/pydocstring/issues/54).
- Google parser: References sections parse into structured
  `Reference { number, content }` entries instead of free text
  ([#55]https://github.com/ryumasai/pydocstring/issues/55).
- Google parser: comma-separated parameter names split into individual
  names, and a `default X` / `default=X` / `default: X` segment in the type
  parentheses round-trips as `Parameter::default_value`
  ([#56]https://github.com/ryumasai/pydocstring/issues/56,
  [#57]https://github.com/ryumasai/pydocstring/issues/57).
- NumPy parser: a type whose name merely starts with `default`
  (e.g. `defaultdict`) was eaten as a default-value marker, leaving the type
  empty ([#64]https://github.com/ryumasai/pydocstring/issues/64).
- Parsers no longer panic on a malformed reStructuredText reference marker
  (`.. [1` without a closing bracket on the same line)
  ([#67]https://github.com/ryumasai/pydocstring/issues/67).

### Added

- Typed CST nodes and accessors: `GoogleDeprecation`, `GoogleReference` (with
  `GoogleSection::references()`), `GoogleArg::names()` /
  `default_keyword()` / `default_separator()` / `default_value()`; mirrored in
  the Python bindings.

### Changed

- **Technically breaking for exhaustive `match`es in Rust**: new variants on
  the public enums `NumPySectionKind` (`KeywordParameters` + eight admonition
  kinds) and `SyntaxKind` (`GOOGLE_DEPRECATION`, `GOOGLE_REFERENCE`). Accepted
  in this patch release given the crate's age; 0.2.0 will add
  `#[non_exhaustive]` to prevent this class of breakage going forward.
  Python users are unaffected.

### Internal

- Test suite rebuilt around a shared corpus: snapshot harness
  (`tests/corpus/` + `tests/snapshots.rs`), round-trip law tests
  (idempotence / model stability / cross-style conversion with a burn-down
  allowlist), and a Python parity suite that checks the bindings
  byte-for-byte against the Rust snapshots.

## [0.1.14] - 2026-07-06

### Fixed

- NumPy and Google parsers: colons belonging to reStructuredText role
  references (e.g. `:attr:`~module.ClassName.attr1``) and trailing colons in
  prose lines (e.g. `Description with attributes:`) were misinterpreted as
  term/classifier separators, causing the colons to be dropped on re-emit.
  A new reStructuredText-aware separator rule now treats a colon as a
  separator only when it follows whitespace (`name : type`) or is attached to
  a single top-level token (`name:type`), leaving role references and prose
  intact ([#26](https://github.com/ryumasai/pydocstring/issues/26)).

### Added

- Sphinx-style (reStructuredText) emit: `emit::sphinx::emit_sphinx` renders a
  style-independent `Docstring` model as a Sphinx field list (`:param:`,
  `:type:`, `:raises:`, `:return:`, `:rtype:`, …), enabling conversion from
  Google / NumPy to Sphinx. Exposed in the Python bindings as `emit_sphinx`.
  Sphinx support is emit-only; `detect_style` still reports Sphinx docstrings as
  `Style::Plain`.

## [0.1.13] - 2026-05-11

### Added

- Python bindings: added Python 3.14 support by updating PyO3 to 0.28.

### Changed

- Python bindings: switched wheel builds to the stable `abi3-py310` ABI so one
  wheel per platform can support Python 3.10 and newer.

## [0.1.12] - 2026-05-09

### Fixed

- Improved indentation handling for Google and NumPy docstrings when converting
  parsed docstrings to the style-independent model and when emitting docstrings
  back to text. Multi-line descriptions, free-text sections, and round-trip
  parse/edit/emit workflows now preserve real-world indentation more reliably.

## [0.1.11] - 2026-04-15

### Changed

- Updated repository URLs to reflect new GitHub username (`ryumasai`).

## [0.1.10] - 2026-04-09

### Fixed

- Google parser: section entries at the same indentation level as the section
  header (e.g. zero-indented docstrings) were incorrectly emitted as
  `STRAY_LINE` tokens and silently dropped. A new `body_is_deeper: Option<bool>`
  flag is introduced to record, on the first body line, whether the body is
  indented deeper than the header. The flush condition is now a three-way
  decision: when no body line has been seen yet, flush only on a *strictly*
  shallower line; when the body is deeper than the header, flush at the
  header's indentation level (previous behaviour); when the body is at the
  same level as the header, never flush by indentation (a following section
  header detected by keyword is still recognised). This also makes the parser
  tolerant of slightly mis-indented entries (e.g. 3-space indent when the
  first entry used 4 spaces).

## [0.1.9] - 2026-04-01

### Fixed

- Google parser: stray lines without a preceding blank line were incorrectly
  absorbed into the current section as bogus entries. The `had_blank_in_section`
  flag is removed; instead, any non-blank line at or below the section header's
  indentation level unconditionally flushes the current section, regardless of
  whether a blank line preceded it.
- NumPy parser: the `had_blank_in_section` flush introduced in v0.1.8 incorrectly
  terminated a section when two entries were separated by a blank line (e.g.
  `x : int\n\ny : float` inside a `Parameters` block). The flag is removed;
  NumPy sections now end only when the next `name\n---` header is detected,
  matching the NumPy docstring specification (stray lines inside NumPy sections
  are a known limitation documented in the source).

## [0.1.8] - 2026-03-31

### Fixed

- Google parser: a non-indented line following a blank line inside a section
  was incorrectly absorbed into that section as a bogus entry (e.g. `stray
  line 1` became an `Args` entry) or appended to the preceding
  `Returns` description. The parser now flushes the current section when a
  blank line is followed by a line whose indentation is at or below the
  section header's indentation level.
- NumPy parser: same fix applied. `FreeText` sections (Notes, Examples, etc.)
  are exempt because their body lines legitimately share the same indentation
  level as the section header.

## [0.1.7] - 2026-03-30

### Fixed

- Google parser: prevent panic when an RST-style parameter line (e.g.
  `:param int seconds: …`) is misclassified as a Google-style `Args:` entry.
  `parse_entry_header` now falls through to the bare-name branch whenever
  the colon is at position 0, avoiding an empty `NAME` token.
- `required_token` no longer panics on zero-length (missing) placeholder
  tokens. It now scans children directly and panics only when the token kind
  is completely absent — indicating a structural bug in the parser — rather
  than when the token is present but zero-length.
- NumPy parser: all `build_*_node` functions now emit zero-length placeholder
  tokens for grammatically expected but source-absent tokens, matching the
  convention already used by the Google `build_arg_node`.
  Affected builders: `build_parameter_node` (TYPE), `build_returns_node`
  (RETURN_TYPE), `build_yields_node` (RETURN_TYPE), `build_exception_node`
  (DESCRIPTION), `build_warning_node` (DESCRIPTION), `build_see_also_node`
  (DESCRIPTION), `build_attribute_node` (TYPE).
  Callers can now reliably use `find_missing()` to distinguish
  "expected but absent" from "not applicable".

## [0.1.6] - 2026-03-29

### Added

- `GoogleSectionKind` / `NumPySectionKind` enums — each section now carries a
  `section_kind` property in place of a plain string, enabling exhaustive
  pattern matching in Python.
- `pydocstring.Visitor` base class — all `enter_*` / `exit_*` hook methods are
  declared here; `walk()` now raises `TypeError` if the visitor does not
  subclass `Visitor`.
- `WalkContext` — second argument passed to every `enter_*` / `exit_*` method;
  exposes `line_col(offset)` using a cached line-starts table for O(log n)
  offset-to-line/column conversion.
- `walk()` now returns the visitor instance (typed as `_VisitorT` in stubs),
  enabling one-liner patterns like `collector = walk(doc, Collector())`.
- `PlainDocstring` dispatch in `walk()``enter_plain_docstring` /
  `exit_plain_docstring` are now called when walking a plain-style docstring.
- All CST punctuation and structural tokens are now exposed on every node
  object: `open_bracket`, `close_bracket`, `colon`, `directive_marker`,
  `double_colon`, `default_keyword`, `default_separator`, etc.
- `Token.is_missing()` — returns `True` for zero-length placeholder tokens
  inserted by the parser for syntactically absent elements (e.g. an empty-
  bracket type `arg (): desc`).
- `TextRange.is_empty()` — returns `True` when `start == end`.
- `SectionKind` enum (24 variants) on the style-independent model IR —
  replaces the previous string-typed `Section.kind`.
- `Section.unknown_name` getter — returns the raw section header string for
  `SectionKind.UNKNOWN` entries.
- Missing entry wrapper classes added to the Python bindings: `GoogleWarning`,
  `GoogleSeeAlsoItem`, `GoogleAttribute`, `GoogleMethod`, `NumPyDeprecation`,
  `NumPyWarning`, `NumPySeeAlsoItem`, `NumPyReference`, `NumPyAttribute`,
  `NumPyMethod`.
- `NumPyDocstring.deprecation` property — direct accessor for the deprecation
  notice node.
- `__all__` in `pydocstring/__init__.py` listing all public symbols.
- `py.typed` marker (PEP 561) — the package now ships inline type stubs.
- Rust core: `DocstringVisitor` gains an associated `Error` type; all
  `visit_*` methods return `Result<(), Self::Error>`, and `walk_node`
  propagates errors via `?`.

### Changed

- **Python bindings**: `GoogleReturns` renamed to `GoogleReturn`; `GoogleYields`
  renamed to `GoogleYield` — consistent with the singular naming convention
  used by all other entry wrapper types.
- **Python bindings**: `parse()` now returns a typed
  `GoogleDocstring | NumPyDocstring | PlainDocstring` union instead of an opaque
  `object`.
- `Token` now implements `__eq__` and `__hash__` so tokens can be used in sets
  and as dictionary keys.
- `pretty_print()` and `to_model()` on all docstring objects now use a cached
  `Arc<Parsed>` internally, avoiding a redundant re-parse on every call.
- `WalkContext.line_col()` replaces the former `doc.line_col()` method on
  docstring objects; cost reduced from O(offset) linear scan to O(log L)
  binary search.
- Visitor package layout converted to a mixed Rust/Python maturin layout:
  the `Visitor` base class is now defined in `pydocstring/_visitor.py` rather
  than being embedded as a string in the Rust source.
- Rust core: `walk` and `walk_node` are now in `parse::visitor` and
  re-exported from `parse::google` and `parse::numpy`.

### Fixed

- `GoogleSection` and `NumPySection` stubs no longer declare 19 child accessor
  properties that had no corresponding Rust implementation.

### Breaking Changes

**Python bindings**

- Typed `*Section` classes (e.g. `GoogleArgsSection`, `NumPyParametersSection`)
  removed; replaced by a single `GoogleSection` / `NumPySection` with a
  `section_kind` property and per-kind accessor methods (`section.args()`,
  `section.parameters()`, `section.returns()`, etc.).
- `cast_google_*` / `cast_numpy_*` methods on docstring objects removed.
- Visitor hook methods renamed: `visit_*``enter_*`, `leave_*``exit_*`
  (ANTLR convention).
- `walk()` now requires the visitor to be a subclass of `pydocstring.Visitor`;
  passing an arbitrary object raises `TypeError`.
- `doc.line_col(offset)` removed from `GoogleDocstring`, `NumPyDocstring`, and
  `PlainDocstring`; use `ctx.line_col(offset)` inside a visitor hook instead.
- `Section.kind` is now a `SectionKind` enum value, not a string.
- `GoogleReturns` / `GoogleYields` class names changed to `GoogleReturn` /
  `GoogleYield`.

**Rust core**

- `DocstringVisitor`: all `visit_*` methods now return `Result<(), Self::Error>`
  and require `type Error = ...;` in every implementation. Infallible
  implementations should use `type Error = std::convert::Infallible`.

## [0.1.5] - 2026-03-22

### Added

- `SyntaxKind::GOOGLE_YIELDS` — dedicated node kind for entries inside a Google
  `Yields:` section. Previously these were emitted as `GOOGLE_RETURNS`.
- `SyntaxKind::NUMPY_YIELDS` — dedicated node kind for entries inside a NumPy
  `Yields` section. Previously these were emitted as `NUMPY_RETURNS`.
- `GoogleYields` typed wrapper with `return_type()`, `colon()`, and
  `description()` accessors (analogous to `GoogleReturns`).
- `NumPyYields` typed wrapper with `name()`, `colon()`, `return_type()`, and
  `description()` accessors (analogous to `NumPyReturns`).
- `GoogleSection::yields()` — accessor returning the `GoogleYields` node for
  a Yields section, distinct from `returns()`.
- `NumPySection::yields()` — accessor returning an iterator of `NumPyYields`
  nodes for a Yields section, distinct from `returns()`.
- Python bindings: `SyntaxKind.GOOGLE_YIELDS`, `SyntaxKind.NUMPY_YIELDS`,
  `GoogleYields` class, `NumPyYields` class, `GoogleSection.yields` property,
  and `NumPySection.yields` property.

### Changed

- Google parser: `Yields:` sections now produce `GOOGLE_YIELDS` child nodes
  instead of `GOOGLE_RETURNS`.
- NumPy parser: `Yields` sections now produce `NUMPY_YIELDS` child nodes
  instead of `NUMPY_RETURNS`.
- `to_model` (Google & NumPy): `Yields` sections now use the `yields()`
  accessor on the typed section wrapper rather than sharing the `returns()`
  code path.

## [0.1.4] - 2026-03-20

### Added

- `Style::Plain` — new style variant returned by `detect_style` for docstrings
  that contain no NumPy section underlines or Google section headers (e.g.
  summary-only docstrings, Sphinx-style docstrings).
- `SyntaxKind::PLAIN_DOCSTRING` — root node kind for plain-style parse trees.
- `parse_plain(input)` — lightweight parser that extracts only a `SUMMARY` and
  an optional `EXTENDED_SUMMARY` token from the input, without attempting
  section detection.
- `parse(input)` — unified entry point that calls `detect_style` and dispatches
  to `parse_google`, `parse_numpy`, or `parse_plain` automatically.
- `PlainDocstring` typed wrapper with `summary()` and `extended_summary()`
  accessors (mirrors the existing `GoogleDocstring` / `NumPyDocstring` API).
- Python bindings: `Style.PLAIN`, `SyntaxKind.PLAIN_DOCSTRING`, `PlainDocstring`
  class, and `parse_plain(input)` function.
- Google parser: zero-length `DESCRIPTION` token emitted when a colon is
  present but no description text follows (e.g. `a (int):`, `a:`), and
  zero-length `TYPE` token emitted for empty brackets `()`.
- NumPy parser: zero-length `TYPE` token emitted when a colon is present but
  type text is absent (e.g. `a :`); zero-length `DEFAULT_VALUE` token emitted
  when a default separator is present but no value follows (e.g. `default =`).
  Callers can use `find_missing(KIND)` to detect these absent-but-declared
  slots without inspecting surrounding tokens.
- `examples/parse_auto.rs` — demonstrates the unified `parse()` entry point
  with Google, NumPy, and plain-style inputs.

### Changed

- `detect_style` rewritten as a single O(n) pass; returns `Style::Plain` as the
  fallback instead of `Style::Google`.

## [0.1.3] - 2026-03-19

### Added

- Section name matching now accepts additional singular and alias forms for both
  Google and NumPy styles:
  - `"arg"`, `"param"`, `"keyword arg"`, `"keyword param"`, `"other arg"`,
    `"other param"`, `"method"`, `"reference"` (Google)
  - `"arguments"`, `"argument"`, `"args"`, `"arg"`, `"other arguments"`,
    `"other argument"`, `"other args"`, `"other arg"`, `"attribute"`,
    `"method"`, `"reference"` (NumPy)
  - Common typos tolerated: `"argment"`, `"paramter"` (Google)

### Fixed

- Google parser: arg entries with no description (e.g. `b :`) inside a section
  body were incorrectly classified as new section headers. Fixed by comparing
  the indentation of each line against the current section header's indentation
  and skipping header detection for more-indented lines.

### Changed

- Refactored Google entry header parsing to use a left-to-right confirmation
  algorithm. Handles missing close brackets, missing colons, and text after
  brackets without a colon more robustly. `close_bracket` in `TypeInfo` is now
  `Option<TextRange>` to represent the missing-bracket case.
- Added `rustfmt.toml` (`max_width = 120`) and reformatted all source files.

## [0.1.2] - 2026-03-16

### Added

- `LineColumn` struct (`lineno`, `col`) in `text.rs` for representing
  line/column positions; `lineno` is 1-based, `col` is a 0-based byte offset
  within the line.
- `LineIndex` in `text.rs` — a newline-offset lookup table built from source
  text; converts any `TextSize` byte offset to `LineColumn` in O(log n).
- `Parsed::line_col(offset: TextSize) -> LineColumn` method for resolving
  byte offsets in the syntax tree to line/column positions.
- Python bindings: `LineColumn` class with `lineno` and `col` properties.
  `col` is expressed in **Unicode codepoints** (compatible with Python's
  `ast` module convention) rather than raw bytes.
- Python bindings: `GoogleDocstring.line_col(offset)` and
  `NumPyDocstring.line_col(offset)` methods; `offset` is typically obtained
  from `Token.range.start` or `Token.range.end`.

## [0.1.1] - 2026-03-10

### Added

- Python bindings: `SyntaxKind` enum exposed as `enum.IntEnum`, usable for
  pattern matching on `Token.kind` and `Node.kind` instead of raw strings.

### Fixed

- `emit_google` / `emit_numpy` now correctly apply `base_indent` to all lines
  of the emitted docstring, not just the first line.

### Changed

- Python bindings: `Token.kind` and `Node.kind` now return `SyntaxKind` instead
  of `str`.

## [0.1.0] - 2025-03-09

### Added

- Google style docstring parsing (`parse_google`)
- NumPy style docstring parsing (`parse_numpy`)
- Automatic style detection (`detect_style`)
- Unified model IR (`Docstring`, `Section`, `Parameter`, `Return`, etc.)
- Emit back to Google style (`emit_google`)
- Emit back to NumPy style (`emit_numpy`)
- Full syntax tree (AST) with byte-precise source locations (`TextRange`)
- Tree traversal via `walk` and visitor pattern
- Pretty-print for AST debugging (`pretty_print`)
- Conversion from AST to unified model (`to_model`)
- Support for all standard sections:
  - Parameters / Args / Keyword Args / Other Parameters
  - Returns / Yields
  - Raises / Warns
  - Attributes / Methods
  - See Also / References
  - Deprecation
  - Free-text sections (Notes, Examples, Warnings, Todo, etc.)
- Error-resilient parsing — never panics on malformed input
- Zero external crate dependencies
- Python bindings via PyO3 (`pydocstring-rs`)

[0.3.0]: https://github.com/ryumasai/pydocstring/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/ryumasai/pydocstring/compare/v0.1.15...v0.2.0
[0.1.15]: https://github.com/ryumasai/pydocstring/compare/v0.1.14...v0.1.15
[0.1.14]: https://github.com/ryumasai/pydocstring/compare/v0.1.13...v0.1.14
[0.1.13]: https://github.com/ryumasai/pydocstring/compare/v0.1.12...v0.1.13
[0.1.12]: https://github.com/ryumasai/pydocstring/compare/v0.1.11...v0.1.12
[0.1.11]: https://github.com/ryumasai/pydocstring/compare/v0.1.10...v0.1.11
[0.1.10]: https://github.com/ryumasai/pydocstring/compare/v0.1.9...v0.1.10
[0.1.9]: https://github.com/ryumasai/pydocstring/compare/v0.1.8...v0.1.9
[0.1.8]: https://github.com/ryumasai/pydocstring/compare/v0.1.7...v0.1.8
[0.1.7]: https://github.com/ryumasai/pydocstring/compare/v0.1.6...v0.1.7
[0.1.6]: https://github.com/ryumasai/pydocstring/compare/v0.1.5...v0.1.6
[0.1.5]: https://github.com/ryumasai/pydocstring/compare/v0.1.4...v0.1.5
[0.1.4]: https://github.com/ryumasai/pydocstring/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/ryumasai/pydocstring/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/ryumasai/pydocstring/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/ryumasai/pydocstring/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/ryumasai/pydocstring/releases/tag/v0.1.0