sphinx-java-autodoc 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/PKG-INFO +1 -1
  2. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/javadoc-support.rst +5 -0
  3. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/schema.md +15 -5
  4. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/usage.rst +8 -1
  5. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/src/demo/Widget.java +1 -1
  6. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/pyproject.toml +1 -1
  7. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/domain.py +71 -13
  8. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/extractor.py +1 -1
  9. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/java/SphinxDoclet.java +182 -34
  10. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/render.py +19 -3
  11. sphinx_java_autodoc-0.4.0/tests/test_conformance.py +235 -0
  12. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_generic_throws.py +2 -1
  13. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_integration.py +26 -8
  14. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_navigation.py +12 -5
  15. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_signatures.py +25 -1
  16. sphinx_java_autodoc-0.3.0/tests/test_conformance.py +0 -92
  17. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/.gitignore +0 -0
  18. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/CONTRIBUTING.md +0 -0
  19. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/LICENSE +0 -0
  20. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/README.md +0 -0
  21. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/SECURITY.md +0 -0
  22. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/adr/0001-jdk-doclet-xml-sphinx-domain.md +0 -0
  23. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/agents/domain.md +0 -0
  24. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/agents/issue-tracker.md +0 -0
  25. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/alternatives.rst +0 -0
  26. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/build-integration.rst +0 -0
  27. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/compatibility.rst +0 -0
  28. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/conf.py +0 -0
  29. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/design.rst +0 -0
  30. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/gallery/comments.rst +0 -0
  31. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/gallery/declarations.rst +0 -0
  32. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/gallery/directives.rst +0 -0
  33. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/gallery/index.rst +0 -0
  34. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/gallery/inheritance.rst +0 -0
  35. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/gallery/links.rst +0 -0
  36. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/gallery/warnings.rst +0 -0
  37. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/getting-started.rst +0 -0
  38. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/guide.rst +0 -0
  39. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/index.rst +0 -0
  40. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/performance.rst +0 -0
  41. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/showcase.rst +0 -0
  42. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/docs/troubleshooting.rst +0 -0
  43. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/README.md +0 -0
  44. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/build-consumer/BuildInfo.java.in +0 -0
  45. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/build-consumer/src/demo/Consumer.java +0 -0
  46. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/dependency/src/vendor/Token.java +0 -0
  47. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/gradle/build.gradle +0 -0
  48. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/gradle/settings.gradle +0 -0
  49. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/maven/pom.xml +0 -0
  50. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/docs/conf.py +0 -0
  51. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/docs/guide.rst +0 -0
  52. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/docs/index.rst +0 -0
  53. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/docs/reference.rst +0 -0
  54. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/src/demo/Label.java +0 -0
  55. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/src/demo/Mode.java +0 -0
  56. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/src/demo/Point.java +0 -0
  57. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/src/demo/Shape.java +0 -0
  58. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/src/demo/Transformer.java +0 -0
  59. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/examples/standalone/src/demo/package-info.java +0 -0
  60. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/__init__.py +0 -0
  61. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/__main__.py +0 -0
  62. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/javadoc.py +0 -0
  63. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/model.py +0 -0
  64. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/pages.py +0 -0
  65. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/src/sphinx_java_autodoc/static/java-autodoc.css +0 -0
  66. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/benchmark.py +0 -0
  67. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/build_tools_check.py +0 -0
  68. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_boundaries.py +0 -0
  69. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_javadoc.py +0 -0
  70. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_options.py +0 -0
  71. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_snippets.py +0 -0
  72. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_sourcepath.py +0 -0
  73. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_tables.py +0 -0
  74. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/test_value.py +0 -0
  75. {sphinx_java_autodoc-0.3.0 → sphinx_java_autodoc-0.4.0}/tests/visual_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sphinx-java-autodoc
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Compiler-backed Java API documentation in native Sphinx nodes
5
5
  Project-URL: Documentation, https://r4sky0.github.io/sphinx-java-autodoc/
6
6
  Project-URL: Repository, https://github.com/r4sky0/sphinx-java-autodoc
