Skip to content

Introduce a syntax for importing elements just for doc comment references #50702

Closed
@srawlins

Description

@srawlins

This is an alternative proposal to #49073. This proposal improves upon the readability and perhaps the simplicity of that proposal. It is based on @lrhn 's brief comment on that proposal.

BACKGROUND

Audience

Anyone writing documented Dart code.

Glossary

  • Doc comment (or dartdoc comment or documentation comment) - a Dart code comment in which each line starts with three slashes (///), and which is placed just before a library element (class, top-level function, top-level constant, etc.) or a class/mixin/enum/extension element (method, field, etc.). A doc comment is parsed (by various tools) as Markdown text.
  • Doc comment reference - the name of any element wrapped in square brackets, inside a doc comment. This is typically one identifier or two separated by a period, like [List] or [Future.wait].
  • Top-level namespace - a set of top-level elements defined by one or more import directives and the current library, which share a common prefix. A library has a set of one or more top-level namespaces.
  • Un-prefixed namespace - the unambiguous union of all elements provided by all of the un-prefixed import directives (no name collisions of non-identical elements are allowed) of a library, plus all elements declared in the library (which may shadow any imported elements).
  • Prefixed namespace - the unambiguous union of all elements provided by all of the import directives which share a common (non-empty) prefix (no name collisions of non-identical elements are allowed).
  • Dart URI resolution - the strategy for resolving a URI string into a path in order to locate a Dart library (or part) file.
  • A Dart library’s export namespace - the collection of Dart elements exported by a library, which includes all public library elements declared in a library (including parts) and all elements exported by the export directives in the library (taking into account any show and hide combinators).

OVERVIEW

The basic design is to add a new annotation, perhaps to the meta package, @docImport which looks similar to an import directive. When a library is annotated with this annotation, the analyzer will add elements to one of a library's top-level namespace, just like an import directive. Doc comment references can then resolve to elements provided by such "doc imports". Quick examples:

@docImport('dart:async')
@docImport('package:flutter/element.dart', show: ['Element'])
@docImport('../path/to/somwhere.dart')
@docImport('dart:html', as: 'html')
library;

/// We can now reference [Future] from dart:async, [Element] from Flutter's element library,
/// and [html.Element] from dart:html, even if none of these libraries are actually imported
/// by this library.
class Foo {}

DETAILED DESIGN/DISCUSSION

A class is introduced which can be used as an annotation:

class docImport {
  final String uri;
  final String? prefix;
  final Set<String> shownNames;
  final Set<String> hiddenNames;
  const docImport(this.uri, {
    String? as,
    Set<String> show,
    Set<String> hide,
  }) :
      this.prefix = as,
      this.shownNames = show
      this.hiddenNames = hide;
}

Each docImport annotating a library directive introduces a doc import which contributes to a new doc namespace.

A library currently has one or more top-level namespaces: the un-prefixed namespace and zero or more prefixed namespaces, which result from import directive(s) with prefixes.

Each identifier in a library (both in code and in doc comment references) is resolved according to various scoping rules, which may resolve to a non-top-level identifier, or a top-level identifier from one of the top-level namespaces. (This is a simplification since a prefix itself is an identifier which needs resolution, and which can also be shadowed by a non-top-level identifier.)

This change introduces a second set of namespaces used only for the resolution of doc comments: the "doc namespaces." The doc namespaces are a superset of the regular namespaces, and each doc namespace is a superset of the corresponding regular namespace (with the same prefix).

For a given library, the doc namespace with prefix p is computed via the following steps:

  1. Start with an empty set of elements.
  2. For each import with prefix p:
    1. For each element provided by the import, according to the rules of 'show' and 'hide':
      • If there is an element with the same name already in the namespace, report a compile-time error.
      • Otherwise, add the element to the namespace.
  3. For each @docImport annotation specified with prefix p:
    1. For each element provided by the import, according to the rules of 'show' and 'hide':
      • If there is an element with the same name already in the namespace, report a static warning.
      • Otherwise, add the element to the namespace.
  4. If this is the un-prefixed doc namespace, then for each top-level element declared in the library:
    1. Add the element to the namespace, replacing any element currently in the namespace with the same name (shadowing it).

(The "regular" namespaces are computed in the exact same way, excluding step 3.)

Each doc comment reference is then resolved using the standard scoping rules, and using the doc namespaces as the set of top-level namespaces.

New static warnings are reported in the following situations:

  • when the URI cannot be resolved, according to the URI-resolving rules
  • when elements from a doc import have name conflicts with each other, or elements from imports with the same prefix
  • when a prefix name does not match certain identifier rules
  • when a show or hide clause includes names of elements which are not found in the library's export namespace.
  • when a doc import is redundant, duplicate, or unnecessary (various subtle definitions) with a regular import or another doc import (lower priority)
  • when a doc import is unused (lower priority)
  • when an element in a show or hide clause of a doc import is unused (lower priority)

ACCESSIBILITY

I am not aware of any accessibility considerations.

INTERNATIONALIZATION

I am not aware of any internationalization considerations.

OPEN QUESTIONS

  1. Can we define the docImport class and constructor in dart:core? This would be required if we want to use docImport functionality in the Flutter engine, which cannot import package:meta.

  2. If not, should we instead support this feature as some sort of directive in a doc comment? For example, instead of the annotations in the OVERVIEW section above, we could instead support:

    /// @doc import 'dart:async';
    /// @doc import 'package:flutter/element.dart' show Element;
    /// @doc import '../path/to/somwhere.dart';
    /// @doc import 'dart:html' as html;
    library;

    This solution has some significant drawbacks:

    • It would be much more complicated to parse all of this syntax in doc comments, making it error-prone.
    • For all of the error cases of the parsing, we'd have to report errors, as long as it really looked like a doc import was intended.
    • We wouldn't get any syntax highlighting for free. That would also have to be implemented, and implemented once for each syntax highlighter we try to keep up (DAS, highlight.js, CodeMirror, Kythe, ...)

TESTING PLAN

Support for this syntax will be tested in:

  • analyzer's unit tests
  • analysis server’s navigation tests, hover tests, etc.
  • dartdoc’s reference unit tests

DOCUMENTATION PLAN

The support for this feature can be documented on the docImport class.

MIGRATION PLAN

If a developer has wanted to reference an out-of-scope element, there are a few strategies they may have ultimately chosen:

  • Use backticks instead. For example, /// See `List` . For this case, we can offer a lint and a quick fix, "this could be a doc comment reference." (This could have many false positives.)
  • Import the element, making it now in-scope, just to be able to reference it. In this case, we could report an "import only used for comment references" lint, with a fix which removes the import and adds a corresponding doc import.
  • Ignore the fact that the IDE won't cross-link, rely on dartdoc's ability to find far-flung elements. For this case, dartdoc will start reporting a warning that such an element will not be discoverable with the current, ambiguous, syntax, and will suggest a doc import. A shell script which parses such warnings could act as a migration tool.

This addresses #44637.

Metadata

Metadata

Assignees

Labels

P3A lower priority bug or feature requestarea-core-librarySDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries.legacy-area-analyzerUse area-devexp instead.library-coretype-design

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions