Open srawlins opened 1 year ago
I'm not sure about the metadata, but the answer in Kotlin to the questions about the doc comments, is that all of that goes in the doc comment for the class.
Putting the constructor documentation into the class documentation worked for Kotlin because they have doc-tags like @param
and @return
to say that the next paragraph applies to a specific part of the declaration.
Dart has so far chosen not to have doc tags, and rely on convention like a paragraph starting with "Returns" being the documentation of the returned value, and an early reference to a parameter starting the parameter documentation, like "/// If [isFoo] is true, then ....
".
That means that our tools can't easily provide the documentation for just a single parameter the way Java/Kotlin can. And convention alone probably won't help us provide documentation for just the constructor taken from the class documentation.
We can document the individual parameters if we want to, by taking DartDoc on the primary constructor parameter as documentation for the implicit field:
class Foo(
/// The number of bars.
int bar,
/// ...
) ...
But there is no good place to put documentation for the constructor.
Maybe if we put the constructor last:
class Foo implements FooBar
/// Constructs a foo.
///
/// The `bar` is the number of bars.
new(
/// Number of bars.
final int bar,
) {
..
}
Where you can document the constructor by putting a comment before the primary constructor declaration, and the fields out introduces by putting comments on the field-declaring parameters.
At that point, we might as well just declare it inside the body as:
class Foo implements FooBar {
/// Constructs a foo.
///
/// The `bar` is the number of bars.
primary new(
/// Number of bars.
final int bar,
) {
..
}
(Can still use dartdoc on "field parameters" as property documentation.)
.. there is no good place to put documentation for the constructor.
True, but this might be OK. Primary constructors (as I've been thinking about them while writing this feature specification, and I think I'm not alone in that respect) are optimized for simple and small declarations. This might justify the advice that the documentation of the constructor is integrated into the documentation of the class, and DartDoc pages would show the documentation for the class as the documentation of the primary constructor as well. In that case it isn't necessary to introduce tags like @primaryConstructor
.
If there is a need to add more detail then the primary constructor in the header can be turned into a primary constructor in the body:
/// An extension type.
extension type V1 {
/// Wraps an [int] as a [V1].
primary V1(int it);
...
}
It's a small tangent, but honestly I wish Dart allowed docs on parameters. We can already do:
class Foo {
Foo({
// The IDE will show a's docs when the parameter is hovered
this.a,
});
/// This is documented
final int? a;
}
To me it's counter intuitive that we don't allow:
class Foo {
Foo({
// Allow the IDE to still show docs
/// Documentation for a
int? a,
}) : _a = a;
final int? _a;
}
If we could do that, then docs on primary constructors likely wouldn't be an issue. Right?
To me it's counter intuitive that we don't allow:
class Foo { Foo({ // Allow the IDE to still show docs /// Documentation for a int? a, }) : _a = a; final int? _a; // <- changed to `_a` }
There are several things in play here:
We have the first one, which is why this.a
gets specialized docs on hover. That's nice, but tricky, because it works only because of peeking through an implementation detail.
The fact that you write this.a)
instead of int a): this.a = a
is an implementation detail, one is just a shorthand which you can choose to use in some cases. We'd never want to give one of them access to special semantics, because then we force an implementation choice. But hover-docs gives one of them special behavior, by providing the doc of the initialized field only for the initializing formal.
Which makes some sense because the shown doc might refer to the name, and only the initializing formal is guaranteed to have the same name.
The tool providing the hover-info (analysis server, I think) could choose to recognize a this.a = a
initializer as well, and show the doc for the field on hover over the int a
parameter. That only really works when the two have the same name, otherwise the doc might not read correctly. (I don't think changing any occurrence of [fieldName]
or `fieldName`
to [parameterName]
and `parameterName`
is going to be viable. Too easy to make mistakes for the `fieldName`
references, when we don't know what they refer to.)
I'd definitely want the second item: Allowing you to write DartDoc on a primary constructor field parameter, one which implicitly introduces a field, so that that field can get DartDoc too. It'd be completely fair to treat the result as equivalent to a documented field and an initializing formal with the same name. That should just work.
Then there is what I believe you are suggesting here: Allowing docs on parameters in general, to be shown on hover. That's probably a viable feature that the DartDoc and analyzer teams can figure out, if we want. It risks conflicting with docs on primary constructor field parameters, but if we get the same doc on the field and the parameter anyway, it's probably fine.
And then there is the discussion of whether we can allow an primary constructor initializing formal to have a private name, and still expose a public-named parameter, like final int _a
introducing a field named _
and a parameter named a
(which is important if it's a named parameter). At that point, providing docs on the primary constructor field parameter would introduce the same docs for a field and a parameter with different names. If one of those names is private, it's probably OK to just write the doc using the public name, because that's all other people will see.
If we have a way to change the name more drastically, like final int? x as int y
, which is equivalent to a final int? x
field, a final int y
parameter and a this.x = y
initializer, then it's probably not possible to provide a single documentation that applies to both. And maybe it's OK, its not a feature we need to support, as long as we support declaring and documenting the field manually, and documenting the parameter as well.
(Again, let's not try top automatically rename references inside docs. Maybe we could allow dartdoc macros to be parameterized, so you can reuse, but also change the name:
default Floo(
/// {@template isFastDoc name}
/// Whether this floo is fast.
///
/// If [@{templatearg name}] is `null`, it's unknown whether the floo is fast.
/// Any attempt to [Floo.actFast] may fail, so you should use [Floo.tryActFast].
/// {@endtemplate)
/// {@macro isFastDoc #fast} <!-- the #fast is checked to resolve to a name in scope, use "fast" if you don't want that -->
bool? fast,
) : isFast = fast;
/// {@macro isFastDoc #isFast}
final bool? isFast;
But also, you can just copy, it's only two occurrences, so there are limits to how much effort is worth it.)
To be clear, I wasn't necessarily talking about private parameters here but rather initializers in general. Flutter suffers from this quite a bit. Lots of widgets defer to initializers. Private fields are only one case. For example I've also seen:
Class({required List list}):
list = list.map(...).toList();
or:
Class({Color? primaryColor, this.scheme}):
primaryColor = primaryColor ?? scheme?[0] ?? fallback;
Heck sometimes constructor parameters don't even have an associated field.
This makes dartdoc a bit unpredictable. Sometimes the IDE shows useful doc; sometimes it doesn't.
And it can vary between two versions of the same package. Because after a refactoring, maybe an initializer was added/removed.
I've been thinking a little bit about how primary constructors could help with cutting down verbosity in Flutter - and I share some of the concerns w.r.t. to documentation discussed in this issue:
In Flutter, the doc comments on constructors are mostly just there to check the box that all public members have docs. They rarely add anything useful. Therefore, I don't think we need a dedicated place for docs of the primary constructor itself. Usually, the class comment as a whole is just sufficient here.
However, we do need a place to provide docs for the individual parameters of the primary constructor (the docs that traditionally would just go on the fields). There's lots of great documentation on the fields today that I wouldn't want to loose and I also wouldn't want to turn the class docs into a wall of text that include documentation for each argument. Something with a little more structure would be desirable.
I really like putting the parameter docs inline into the primary constructor:
/// A widget that shows the current count along with a button to increase it.
class Counter const (
super.key,
/// The name of the counter.
///
/// Will be displayed in the UI.
String name,
/// The starting value for the counter.
///
/// Defaults to 0.
int startValue = 0,
) extends StatelessWidget {
// ...
}
I believe that neither the spec for extension types nor the proposed spec for primary constructors allow for doc comments or metadata (annotations) on a primary constructor.
Maybe that's fine. Let's examine each of these cases:
Cannot specify doc comment on an extension type's primary constructor
Well, an extension type's primary constructor can only have one parameter. The meaning of that parameter is always the same. Maybe it is difficult to conceive of a case where a doc comment specific to the primary constructor is important.
If the ability to specify doc comments is important, maybe we can just introduce a doc comment directive, e.g.
(Doc comment directives use kebab case; it is important to use as many cases in a programming language as possible.)
Cannot specify metadata on an extension type's primary constructor
This one worries me because I have to imagine there are plenty of cases where users desire to annotate a constructor. Maybe many cases can be handled by just assuming an annotation on the extension type is intended for the primary constructor. Maybe not.
I want to highlight the 3 primary purposes of annotations:
@visibleForTesting
,@experimental
, etc.Cannot specify doc comment on a (class's, etc.) primary constructor
Move the constructor (and fields) to be written explicitly, and then document the constructor as usual. The same goes for documenting the fields.
Cannot specify metadata on a (class's, etc.) primary constructor
Move the constructor (and fields) to be written explicitly, and then annotate as usual.