@@ -20,6 +20,11 @@ Tags and HTML
20
20
  ``@author``, ``@version`` and the ``@serial`` tags are omitted silently, as in the
21
21
  standard doclet's default output. Other custom tags warn.
22
22
 
23
+ Text reads as in the standard doclet. A link without a label shows
24
+ ``Map.Entry``, ``String.format(String, Object...)``, or ``of(L, R)`` for a member of
25
+ the same type. Sphinx's smart quotes are off, so quotes and ``...`` stay as written.
26
+ ``@throws`` names link to the exception type.
27
+
23
28
  Constant values
24
29
  ---------------
25
30
 
@@ -26,12 +26,20 @@ not a general XML document interchange service. No external entities are fetched
26
26
  parameter or record component. `r` carries `target` (canonical ID) and the external
27
27
  Javadoc `path`, `module` and `fragment`; its text is the nested simple name.
28
28
  Readers reject unknown tokens and malformed `r` targets.
29
- - `doc/part`: `kind` (`body`, `param`, `typeparam`, `return`, `throws`, `see`,
30
- `since`, `deprecated`, `apiNote`, `implSpec`, `implNote`) and a `label`. The three
31
- note kinds are additive in schema 1: older renderers show them under their kind name. Parameter labels follow the child declaration
29
+ - `doc/part`: `kind` (`body`, `specifiedBy`, `overrides`, `param`, `typeparam`,
30
+ `return`, `throws`, `see`, `since`, `deprecated`, `apiNote`, `implSpec`, `implNote`)
31
+ and a `label`. The three note kinds, `specifiedBy` and `overrides` are additive in
32
+ schema 1: older renderers show them under their kind name. `specifiedBy` and
33
+ `overrides` follow `body`: one `specifiedBy` per implemented interface method, then
34
+ the superclass method as `overrides`, or `specifiedBy` when it is abstract. Each
35
+ holds a method `link`, ` in class ` or ` in interface ` text, and a type `link`. Parameter labels follow the child declaration
32
36
  even when prose is inherited. Part contents are an ordered token sequence.
37
+ `typeparam` parts document the type parameters of generic types and methods.
33
38
  Throws labels name the overriding method's exception after generic substitution;
34
- type variables use their declared names.
39
+ type variables use their declared names. A `throws` part whose exception is a type
40
+ also carries `target`, `role` and the external Javadoc `path`, `module` and
41
+ `fragment`, as `link` does, and its label is the type's name without its package.
42
+ These attributes are additive in schema 1.
35
43
  Repeated `throws` descriptions for one exception share a part, separated by
36
44
  paragraph tokens, so explicit and omitted inheritance preserve all descriptions.
37
45
  - Tokens: `text`, `literal`, `code`, `link`, `start`, `end`. Text is element text,
@@ -52,7 +60,9 @@ not a general XML document interchange service. No external entities are fetched
52
60
  - `link`: `target` is the compiler's canonical symbol ID; `role` identifies the
53
61
  domain object kind. `path`, `module`, `fragment` hold external Javadoc location
54
62
  metadata; `plain` distinguishes link/plainlink presentation. Children form the
55
- label. An unresolved link has no resolved location and has a diagnostic.
63
+ label; a comment link without one gets the standard doclet's text, such as
64
+ `Map.Entry`, `String.format(String, Object...)` or `of(L, R)` within the same type.
65
+ An unresolved link has no resolved location and has a diagnostic.
56
66
  - `diagnostic`: symbol-qualified warning text, including unsupported constructs.
57
67
  Warnings survive cache hits and are re-emitted by Sphinx.
58
68
 
@@ -95,7 +95,9 @@ Other directives work the same way:
95
95
 
96
96
  ``java:autopackage`` renders the ``package-info.java`` comment.
97
97
  ``java:autosummary`` lists each entry with a link and its first sentence; unknown
98
- entries warn as ``java.summary``. Generated pages consist of these directives.
98
+ entries warn as ``java.summary``. As in Javadoc, members are listed with their
99
+ declared parameters, and methods and fields get a column with their modifiers and
100
+ type, such as ``static <L, R> Pair<L, R>``. Generated pages consist of these directives.
99
101
 
