| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// Various classes for data computed for doc comments. |
| library; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:meta/meta.dart'; |
| |
| /// A block doc directive, denoted by an opening tag, and a closing tag. |
| /// |
| /// The text in between the two tags is not explicitly called out. It can be |
| /// read from the original compilation unit, between the offsets of the opening |
| /// and closing tags. |
| @experimental |
| final class BlockDocDirective implements DocDirective { |
| final DocDirectiveTag openingTag; |
| final DocDirectiveTag? closingTag; |
| |
| BlockDocDirective(this.openingTag, this.closingTag); |
| |
| @override |
| DocDirectiveType get type => openingTag.type; |
| } |
| |
| /// The type of Markdown code block in a documentation comment. |
| @experimental |
| enum CodeBlockType { |
| /// Fenced code blocks begin with a code fence, preceded by up to three spaces |
| /// of indentation. |
| /// |
| /// A code fence is a sequence of at least three consecutive backtick |
| /// characters (`) or tildes (~). |
| fenced, |
| |
| /// Indented code blocks have every line of code preceded by four or more |
| /// spaces of indentation. |
| indented, |
| } |
| |
| /// An instance of a [DocDirectiveType] in the text of a doc comment, either |
| /// as a [SimpleDocDirective], represented by a single [DocDirectiveTag], or a |
| /// [BlockDocDirective], represented by an opening [DocDirectiveTag] and a |
| /// closing one (in well-formed text). |
| @experimental |
| sealed class DocDirective { |
| DocDirectiveType get type; |
| } |
| |
| /// An argument in a doc directive. See [DocDirective] for their syntax. |
| @experimental |
| sealed class DocDirectiveArgument { |
| /// The offset of the start of the argument, from the beginning of the |
| /// compilation unit. |
| final int offset; |
| |
| /// The offset just after the end of the argument, from the beginning of the |
| /// compilation unit. |
| final int end; |
| |
| /// The value of the argument. |
| final String value; |
| |
| DocDirectiveArgument({ |
| required this.offset, |
| required this.end, |
| required this.value, |
| }); |
| } |
| |
| /// A named argument in a doc directive. See [DocDirective] for their syntax. |
| @experimental |
| final class DocDirectiveNamedArgument extends DocDirectiveArgument { |
| /// The name of the argument. |
| final String name; |
| |
| DocDirectiveNamedArgument({ |
| required super.offset, |
| required super.end, |
| required this.name, |
| required super.value, |
| }); |
| } |
| |
| /// A parameter in a doc directive, with it's expected format, if it has one. |
| @experimental |
| final class DocDirectiveParameter { |
| final String name; |
| final DocDirectiveParameterFormat expectedFormat; |
| |
| const DocDirectiveParameter(this.name, this.expectedFormat); |
| } |
| |
| /// The expected format of a doc directive parameter, which indicates some |
| /// minimal validation that can produce diagnostics. |
| @experimental |
| enum DocDirectiveParameterFormat { |
| /// A format indicating that arguments are not validated. |
| any('any'), |
| |
| /// A format indicating that an argument must be parsable as an integer. |
| integer('an integer'), |
| |
| /// A format indicating that an argument must be parsable as a URI. |
| uri('a URI'), |
| |
| /// A format indicating that an argument must be parsable as a URI, and be in |
| /// the format of a YouTube video URL. |
| youtubeUrl("a YouTube URL, starting with '$youtubeUrlPrefix'"); |
| |
| static const youtubeUrlPrefix = 'https://www.youtube.com/watch?v='; |
| |
| final String displayString; |
| |
| const DocDirectiveParameterFormat(this.displayString); |
| } |
| |
| /// A positional argument in a doc directive. See [DocDirective] for their |
| /// syntax. |
| @experimental |
| final class DocDirectivePositionalArgument extends DocDirectiveArgument { |
| DocDirectivePositionalArgument({ |
| required super.offset, |
| required super.end, |
| required super.value, |
| }); |
| } |
| |
| /// A documentation directive, found in a doc comment. |
| /// |
| /// Documentation directives are declared with `{@` at the start of a line of a |
| /// documentation comment, followed the name of a doc directive, arguments, and |
| /// finally a right curly brace (`}`). |
| /// |
| /// Arguments are separated from the directive name, and from each other, by |
| /// whitespace. There are two types of arguments: positional and named. Named |
| /// arguments are written as `NAME=VALUE`, without any internal whitespace. |
| /// Named arguments can be optional. |
| @experimental |
| final class DocDirectiveTag { |
| /// The offset of the starting text; for example: '@animation'. |
| final int offset; |
| final int end; |
| final int nameOffset; |
| final int nameEnd; |
| |
| final DocDirectiveType type; |
| |
| final List<DocDirectiveArgument> positionalArguments; |
| final List<DocDirectiveNamedArgument> namedArguments; |
| |
| DocDirectiveTag({ |
| required this.offset, |
| required this.end, |
| required this.nameOffset, |
| required this.nameEnd, |
| required this.type, |
| required this.positionalArguments, |
| required this.namedArguments, |
| }); |
| } |
| |
| @experimental |
| enum DocDirectiveType { |
| /// A [DocDirective] declaring an embedded video with HTML video controls. |
| /// |
| /// This directive has three required arguments: the width, the height, and |
| /// the URL. A named 'id' argument can also be given. For example: |
| /// |
| /// ```none |
| /// {@animation 600 400 https://www.example.com/example.mp4 id=video1} |
| /// ``` |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#animation---animations>. |
| animation( |
| 'animation', |
| positionalParameters: [ |
| DocDirectiveParameter('width', DocDirectiveParameterFormat.integer), |
| DocDirectiveParameter('height', DocDirectiveParameterFormat.integer), |
| DocDirectiveParameter('url', DocDirectiveParameterFormat.uri), |
| ], |
| namedParameters: [ |
| DocDirectiveParameter('id', DocDirectiveParameterFormat.any), |
| ], |
| ), |
| |
| /// A [DocDirective] declaring the associated library is the "canonical" |
| /// location for a certain element. |
| /// |
| /// Dartdoc uses some heuristics to decide what the public-facing libraries |
| /// are, and which public-facing library is the "canonical" location for an |
| /// element. When that heuristic needs to be overridden, a user can use this |
| /// directive. Example: |
| /// |
| /// ```none |
| /// {@canonicalFor some_library.SomeClass} |
| /// ``` |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#canonicalfor---canonicalization>. |
| canonicalFor( |
| // TODO(srawlins): We have mostly used 'kebab-case' in directive names. This |
| // directive name is a rare departure from that style. Migrate users to use |
| // 'canonical-for'. |
| 'canonicalFor', |
| positionalParameters: [ |
| DocDirectiveParameter('element', DocDirectiveParameterFormat.any), |
| ], |
| ), |
| |
| /// A [DocDirective] declaring a categorization into a named category. |
| /// |
| /// This directive has one required argument: the category name. The category |
| /// name is allowed to contain whitespace. |
| // TODO(srawlins): I think allowing a category name, which is parsed as |
| // multiple positional arguments (or I guess named arguments if one contains) |
| // an equal sign!) is too loosy-goosey. We should just support quoting and |
| // require that a category name with spaces be wrapped in quotes. |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#category-and-subcategory---categories>. |
| category('category', restParametersAllowed: true), |
| |
| /// The end tag for the [DocDirectiveType.injectHtml] tag. |
| /// |
| /// This tag should not really constitute a "type" of doc directive, but this |
| /// implementation is a one-to-one mapping of "types" and "tags", so end tags |
| /// are included. This also allows us to parse (erroneous) dangling end tags. |
| endInjectHtml.end('end-inject-html', openingTag: 'inject-html'), |
| |
| /// The end tag for the [DocDirectiveType.tool] tag. |
| /// |
| /// This tag should not really constitute a "type" of doc directive, but this |
| /// implementation is a one-to-one mapping of "types" and "tags", so end tags |
| /// are included. This also allows us to parse (erroneous) dangling end tags. |
| endTool.end('end-tool', openingTag: 'tool'), |
| |
| /// The end tag for the [DocDirectiveType.template] tag. |
| /// |
| /// This tag should not really constitute a "type" of doc directive, but this |
| /// implementation is a one-to-one mapping of "types" and "tags", so end tags |
| /// are included. This also allows us to parse (erroneous) dangling end tags. |
| endTemplate.end('endtemplate', openingTag: 'template'), |
| |
| /// A [DocDirective] declaring a block of HTML content which is to be inserted |
| /// after all other processing, including Markdown parsing. |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#inject-html---injected-html>. |
| injectHtml.block('inject-html', 'end-inject-html'), |
| |
| /// A [DocDirective] declaring a macro application. |
| /// |
| /// This directive has one required argument: the name. For example: |
| /// |
| /// ```none |
| /// {@macro some-macro} |
| /// ``` |
| macro( |
| 'macro', |
| positionalParameters: [ |
| DocDirectiveParameter('name', DocDirectiveParameterFormat.any), |
| ], |
| ), |
| |
| /// A [DocDirective] declaring a categorization into a named sub-category. |
| /// |
| /// This directive has one required argument: the sub-category name. The |
| /// sub-category name is allowed to contain whitespace. |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#category-and-subcategory---categories>. |
| subCategory( |
| // TODO(srawlins): We have mostly used 'kebab-case' in directive names. This |
| // directive name is the sole departure from that style. Migrate users to |
| // use 'sub-category'. |
| 'subCategory', |
| restParametersAllowed: true, |
| ), |
| |
| /// A [DocDirective] declaring a template of text which can be applied to |
| /// other doc comments with a macro. |
| /// |
| /// A template can contain any recognized doc comment content between the |
| /// opening and closing tags, like Markdown text, comment references, and |
| /// simple doc directives. |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#template-and-macro---templates-and-macros>. |
| // TODO(srawlins): Migrate users to use 'end-template'. |
| template.block( |
| 'template', |
| 'endtemplate', |
| positionalParameters: [ |
| DocDirectiveParameter('name', DocDirectiveParameterFormat.any), |
| ], |
| ), |
| |
| /// A [DocDirective] declaring a tool. |
| /// |
| /// A tool directive invokes an external tool, with the text between the |
| /// opening and closing tags as stdin, and replaces the directive with the |
| /// output of the tool. |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#tool---external-tools>. |
| tool.block( |
| 'tool', |
| 'end-tool', |
| positionalParameters: [ |
| DocDirectiveParameter('name', DocDirectiveParameterFormat.any), |
| ], |
| restParametersAllowed: true, |
| ), |
| |
| /// A [DocDirective] declaring an embedded YouTube video. |
| /// |
| /// This directive has three required arguments: the width, the height, and |
| /// the URL. For example: |
| /// |
| /// ``` |
| /// {@youtube 600 400 https://www.youtube.com/watch?v=abc123} |
| /// ``` |
| /// |
| /// See documentation at |
| /// <https://github.com/dart-lang/dartdoc/blob/main/doc/directives.md#youtube---youtube-videos>. |
| youtube( |
| 'youtube', |
| positionalParameters: [ |
| DocDirectiveParameter('width', DocDirectiveParameterFormat.integer), |
| DocDirectiveParameter('height', DocDirectiveParameterFormat.integer), |
| DocDirectiveParameter('url', DocDirectiveParameterFormat.youtubeUrl), |
| ], |
| ); |
| |
| /// Whether this starts a block directive, which must be closed by a specific |
| /// closing directive. |
| /// |
| /// For example, the 'inject-html' directive begins with `{@inject-html}` and |
| /// ends with `{@end-inject-html}`. |
| final bool isBlock; |
| |
| /// The name of the directive, as written in a doc comment. |
| final String name; |
| |
| /// The name of the directive that ends this one, in the case of a block |
| /// directive's opening tag, the name of the directive that starts this one, |
| /// in the case of a block directive's closing tag, and `null` otherwise. |
| final String? opposingName; |
| |
| /// The positional parameters, which are each required. |
| final List<DocDirectiveParameter> positionalParameters; |
| |
| /// The named parameters, which are each optional. |
| final List<DocDirectiveParameter> namedParameters; |
| |
| /// Whether "rest" parameters are allowed. |
| /// |
| /// In such a doc directive type, we do not enforce a maximum number of |
| /// arguments. |
| final bool restParametersAllowed; |
| |
| const DocDirectiveType( |
| this.name, { |
| this.positionalParameters = const <DocDirectiveParameter>[], |
| this.namedParameters = const <DocDirectiveParameter>[], |
| this.restParametersAllowed = false, |
| }) : isBlock = false, |
| opposingName = null; |
| |
| const DocDirectiveType.block( |
| this.name, |
| this.opposingName, { |
| this.positionalParameters = const <DocDirectiveParameter>[], |
| this.restParametersAllowed = false, |
| }) : isBlock = true, |
| namedParameters = const <DocDirectiveParameter>[]; |
| |
| const DocDirectiveType.end(this.name, {required String openingTag}) |
| : opposingName = openingTag, |
| isBlock = false, |
| positionalParameters = const <DocDirectiveParameter>[], |
| namedParameters = const <DocDirectiveParameter>[], |
| restParametersAllowed = false; |
| } |
| |
| /// A documentation import, found in a doc comment. |
| /// |
| /// Documentation imports are declared with `@docImport` at the start of a line |
| /// of a documentation comment, followed by regular import elements (URI, |
| /// optional prefix, optional combinators), ending with a semicolon. |
| @experimental |
| final class DocImport { |
| /// The offset of the starting text, '@docImport'. |
| int offset; |
| |
| ImportDirective import; |
| |
| DocImport({required this.offset, required this.import}); |
| } |
| |
| /// A Markdown fenced code block found in a documentation comment. |
| @experimental |
| final class MdCodeBlock { |
| /// The 'info string'. |
| /// |
| /// This includes any text (trimming whitespace) following the opening |
| /// backticks (for a fenced code block). For example, in a fenced code block |
| /// starting with "```dart", the info string is "dart". |
| /// |
| /// If the code block is an indented code block, or a fenced code block with |
| /// no text following the opening backticks, the info string is `null`. |
| /// |
| /// See CommonMark specification at |
| /// <https://spec.commonmark.org/0.30/#fenced-code-blocks>. |
| final String? infoString; |
| |
| /// Information about the comment lines that make up this code block. |
| /// |
| /// For a fenced code block, these lines include the opening and closing |
| /// fence delimiter lines. |
| final List<MdCodeBlockLine> lines; |
| |
| /// The type of Markdown code block that is being represented. |
| final CodeBlockType type; |
| |
| MdCodeBlock({ |
| required this.infoString, |
| required List<MdCodeBlockLine> lines, |
| required this.type, |
| }) : lines = List.of(lines, growable: false); |
| } |
| |
| /// A Markdown code block line found in a documentation comment. |
| @experimental |
| final class MdCodeBlockLine { |
| /// The offset of the start of the code block, from the beginning of the |
| /// compilation unit. |
| final int offset; |
| |
| /// The length of the fenced code block. |
| final int length; |
| |
| MdCodeBlockLine({required this.offset, required this.length}); |
| } |
| |
| @experimental |
| final class SimpleDocDirective implements DocDirective { |
| final DocDirectiveTag tag; |
| |
| SimpleDocDirective(this.tag); |
| |
| @override |
| DocDirectiveType get type => tag.type; |
| } |