Version: 1.0.0
The purpose of this document is to provide a single specification for documentation comments in the Dart programming language. Its goal is to ensure that documentation is parsed consistently by all tools and to serve as a definitive standard for developers and tool authors.
In Scope
This specification covers the following topics:
Out of Scope
This document does not cover:
dartdoc, analysis server).@template, @canonicalFor).MyClass, myMethod, prefix).[MyClass] or [MyClass.myMethod]) that links to an element.A line-based doc comment is a comment that starts with ///. One or more consecutive lines that begin with /// form a doc comment block.
The block continues even if interrupted by single-line non-doc comments (lines starting with //) or by blank lines. The interrupting lines are ignored when extracting documentation text.
For each line that begins with ///, the parser removes the three slashes and all leading whitespace to produce the documentation text. Exception: inside fenced code blocks (```), whitespace after the leading /// is preserved to maintain code formatting.
Example
/// This line has leading whitespace after the slashes. // This regular comment is ignored. /// /// The line above is a rendered blank line (created by a `///` alone). void myFunction() {}
The extracted documentation text from the example above would be:
This line has leading whitespace after the slashes. This line above is a rendered blank line (created by a `///` alone).
Example
/// ```dart
/// void main() {
/// print('Hello, World!');
/// }
/// ```
void anotherFunction() {}
The extracted documentation text from the example above would be:
void main() {
print('Hello, World!');
}
A block-based doc comment is a comment that starts with /** and ends with the matching */.
Block comments may contain nested comment sequences (for example: /** outer /* inner */ outer */). Documentation tools must respect nesting and ensure that the comment block only ends at the closing */ that matches the opening delimiter.
The content within the delimiters is treated as the documentation. On each line after the opening /**, any leading whitespace followed by a single asterisk (*) is considered stylistic and is stripped by doc generators. Any whitespace immediately following the asterisk is also stripped, if present.
Example
/** * * */
The text within a documentation comment block is parsed as GitHub Flavored Markdown (GFM), an extension of CommonMark, allowing for rich text formatting. This includes headings, lists, code blocks, tables, and emphasis, which are converted for instance to HTML in the generated documentation.
A reference is a special directive within the Markdown content that creates a hyperlink to a Dart element. It is written by enclosing a name in square brackets (e.g., [foo]). See Section 4 for detailed information about which elements can be referenced.
Conceptually, these behave like reference-style links in Markdown. The documentation generator resolves the name against the available source code to create the link's destination. See Section 5 for detailed resolution rules.
It is important to distinguish references from other link syntaxes in GFM. Documentation comment references use the shorthand reference link syntax ([name]) for their own purpose. Other forms of Markdown links are not treated as references and are parsed as standard Markdown. This includes:
[A link](https://www.example.com)[A link][id] (where id is a defined link reference [id]:https://www.example.com "title")[^1]Only a standalone [name] that does not correspond to a defined link reference in the Markdown document is treated as a reference to a Dart element.
Doc comments are associated with the declaration that immediately follows them. They are only considered valid when placed directly before the following types of declarations:
library directives.classmixinenumextensionextension typetypedefWhile not strictly disallowed by the language, any other placement of a comment with the /// syntax, is not considered a doc comment, and hence should be ignored by documentation tools.
Note on Metadata Annotations: Doc comments can be placed either before or after metadata annotations (e.g., @override, @deprecated). If doc comments appear in both locations, the doc comment closest to the declaration (after the annotation) takes precedence. The comment before the annotations is ignored, and documentation tools should issue a warning. For example:
/// This doc comment is ignored because there is another one after the /// annotation. @override /// This doc comment takes precedence because it is closer to the declaration. void foo(int s) {}
A reference in a doc comment (e.g., [name]) can link to any Dart element that is visible from the Dart scope of the documented element. See Section 5 for more details about scoping. This includes:
[MyClass])[MyMixin])[MyEnum])[MyExtension])[MyExtensionType])[MyTypedef])[myTopLevelFunction])[myTopLevelVar])[myMethod], [MyClass.myMethod])[myField], [MyClass.myField])[MyClass.new], [MyClass.named])[MyEnum.value])[parameterName])[T])[field])[param])When a name is enclosed in square brackets (e.g., [MyClass.myMethod]), documentation tools attempt to resolve it to a specific API member. This section details the principles of that resolution process, the scope hierarchy it follows, and how to handle special cases.
Resolution Follows Scope Hierarchy: Lookup begins relative to the documented element and proceeds outwards through the well-defined Dart scope precedence hierarchy (see Section 5.2). The first valid match found in this search is used.
Disambiguation via Qualification: To prevent ambiguity or to reference an element from a distant scope, a reference should be qualified. This is done by prefixing the name with a class name (e.g., [ClassName.memberName]) or an import prefix (e.g., [prefix.elementName]).
Handling Ambiguity: If a reference could resolve to multiple declarations within the same scope, it should not resolve to any element. If no resolution is found after checking all scopes, documentation tools should issue a warning.
The resolution process for a reference [name] follows the standard Dart scope of the documented element with the extension of the doc imported scope at the end. The resolution starts with the first identifier of a name. Search is done in a specific order of precedence from the narrowest (most local) scope to the broadest (globally available).
The hierarchy is searched from the inside out. Below is an example for an instance method:
+--------------------------------------------------------------------------+ | 7. Doc Imported Scope (documentation-only imports with @docImport ) | | +----------------------------------------------------------------------+ | | | 6. Imported Scopes (all 'import' directives + implicit 'dart:core') | | | | +------------------------------------------------------------------+ | | | | | 5. Library Scope (all declarations incl. prefixes in the file). | | | | | | +--------------------------------------------------------------+ | | | | | | | 4. Class Type Parameter Scope (e.g., <T>) | | | | | | | | +----------------------------------------------------------+ | | | | | | | | | 3. Class Member Scope (e.g., static/instance members) | | | | | | | | | | +------------------------------------------------------+ | | | | | | | | | | | 2. Method Type Parameter Scope (e.g., <R>) | | | | | | | | | | | | +--------------------------------------------------+ | | | | | | | | | | | | | 1. Formal Parameter Scope (e.g., parameter names)| | | | | | | | | | | | | +--------------------------------------------------+ | | | | | | | | | | | +------------------------------------------------------+ | | | | | | | | | +----------------------------------------------------------+ | | | | | | | +--------------------------------------------------------------+ | | | | | +------------------------------------------------------------------+ | | | +----------------------------------------------------------------------+ | +--------------------------------------------------------------------------+
The lookup process begins at a specific “starting scope” determined by the context of the doc comment and then follows the scope hierarchy.
The following sections detail the starting scope for each type of declaration.
This applies to doc comments on methods, constructors, and operators within a class, enum, mixin, extension, or extension type. The search begins with the method's own parameters.
Example
/// @docImport 'dart:math'; import 'dart:convert'; class AnotherClass {} class MyClass<T> { T? value; /// A method. /// /// Lookup examples: /// * [param]: Resolves to the parameter (Formal Parameter Scope). /// * [R]: Resolves to the method type parameter (Method Type Parameter Scope). /// * [value]: Resolves to the class member (Class Member Scope). /// * [myStaticMethod]: Resolves to the static class member (Class Member Scope). /// * [myMethod]: Resolves to this method itself (Class Member Scope). /// * [T]: Resolves to the class type parameter (Class Type Parameter Scope). /// * [MyClass]: Resolves to the parent class (Library Scope). /// * [AnotherClass]: Resolves to the library member (Library Scope). /// * [List]: Resolves from 'dart:core' (Imported Scopes). /// * [JsonCodec]: Resolves from 'dart:convert' (Imported Scope). /// * [Random]: Resolves from 'dart:math' (Doc Imported Scope). /// * [named] is not in scope (constructors are not in Class Member Scope) void myMethod<R>(T param) { // ... } /// A non-redirecting generative constructor. /// /// Lookup examples: /// * [value]: Resolves to the parameter (Formal Parameter Scope). /// * [param]: Resolves to the parameter (Formal Parameter Scope). /// * [named]: Resolves to this constructor itself (Class Member Scope). MyClass.named(this.value, int param); static void myStaticMethod() { // ... } }
This applies to doc comments on fields within a class, enum, or mixin. Since fields have no parameters, the search starts at the member level, but otherwise follows the same route as instance methods.
A top-level function operates like a method but without any enclosing class-like scopes.
For top-level variables, the search has no local context and must begin at the file's library scope.
For doc comments placed directly on classes, enums, mixins, extensions, extension types.
Example
/// @docImport 'dart:math';
/// A top-level function.
///
/// Lookup examples:
/// * [input]: Resolves to the parameter (Formal Parameter Scope).
/// * [R]: Resolves to the function type parameter (Function Type Parameter Scope).
/// * [globalConstant]: Resolves to the library member (Library Scope).
/// * [topLevelFunction]: Resolves to the function itself (Library Scope).
/// * [String]: Resolves from 'dart:core'(Imported Scope).
R topLevelFunction<R>(String input) {
// ...
}
/// A top-level variable.
///
/// Lookup examples:
/// * [topLevelFunction]: Resolves to the library member (Library Scope).
/// * [globalConstant]: Resolves to the variable itself (Library Scope).
/// * [Duration]: Resolves from 'dart:core' (Imported Scope).
/// * [Random]: Resolves from 'dart:math' (Doc Imported Scope).
const int globalConstant = 10;
/// A class declaration.
///
/// Lookup examples:
/// * [myMethod]: Resolves to the class member (Class Member Scope).
/// * [T]: Resolves to the class type parameter (Class Type Parameter Scope).
/// * [topLevelFunction]: Resolves to the library member (Library Scope).
/// * [MyClass]: Resolves to the class itself (Library Scope).
/// * [List]: Resolves from 'dart:core' (Imported Scopes).
/// * [Random]: Resolves from 'dart:math' (Doc Imported Scope).
class MyClass<T> {
/// A class member.
void myMethod() {}
}
The starting scope for a typedef doc comment depends on what it is an alias for. For function and record types, the scope starts at the parameter/field level. For all other types, it starts at the typedef's own type parameter scope.
| Typedef Type | Starting Scope |
|---|---|
| Function Type | Formal parameter scope |
| Record Type | Record fields scope |
| All Other Types | Typedef type parameter scope |
Example
/// @docImport 'dart:math';
class AnotherClass {}
/// A typedef for a Map.
///
/// Lookup examples:
/// * [T]: Resolves to the typedef type parameter (Typedef Type Parameter Scope).
/// * [JsonMap]: Resolves to the typedef itself (Library Scope).
/// * [AnotherClass]: Resolves to the library member (Library Scope).
/// * [Map]: Resolves from 'dart:core' (Imported Scope).
/// * [String]: Resolves from 'dart:core' (Imported Scope).
/// * [Random]: Resolves from 'dart:math' (Doc Imported Scope).
typedef JsonMap<T> = Map<String, T>;
/// A record typedef.
///
/// [a] : Resolves to record field
typedef MyRecord = ({int a, bool b});
/// A function type typedef.
///
/// [p] : Resolves to parameter
typedef MySecondTypedef<T> = void Function(int p);
/// A function type typedef (old syntax).
///
/// [p] : Resolves to parameter
typedef void MyTypedef<T>(int p);
/// A function type typedef with nested function functions.
///
/// [fun] : Resolves to parameter
/// [p] : is not in scope
typedef F<T> = void Function(void Function<S>(void Function(T p)) fun);
Note on Nested Scopes: The lookup only applies to the immediate parameters or fields of the typedef. It does not extend into parameters of nested function types.
When a reference contains a qualified name (e.g., [prefix.ClassName.member]), the resolution process is an iterative, left-to-right evaluation of each identifier.
The tool first resolves the first identifier in the qualified name (e.g., prefix) using the standard lookup process (Section 5.3), starting from the doc comment's current scope.
Once an identifier is resolved, the element it resolves to establishes a new “namespace” for resolving the next identifier in the chain. This process repeats until the entire qualified name is resolved.
The namespace available depends on the type of element the previous identifier resolved to. Below are the primary cases.
Namespace-Providing Elements
If an Identifier resolves to one of the following elements, it establishes a new namespace for resolving the next identifier in the chain.
Case 1: Import Prefix
[math.pi], the identifier math resolves to an import prefix (e.g., from import 'dart:math' as math;). The tool then searches the prefixed import namespace for the identifier pi.import 'dart:math' as math show sin;, the namespace for math would only contain sin. A reference to [math.pi] would fail to resolve, as pi was not included in the show list.Case 2: Class-like top-level declaration
Namespace: The set of all members accesible on that element, including:
Notes on Generics:
MyClass<T>, a reference like [MyClass.T] is invalid because T is not a member of MyClass's namespace.[List.filled], not [List<int>.filled]).Example: To resolve the reference [collection.BoolList.empty]:
collection resolves to an import prefix.BoolList resolves to a class element within the collection library's public namespace.empty resolves to a named constructor element within the BoolList class's member namespace.Leaf Elements (Empty Namespace)
The following elements are “leaf” nodes. If an Identifier resolves to one of these, it provides no further namespace, and the chain of resolution must end.
Case 3: Function, or Method
myFunction<T>(int param) is a top-level function, references like [myFunction.param] or [myFunction.T] are invalid.Case 4: Variable, Field, or Formal Parameter
myField is a class field, a Reference like [myField.something] is invalid. Similarly, if param is a method parameter, [param.something] is also invalid.Case 5: Type Parameter
MyClass<T>) is a “leaf” element representing a type. It does not provide a namespace for further resolution.T resolves to a type parameter, which has no member namespace.Constructors: To refer to a named constructor, the syntax is [ClassName.constructorName]. Referencing the default, unnamed constructor requires the special syntax [ClassName.new].
Constructor vs. Member Conflicts A naming conflict can occur if a class declares a named constructor (e.g., MyClass.foo) and also contains an instance member with the same name (e.g., a method foo() or a field foo).
[foo]): There is no ambiguity. A reference to [foo] always resolves to the member. A named constructor cannot be referenced by its name alone, it is not in scope as a simple identifier.[MyClass.foo]): This syntax is ambiguous because it could validly refer to either the named constructor or the member (via the class namespace). In this specific conflict scenario, [MyClass.foo] resolves to the named constructor. It is recommended that documentation tools issue a warning when this shadowing occurs. To reference the member explicitly in this context, users should be advised to use the unqualified [foo] if within the class context.Example
class A {
/// A named constructor.
A.foo();
/// An instance method.
void foo() {}
/// Usage in documentation:
/// * [foo] -> Resolves to the method foo().
/// * [A.foo] -> Resolves to the constructor A.foo().
}
Getters and Setters: A reference to a property name (e.g., [value]) resolves to the conceptual property rather than the individual getter or setter. See full discussion in Section 6.2.2.
Shadowing: A name in an inner scope (like a [parameterName]) “shadows” a name in an outer scope (like a class field). The shadowed outer element must be qualified to be referenced, e.g., [MyClass.fieldName].
While the placement rules in Section 3 define where a doc comment must be written, this section defines how documentation tools should associate a specific documentation block with a given code element, especially in cases where the association isn't direct.
The documentation for a given Dart element is, by default, the single doc comment that immediately precedes its declaration in the source code.
In some cases, the effective documentation for an element is determined by context, inheritance, or combination rules rather than by a direct preceding comment.
When a member overrides an ancestor, its documentation is determined by a set of inheritance and precedence rules.
Explicit Documentation: If an overriding member does have its own doc comment, that comment takes full precedence. The documentation from any base members is ignored.
Unambiguous Documentation Inheritance: If a member overrides a member from an ancestor and does not have its own doc comment, it inherits the documentation from the base member. This rule applies when the source of the documentation is unambiguous (e.g., overriding a single method). Documentation tools should display this inherited comment, ideally noting its origin (e.g., “Copied from BaseClass”).
Ambiguous Documentation from Multiple Ancestors: The behavior for resolving documentation when a member inherits conflicting documentation from multiple ancestors is reserved for future standardization. Currently, tools may handle this case at their discretion, or ignore inherited documentation in ambiguous scenarios. It is recommended that tools issue a warning when this scenario is encountered, prompting the user to provide explicit documentation to resolve the ambiguity.
A getter and a setter that share the same name are treated as a single conceptual property rather than the individual getter or setter.
Documentation tools should handle this property with the following rules:
[value]) resolves to the conceptual property rather than the individual getter or setter. This applies in all cases where a getter and setter share the same name, including:get value) and setter (set value(...)) are declared.Parameters of functions, methods, and constructors do not have their own preceding doc comments. They are documented within the doc comment of their enclosing element and referenced using an element reference [p].