100
102
  What gets documented
101
103
  --------------------
@@ -105,7 +107,12 @@ What gets documented
105
107
  protected types.
106
108
  - Only declared members are listed. An overriding method inherits its ancestor's
107
109
  comment; other inherited methods stay on the declaring type's page.
110
+ - As in Javadoc, an overriding method notes what it implements or overrides:
111
+ "Specified by: ``convert`` in interface ``Transformer``", or "Overrides:
112
+ ``hashCode`` in class ``Object``".
108
113
  - Nested types get their own pages.
114
+ - Signatures show only ``@Documented`` annotations, as the standard doclet does, so
115
+ ``@Override`` and ``@SuppressWarnings`` are left out.
109
116
 
110
117
  Roles
111
118
  -----
@@ -50,7 +50,7 @@ public class Widget implements Transformer<String> {
50
50
  * @throws IllegalArgumentException {@inheritDoc}
51
51
  */
52
52
  @Override public String convert(String renamed) throws IllegalArgumentException { return renamed; }
53
- /** Converts a number to text.
53
+ /** Converts a number to text, such as "42"...
54
54
  * @param number the number
55
55
  * @return decimal text
56
56
  */
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sphinx-java-autodoc"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Compiler-backed Java API documentation in native Sphinx nodes"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -98,6 +98,49 @@ def signature_nodes(tokens) -> list[nodes.Node]:
98
98
  return result
99
99
 
100
100
 
101
+ # Javadoc's "Modifier and Type" column leaves out visibility and implementation details.
102
+ SUMMARY_MODIFIERS = {"protected", "static", "final", "abstract", "default"}
103
+
104
+
105
+ def summary_type(element) -> list[nodes.Node]:
106
+ """A member's modifiers, type parameters and type, as in Javadoc's summary column."""
107
+ tokens = []
108
+ sig = list(element.find("sig"))
109
+ i = 0
110
+ while i < len(sig) and sig[i].tag != "n":
111
+ if sig[i].tag == "t" and sig[i].text == "@":
112
+ # An annotation: "@", its type, optional arguments and a space.
113
+ i += 2
114
+ if i < len(sig) and sig[i].tag == "t" and sig[i].text.startswith("("):
115
+ i += 1
116
+ else:
117
+ tokens.append(sig[i])
118
+ i += 1
119
+ owner = SYMBOLS.get(element.get("id").partition("#")[0])
120
+ interface = owner is not None and owner.get("kind") in {
121
+ "interface",
122
+ "annotation_type",
123
+ }
124
+ modifiers = []
125
+ while tokens and (tokens[0].tag == "k" or tokens[0].text == " "):
126
+ text = tokens.pop(0).text
127
+ if text in SUMMARY_MODIFIERS and not (interface and text == "abstract"):
128
+ modifiers.append(text)
129
+ if tokens and tokens[-1].tag == "t" and tokens[-1].text == " ":
130
+ tokens.pop()
131
+ prefix = [nodes.Text(" ".join(modifiers) + " ")] if modifiers else []
132
+ return prefix + signature_nodes(tokens)
133
+
134
+
135
+ def summary_label(element) -> str:
136
+ """A member's name with declared parameters, as in Javadoc's summaries."""
137
+ params = element.find("sig/params")
138
+ if element.tag == "type" or params is None:
139
+ return local_name(element)
140
+ declared = ("".join(t.text or "" for t in param) for param in params)
141
+ return f"{element.find('sig/n').text}({', '.join(declared)})"
142
+
143
+
101
144
  def display_name(symbol: str) -> str:
102
145
  owner, _, member = symbol.partition("#")
103
146
  return f"{owner}.{member_label(symbol)}" if member else symbol
@@ -271,24 +314,24 @@ class AutoPackage(JavaDirective):
271
314
 
272
315
 
273
316
  class AutoSummary(JavaDirective):
274
- """A table of linked names and first sentences, like sphinx.ext.autosummary."""
317
+ """A table of linked names and first sentences, like sphinx.ext.autosummary.
318
+
319
+ Methods and fields get a first column with their modifiers and type, as in Javadoc.
320
+ """
275
321
 
276
322
  has_content = True
277
323
 
278
324
  def run(self):
279
325
  self.env.note_dependency(self.env.java_model_path)
280
326
  table = nodes.table("", classes=["autosummary", "longtable", "java"])
281
- group = nodes.tgroup("", cols=2)
282
- group += nodes.colspec("", colwidth=10)
283
- group += nodes.colspec("", colwidth=90)
284
- body = nodes.tbody()
327
+ rows = []
285
328
  for line in self.content:
286
329
  symbol = re.sub(r"\s+", "", line)
287
330
  if not symbol:
288
331
  continue
289
332
  element = self.lookup(symbol)
290
333
  if element is not None:
291
- label = local_name(element)
334
+ label = summary_label(element)
292
335
  elif symbol in PACKAGES:
293
336
  element, label = PACKAGES[symbol], symbol
294
337
  else:
@@ -303,17 +346,32 @@ class AutoSummary(JavaDirective):
303
346
  refexplicit=True,
304
347
  refwarn=True,
305
348
  )
306
- text = nodes.paragraph("")
349
+ text = nodes.paragraph("", support_smartquotes=False)
307
350
  if deprecated(element):
308
351
  text += [nodes.strong("", "Deprecated."), nodes.Text(" ")]
309
352
  text.extend(summary(element, self.get_location()))
310
- body += nodes.row(
311
- "",
312
- nodes.entry("", nodes.paragraph("", "", link)),
313
- nodes.entry("", text),
314
- )
315
- if not body.children:
353
+ typed = element.tag == "member" and element.get("kind") in {
354
+ "method",
355
+ "field",
356
+ }
357
+ kind = nodes.literal("", "", *summary_type(element)) if typed else None
358
+ rows.append((kind, nodes.paragraph("", "", link), text))
359
+ if not rows:
316
360
  return []
361
+ typed = any(kind is not None for kind, _, _ in rows)
362
+ widths = (15, 25, 60) if typed else (10, 90)
363
+ group = nodes.tgroup("", cols=len(widths))
364
+ group.extend(nodes.colspec("", colwidth=width) for width in widths)
365
+ body = nodes.tbody()
366
+ for kind, name, text in rows:
367
+ cells = []
368
+ if typed:
369
+ cells.append(
370
+ nodes.entry()
371
+ if kind is None
372
+ else nodes.entry("", nodes.paragraph("", "", kind))
373
+ )
374
+ body += nodes.row("", *cells, nodes.entry("", name), nodes.entry("", text))
317
375
  group += body
318
376
  table += group
319
377
  return [table]
@@ -16,7 +16,7 @@ from sphinx.errors import ExtensionError
16
16
 
17
17
  from .model import Model, read_model
18
18
 
19
- VERSION = "0.3.0" # x-release-please-version
19
+ VERSION = "0.4.0" # x-release-please-version
20
20
  DOCLET = Path(__file__).parent / "java" / "SphinxDoclet.java"
21
21
  PACKAGE = re.compile(r"[^\W\d]\w*(?:\.[^\W\d]\w*)*")
22
22
 
@@ -3,6 +3,7 @@ package sphinxjava;
3
3
  import com.sun.source.doctree.*;
4
4
  import com.sun.source.util.DocTreePath;
5
5
  import com.sun.source.util.DocTrees;
6
+ import java.lang.annotation.Documented;
6
7
  import java.nio.file.*;
7
8
  import java.util.*;
8
9
  import java.util.function.Consumer;
@@ -301,7 +302,7 @@ public final class SphinxDoclet implements Doclet {
301
302
 
302
303
  private void sig(Xml parent, Element e) {
303
304
  var sig = parent.add("sig");
304
- for (var annotation : e.getAnnotationMirrors()) {
305
+ for (var annotation : documented(e.getAnnotationMirrors())) {
305
306
  annotation(sig, annotation);
306
307
  sig.addText("t", " ");
307
308
  }
@@ -344,7 +345,7 @@ public final class SphinxDoclet implements Doclet {
344
345
  var params = sig.add("params");
345
346
  for (var component : type.getRecordComponents()) {
346
347
  var param = params.add("param");
347
- for (var annotation : component.getAnnotationMirrors()) {
348
+ for (var annotation : documented(component.getAnnotationMirrors())) {
348
349
  annotation(param, annotation);
349
350
  param.addText("t", " ");
350
351
  }
@@ -380,7 +381,7 @@ public final class SphinxDoclet implements Doclet {
380
381
  for (int i = 0; i < parameters.size(); i++) {
381
382
  var parameter = parameters.get(i);
382
383
  var param = params.add("param");
383
- for (var annotation : parameter.getAnnotationMirrors()) {
384
+ for (var annotation : documented(parameter.getAnnotationMirrors())) {
384
385
  annotation(param, annotation);
385
386
  param.addText("t", " ");
386
387
  }
@@ -448,7 +449,7 @@ public final class SphinxDoclet implements Doclet {
448
449
  }
449
450
 
450
451
  private void typeTokens(Xml sig, TypeMirror type) {
451
- for (var annotation : type.getAnnotationMirrors()) {
452
+ for (var annotation : documented(type.getAnnotationMirrors())) {
452
453
  annotation(sig, annotation);
453
454
  sig.addText("t", " ");
454
455
  }
@@ -501,13 +502,24 @@ public final class SphinxDoclet implements Doclet {
501
502
 
502
503
  /** A link to a type: its compiler ID, external Javadoc location and nested simple name. */
503
504
  private void typeReference(Xml sig, TypeElement type) {
504
- String pkg = elements.getPackageOf(type).getQualifiedName().toString();
505
- String name = type.getQualifiedName().toString();
506
505
  var reference = sig.add("r").set("target", id(type));
507
506
  javadocLocation(reference, type);
508
- reference
509
- .node()
510
- .setTextContent(Xml.safe(pkg.isEmpty() ? name : name.substring(pkg.length() + 1)));
507
+ reference.node().setTextContent(Xml.safe(localName(type)));
508
+ }
509
+
510
+ /** The type's name without its package, such as {@code Map.Entry}. */
511
+ private String localName(TypeElement type) {
512
+ String pkg = elements.getPackageOf(type).getQualifiedName().toString();
513
+ String name = type.getQualifiedName().toString();
514
+ return pkg.isEmpty() ? name : name.substring(pkg.length() + 1);
515
+ }
516
+
517
+ /** The standard doclet shows only annotations whose type is {@code @Documented}. */
518
+ private static List<? extends AnnotationMirror> documented(
519
+ List<? extends AnnotationMirror> annotations) {
520
+ return annotations.stream()
521
+ .filter(a -> a.getAnnotationType().asElement().getAnnotation(Documented.class) != null)
522
+ .toList();
511
523
  }
512
524
 
513
525
  private void annotation(Xml sig, AnnotationMirror annotation) {
@@ -601,8 +613,14 @@ public final class SphinxDoclet implements Doclet {
601
613
  private void putParam(Map<String, Part> result, Element e, ParamTree param) {
602
614
  String name = param.getName().toString();
603
615
  String key = "param:" + name;
604
- if (e instanceof ExecutableElement method) {
605
- var declared = param.isTypeParameter() ? method.getTypeParameters() : method.getParameters();
616
+ List<? extends Element> declared = null;
617
+ if (param.isTypeParameter() && e instanceof Parameterizable generic) {
618
+ declared = generic.getTypeParameters();
619
+ } else if (e instanceof ExecutableElement method) {
620
+ declared = method.getParameters();
621
+ }
622
+ // Record components keep their names; positions follow renamed overriding parameters.
623
+ if (declared != null) {
606
624
  int index = -1;
607
625
  for (int i = 0; i < declared.size(); i++) {
608
626
  if (declared.get(i).getSimpleName().contentEquals(name)) {
@@ -761,8 +779,9 @@ public final class SphinxDoclet implements Doclet {
761
779
  private void documentation(Xml node, Element e) {
762
780
  var doc = node.add("doc");
763
781
  var own = parts(e);
764
- var keys = new LinkedHashSet<>(own.keySet());
782
+ var keys = new LinkedHashSet<String>();
765
783
  keys.add("body");
784
+ keys.addAll(own.keySet());
766
785
  var method = overridingMethod(e);
767
786
  if (method != null) {
768
787
  for (int i = 0; i < method.getParameters().size(); i++) {
@@ -779,26 +798,121 @@ public final class SphinxDoclet implements Doclet {
779
798
  }
780
799
  }
781
800
  for (var key : keys) {
782
- Part part = own.get(key);
783
- if (part == null && method != null) {
784
- part = findPart(e, key, new HashSet<>());
801
+ part(doc, e, own, method, key);
802
+ // The standard doclet notes overridden methods between the description and the tags.
803
+ if (key.equals("body")) {
804
+ overrides(doc, e);
805
+ }
806
+ }
807
+ }
808
+
809
+ private void part(
810
+ Xml doc, Element e, Map<String, Part> own, ExecutableElement method, String key) {
811
+ Part part = own.get(key);
812
+ if (part == null && method != null) {
813
+ part = findPart(e, key, new HashSet<>());
814
+ }
815
+ if (part == null) {
816
+ if (method != null) {
817
+ warn(
818
+ e,
819
+ "Cannot inherit %s; provide the overridden method's source documentation or"
820
+ + " document this part explicitly.",
821
+ key);
785
822
  }
786
- if (part == null) {
787
- if (method != null) {
788
- warn(
789
- e,
790
- "Cannot inherit %s; provide the overridden method's source documentation or"
791
- + " document this part explicitly.",
792
- key);
823
+ return;
824
+ }
825
+ var xml = doc.add("part").set("kind", key.split(":", 2)[0]).set("label", label(e, key, part));
826
+ if (key.startsWith(THROWS)
827
+ && elements.getTypeElement(key.substring(THROWS.length())) instanceof TypeElement thrown) {
828
+ xml.set("label", localName(thrown)).set("target", id(thrown)).set("role", "type");
829
+ javadocLocation(xml, thrown);
830
+ }
831
+ String context = key.startsWith(THROWS) ? THROWS + part.label() : key;
832
+ emit(xml, part.owner(), context, part.trees(), new HashSet<>());
833
+ }
834
+
835
+ /**
836
+ * The standard doclet's notes: "Specified by" for each implemented interface method, then
837
+ * "Overrides" for the superclass method, or "Specified by" when that method is abstract.
838
+ */
839
+ private void overrides(Xml doc, Element e) {
840
+ if (!(e instanceof ExecutableElement method)
841
+ || method.getKind() != ElementKind.METHOD
842
+ || method.getModifiers().contains(Modifier.STATIC)) {
843
+ return;
844
+ }
845
+ var owner = (TypeElement) method.getEnclosingElement();
846
+ var specified = new ArrayList<ExecutableElement>();
847
+ implemented(method, owner, owner.getInterfaces(), specified, new HashSet<>());
848
+ // An interface method that another listed method overrides adds nothing.
849
+ specified.removeIf(
850
+ a ->
851
+ specified.stream()
852
+ .anyMatch(
853
+ b ->
854
+ b != a && elements.overrides(b, a, (TypeElement) b.getEnclosingElement())));
855
+ specified.forEach(ancestor -> overridePart(doc, "specifiedBy", ancestor));
856
+ for (var type = owner.getSuperclass();
857
+ types.asElement(type) instanceof TypeElement superclass;
858
+ type = superclass.getSuperclass()) {
859
+ for (var candidate : superclass.getEnclosedElements()) {
860
+ if (candidate instanceof ExecutableElement ancestor
861
+ && ancestor.getKind() == ElementKind.METHOD
862
+ && visible(ancestor, false)
863
+ && elements.overrides(method, ancestor, owner)) {
864
+ boolean specifies = ancestor.getModifiers().contains(Modifier.ABSTRACT);
865
+ overridePart(doc, specifies ? "specifiedBy" : "overrides", ancestor);
866
+ return;
793
867
  }
868
+ }
869
+ }
870
+ }
871
+
872
+ /** Collects the methods of {@code interfaces} and their superinterfaces that method overrides. */
873
+ private void implemented(
874
+ ExecutableElement method,
875
+ TypeElement owner,
876
+ List<? extends TypeMirror> interfaces,
877
+ List<ExecutableElement> result,
878
+ Set<Element> seen) {
879
+ for (var type : interfaces) {
880
+ var face = (TypeElement) types.asElement(type);
881
+ if (!seen.add(face)) {
794
882
  continue;
795
883
  }
796
- var xml = doc.add("part").set("kind", key.split(":", 2)[0]).set("label", label(e, key, part));
797
- String context = key.startsWith(THROWS) ? THROWS + part.label() : key;
798
- emit(xml, part.owner(), context, part.trees(), new HashSet<>());
884
+ for (var candidate : face.getEnclosedElements()) {
885
+ if (candidate instanceof ExecutableElement ancestor
886
+ && ancestor.getKind() == ElementKind.METHOD
887
+ && visible(ancestor, false)
888
+ && elements.overrides(method, ancestor, owner)) {
889
+ result.add(ancestor);
890
+ }
891
+ }
892
+ implemented(method, owner, face.getInterfaces(), result, seen);
799
893
  }
800
894
  }
801
895
 
896
+ /** A note such as "{@code hashCode} in class {@code Object}", linking both names. */
897
+ private void overridePart(Xml doc, String kind, ExecutableElement ancestor) {
898
+ var holder = (TypeElement) ancestor.getEnclosingElement();
899
+ var part = doc.add("part").set("kind", kind).set("label", "");
900
+ link(part, ancestor, ancestor.getSimpleName().toString());
901
+ part.addText("text", holder.getKind().isInterface() ? " in interface " : " in class ");
902
+ link(part, holder, localName(holder));
903
+ }
904
+
905
+ private void link(Xml parent, Element target, String text) {
906
+ var link =
907
+ parent
908
+ .add("link")
909
+ .set("plain", "false")
910
+ .set("target", id(target))
911
+ .set("role", role(target));
912
+ javadocLocation(link, target);
913
+ link.addText("text", text);
914
+ }
915
+
802
916
  /** Labels follow the documented declaration, even when the prose is inherited. */
803
917
  private static String label(Element e, String key, Part part) {
804
918
  if (key.startsWith(THROWS)) {
@@ -918,23 +1032,57 @@ public final class SphinxDoclet implements Doclet {
918
1032
  "Unresolved Javadoc reference %s; check spelling, source paths and classpath.",
919
1033
  ref);
920
1034
  } else {
921
- String role =
922
- switch (target) {
923
- case TypeElement _ -> "type";
924
- case ExecutableElement _ -> "method";
925
- case PackageElement _ -> "package";
926
- default -> "field";
927
- };
928
- link.set("role", role);
1035
+ link.set("role", role(target));
929
1036
  javadocLocation(link, target);
930
1037
  }
931
1038
  if (label.isEmpty()) {
932
- link.addText("text", ref.toString());
1039
+ link.addText("text", target == null ? ref.toString() : linkLabel(owner, ref, target));
933
1040
  } else {
934
1041
  emit(link, owner, "label", label, new HashSet<>());
935
1042
  }
936
1043
  }
937
1044
 
1045
+ /**
1046
+ * The standard doclet's text for a link without a label: {@code Map.Entry}, {@code
1047
+ * String.format(String, Object...)}, or {@code of(L, R)} for a member of the same type.
1048
+ */
1049
+ private String linkLabel(Element owner, ReferenceTree ref, Element target) {
1050
+ if (target instanceof PackageElement pkg) {
1051
+ return pkg.getQualifiedName().toString();
1052
+ }
1053
+ if (target instanceof TypeElement type) {
1054
+ return localName(type);
1055
+ }
1056
+ String signature = ref.getSignature();
1057
+ String member = signature.substring(signature.indexOf('#') + 1);
1058
+ if (target instanceof ExecutableElement method && !member.contains("(")) {
1059
+ member +=
1060
+ method.getParameters().stream()
1061
+ .map(p -> linkType(p.asType()).replaceAll("^.*\\.", ""))
1062
+ .collect(Collectors.joining(", ", "(", ")"));
1063
+ if (method.isVarArgs()) {
1064
+ member = member.substring(0, member.length() - 3) + "...)";
1065
+ }
1066
+ }
1067
+ Element scope = owner;
1068
+ while (scope != null && !(scope instanceof TypeElement)) {
1069
+ scope = scope.getEnclosingElement();
1070
+ }
1071
+ var type = (TypeElement) target.getEnclosingElement();
1072
+ return target.getKind() == ElementKind.CONSTRUCTOR || type.equals(scope)
1073
+ ? member
1074
+ : localName(type) + "." + member;
1075
+ }
1076
+
1077
+ private static String role(Element target) {
1078
+ return switch (target) {
1079
+ case TypeElement _ -> "type";
1080
+ case ExecutableElement _ -> "method";
1081
+ case PackageElement _ -> "package";
1082
+ default -> "field";
1083
+ };
1084
+ }
1085
+
938
1086
  /** Records where standard Javadoc publishes the target, for external Javadoc links. */
939
1087
  private void javadocLocation(Xml link, Element target) {
940
1088
  Element type = target;
@@ -356,6 +356,9 @@ def comment(part: ET.Element, location, quiet: bool = False) -> list[nodes.Node]
356
356
  )
357
357
  elif not p.astext().strip():
358
358
  p.parent.remove(p)
359
+ # Javadoc prints comment text as written; Sphinx would curl quotes and join dots.
360
+ for node in root.children:
361
+ node["support_smartquotes"] = False
359
362
  return root.children
360
363
 
361
364
 
@@ -408,6 +411,8 @@ def quiet_reference(app, env, node, contnode):
408
411
 
409
412
 
410
413
  FIELD_TITLES = {
414
+ "specifiedBy": "Specified by",
415
+ "overrides": "Overrides",
411
416
  "param": "Parameters",
412
417
  "typeparam": "Type parameters",
413
418
  "return": "Returns",
@@ -427,10 +432,11 @@ def field(kind: str, items) -> nodes.field:
427
432
  for label, content in items:
428
433
  content = list(content)
429
434
  if label:
435
+ label = nodes.strong("", "", label)
430
436
  if content and isinstance(content[0], nodes.paragraph):
431
- content[0].insert(0, [nodes.strong("", label), nodes.Text(" – ")])
437
+ content[0].insert(0, [label, nodes.Text(" – ")])
432
438
  else:
433
- content.insert(0, nodes.paragraph("", "", nodes.strong("", label)))
439
+ content.insert(0, nodes.paragraph("", "", label))
434
440
  bodies.append(content)
435
441
  if len(bodies) == 1:
436
442
  body = nodes.field_body("", *bodies[0])
@@ -453,12 +459,22 @@ def documentation(element: ET.Element, location) -> list[nodes.Node]:
453
459
  for part in element.findall("doc/part"):
454
460
  content = comment(part, location)
455
461
  kind = part.get("kind")
462
+ if kind in {"specifiedBy", "overrides"}:
463
+ # Ancestors outside the documented API stay text, as in summaries.
464
+ for node in content:
465
+ for xref in node.findall(addnodes.pending_xref):
466
+ xref["java:quiet"] = True
456
467
  if kind == "body":
457
468
  result.extend(content)
458
469
  elif kind == "deprecated":
459
470
  notice.extend(content)
460
471
  else:
461
- fields_by_kind.setdefault(kind, []).append((part.get("label"), content))
472
+ label = part.get("label") and nodes.Text(part.get("label"))
473
+ if label and part.get("target"):
474
+ # A documented exception links to its type, as in signatures.
475
+ label = reference(part, [label])
476
+ label["refwarn"] = False
477
+ fields_by_kind.setdefault(kind, []).append((label, content))
462
478
  for kind, items in fields_by_kind.items():
463
479
  fields += field(kind, items)
464
480
  if notice is not None: