diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a8acd172d..c90edd23a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.2.2-dev +* Add a hideImplementations directive to disable showing constant + implementations alongside one-line docs on class pages (#3398) + ## 6.2.2 * Add chips to class pages for class modifiers (#3401) * Fix a case where dartdoc was not properly handling extension diff --git a/README.md b/README.md index b62b94dacc..8a01f4c81e 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,41 @@ markdown link isn't linked). It's best to only inject HTML that is self-contained and doesn't depend upon other elements on the page, since those may change in future versions of Dartdoc. +### Skipping constant rendering with one-line docs + +For some classes or libraries full of well-documented constants, showing the +implementation on the enclosing `class` or `library` page can be distracting +or even misleading. To prevent the rendering of constant implementations, +place the `{@hideConstantImplementations}` in the documentation comment for +the enclosing context where the constant is defined. For members of a class, +place the directive in the class documentation where the constants are defined. +For top level constants, place the directive in the library where the constants +are defined. + +For example: + +```dart +/// This is truly an amazing library. +/// {@hideConstantImplementations} +library my_library; + +/// This top level constant will not show its implementation. +const a = 7; + +/// {@hideConstantImplementations} +class A { + /// This constant will not show its implementation. + static const aConst = 12; +} + +class B { + /// Despite the library directive, because this is a class + /// member and there is no hideConstantImplementations + /// directive on the class, we will show this implementation. + static const bConst = 27; +} +``` + ### Auto including dependencies If `--auto-include-dependencies` flag is provided, dartdoc tries to automatically add diff --git a/dartdoc_options.yaml b/dartdoc_options.yaml index fed696afcc..b358c8af06 100644 --- a/dartdoc_options.yaml +++ b/dartdoc_options.yaml @@ -1,4 +1,4 @@ dartdoc: linkToSource: root: '.' - uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v6.2.2/%f%#L%l%' + uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v6.2.2-dev/%f%#L%l%' diff --git a/lib/src/generator/generator_utils.dart b/lib/src/generator/generator_utils.dart index 6251b4b0af..e0d176f40d 100644 --- a/lib/src/generator/generator_utils.dart +++ b/lib/src/generator/generator_utils.dart @@ -5,7 +5,7 @@ import 'dart:convert'; import 'package:collection/collection.dart'; -import 'package:dartdoc/src/model/categorization.dart'; +import 'package:dartdoc/src/model/directives/categorization.dart'; import 'package:dartdoc/src/model/enclosed_element.dart'; import 'package:dartdoc/src/model/indexable.dart'; import 'package:dartdoc/src/model/model_element.dart'; @@ -73,7 +73,7 @@ String generateSearchIndexJson( } // Compares two elements, first by fully qualified name, then by kind. -int _compareElementRepresentations(Indexable a, Indexable b) { +int _compareElementRepresentations(T a, T b) { final value = compareNatural(a.fullyQualifiedName, b.fullyQualifiedName); if (value == 0) { return compareNatural(a.kind, b.kind); diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index cf3fa01b08..f6308a001b 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -686,6 +686,40 @@ class _Renderer_Callable extends RendererBase { } } +class _Renderer_CanonicalFor extends RendererBase { + static final Map _propertyMapCache = {}; + static Map> propertyMap() => + _propertyMapCache.putIfAbsent( + CT_, + () => { + 'canonicalFor': Property( + getValue: (CT_ c) => c.canonicalFor, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable( + c, remainingNames, 'Set'), + renderIterable: (CT_ c, RendererBase r, + List ast, StringSink sink) { + return c.canonicalFor.map((e) => + _render_String(e, ast, r.template, sink, parent: r)); + }, + ), + }) as Map>; + + _Renderer_CanonicalFor(CanonicalFor context, RendererBase? parent, + Template template, StringSink sink) + : super(context, parent, template, sink); + + @override + Property? getProperty(String key) { + if (propertyMap().containsKey(key)) { + return propertyMap()[key]; + } else { + return null; + } + } +} + class _Renderer_Canonicalization extends RendererBase { static final Map _propertyMapCache = {}; static Map> propertyMap< @@ -2660,6 +2694,7 @@ class _Renderer_Container extends RendererBase { ..._Renderer_ModelElement.propertyMap(), ..._Renderer_Categorization.propertyMap(), ..._Renderer_TypeParameters.propertyMap(), + ..._Renderer_HideConstantImplementations.propertyMap(), 'allCanonicalModelElements': Property( getValue: (CT_ c) => c.allCanonicalModelElements, renderVariable: (CT_ c, Property self, @@ -5547,6 +5582,13 @@ class _Renderer_Field extends RendererBase { parent: r); }, ), + 'hasHideConstantImplementation': Property( + getValue: (CT_ c) => c.hasHideConstantImplementation, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.hasHideConstantImplementation == true, + ), 'href': Property( getValue: (CT_ c) => c.href, renderVariable: @@ -6270,6 +6312,13 @@ class _Renderer_GetterSetterCombo extends RendererBase { self.renderSimpleVariable(c, remainingNames, 'bool'), getBool: (CT_ c) => c.hasGetterOrSetter == true, ), + 'hasHideConstantImplementation': Property( + getValue: (CT_ c) => c.hasHideConstantImplementation, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.hasHideConstantImplementation == true, + ), 'hasNoGetterSetter': Property( getValue: (CT_ c) => c.hasNoGetterSetter, renderVariable: (CT_ c, Property self, @@ -6528,6 +6577,38 @@ class _Renderer_HasNoPage extends RendererBase { } } +class _Renderer_HideConstantImplementations + extends RendererBase { + static final Map _propertyMapCache = {}; + static Map> + propertyMap() => + _propertyMapCache.putIfAbsent( + CT_, + () => { + 'hasHideConstantImplementations': Property( + getValue: (CT_ c) => c.hasHideConstantImplementations, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => + c.hasHideConstantImplementations == true, + ), + }) as Map>; + + _Renderer_HideConstantImplementations(HideConstantImplementations context, + RendererBase? parent, Template template, StringSink sink) + : super(context, parent, template, sink); + + @override + Property? getProperty(String key) { + if (propertyMap().containsKey(key)) { + return propertyMap()[key]; + } else { + return null; + } + } +} + class _Renderer_Indexable extends RendererBase { static final Map _propertyMapCache = {}; static Map> propertyMap() => @@ -7643,6 +7724,8 @@ class _Renderer_Library extends RendererBase { ..._Renderer_ModelElement.propertyMap(), ..._Renderer_Categorization.propertyMap(), ..._Renderer_TopLevelContainer.propertyMap(), + ..._Renderer_CanonicalFor.propertyMap(), + ..._Renderer_HideConstantImplementations.propertyMap(), 'allClasses': Property( getValue: (CT_ c) => c.allClasses, renderVariable: (CT_ c, Property self, @@ -7668,15 +7751,15 @@ class _Renderer_Library extends RendererBase { parent: r)); }, ), - 'canonicalFor': Property( - getValue: (CT_ c) => c.canonicalFor, + 'allOriginalModelElementNames': Property( + getValue: (CT_ c) => c.allOriginalModelElementNames, renderVariable: (CT_ c, Property self, List remainingNames) => self.renderSimpleVariable( - c, remainingNames, 'Set'), + c, remainingNames, 'Iterable'), renderIterable: (CT_ c, RendererBase r, List ast, StringSink sink) { - return c.canonicalFor.map((e) => + return c.allOriginalModelElementNames.map((e) => _render_String(e, ast, r.template, sink, parent: r)); }, ), @@ -12085,7 +12168,7 @@ class _Renderer_Package extends RendererBase { } } -String renderSearchPage(PackageTemplateData context, Template template) { +String renderIndex(PackageTemplateData context, Template template) { var buffer = StringBuffer(); _render_PackageTemplateData(context, template.ast, template, buffer); return buffer.toString(); @@ -12323,7 +12406,7 @@ class _Renderer_PackageTemplateData extends RendererBase { } } -String renderIndex(PackageTemplateData context, Template template) { +String renderSearchPage(PackageTemplateData context, Template template) { var buffer = StringBuffer(); _render_PackageTemplateData(context, template.ast, template, buffer); return buffer.toString(); @@ -14529,6 +14612,13 @@ class _Renderer_TopLevelVariable extends RendererBase { parent: r); }, ), + 'hasHideConstantImplementation': Property( + getValue: (CT_ c) => c.hasHideConstantImplementation, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.hasHideConstantImplementation == true, + ), 'href': Property( getValue: (CT_ c) => c.href, renderVariable: @@ -16155,6 +16245,7 @@ const _invisibleGetters = { 'hasExplicitSetter', 'hasGetter', 'hasGetterOrSetter', + 'hasHideConstantImplementation', 'hasNoGetterSetter', 'hasParameters', 'hasPublicGetter', diff --git a/lib/src/model/container.dart b/lib/src/model/container.dart index eece57545e..eba4fe5dda 100644 --- a/lib/src/model/container.dart +++ b/lib/src/model/container.dart @@ -28,7 +28,7 @@ import 'package:meta/meta.dart'; /// * **has** : boolean getters indicating whether the underlying collections /// are empty. These are available mostly for the templating system. abstract class Container extends ModelElement - with Categorization, TypeParameters { + with Categorization, TypeParameters, HideConstantImplementations { Container(super.library, super.packageGraph); // TODO(jcollins-g): Implement a ContainerScope that flattens supertypes? diff --git a/lib/src/model/directives/canonical_for.dart b/lib/src/model/directives/canonical_for.dart new file mode 100644 index 0000000000..0f6a9ad71d --- /dev/null +++ b/lib/src/model/directives/canonical_for.dart @@ -0,0 +1,40 @@ +// 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. + +import 'package:dartdoc/src/model/documentation_comment.dart'; +import 'package:dartdoc/src/model/library.dart'; + +final _canonicalRegExp = RegExp(r'{@canonicalFor\s([^}]+)}'); + +/// Used by [Library] to implement the `canonicalFor` directive. +mixin CanonicalFor on DocumentationComment { + Set? _canonicalFor; + + Set get canonicalFor { + if (_canonicalFor == null) { + buildDocumentationAddition(documentationComment); + } + return _canonicalFor!; + } + + /// Hides `canonicalFor` from doc while leaving a note to ourselves to + /// help with ambiguous canonicalization determination. + /// + /// Example: + /// + /// {@canonicalFor libname.ClassName} + @override + String buildDocumentationAddition(String rawDocs) { + rawDocs = super.buildDocumentationAddition(rawDocs); + var newCanonicalFor = {}; + rawDocs = rawDocs.replaceAllMapped(_canonicalRegExp, (Match match) { + var elementName = match.group(1)!; + newCanonicalFor.add(elementName); + return ''; + }); + + _canonicalFor = newCanonicalFor; + return rawDocs; + } +} diff --git a/lib/src/model/categorization.dart b/lib/src/model/directives/categorization.dart similarity index 94% rename from lib/src/model/categorization.dart rename to lib/src/model/directives/categorization.dart index e69212f7b3..b26c17d0cb 100644 --- a/lib/src/model/categorization.dart +++ b/lib/src/model/directives/categorization.dart @@ -9,11 +9,11 @@ final RegExp _categoryRegExp = RegExp( r'[ ]*{@(api|category|subCategory|image|samples) (.+?)}[ ]*\n?', multiLine: true); -/// Mixin implementing dartdoc categorization for ModelElements. -mixin Categorization implements ModelElement { +/// Mixin parsing the `@category` directive for ModelElements. +mixin Categorization on DocumentationComment implements Indexable { @override String buildDocumentationAddition(String rawDocs) => - _stripAndSetDartdocCategories(rawDocs); + _stripAndSetDartdocCategories(super.buildDocumentationAddition(rawDocs)); /// Parse `{@category ...}` and related information in API comments, stripping /// out that information from the given comments and returning the stripped @@ -61,7 +61,6 @@ mixin Categorization implements ModelElement { return _subCategoryNames; } - @override bool get hasCategoryNames => categoryNames?.isNotEmpty ?? false; List? _categoryNames; @@ -99,7 +98,6 @@ mixin Categorization implements ModelElement { ...?categoryNames?.map((n) => package.nameToCategory[n]).whereNotNull() ]..sort(); - @override Iterable get displayedCategories { if (config.showUndocumentedCategories) return categories; return categories.where((c) => c.isDocumented); diff --git a/lib/src/model/directives/hide_constant_implementations.dart b/lib/src/model/directives/hide_constant_implementations.dart new file mode 100644 index 0000000000..9815cf88e0 --- /dev/null +++ b/lib/src/model/directives/hide_constant_implementations.dart @@ -0,0 +1,41 @@ +// 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. + +import 'package:dartdoc/src/model/documentation_comment.dart'; +import 'package:dartdoc/src/model/model_element.dart'; + +final _hideConstantImplementationsRegExp = + RegExp(r'{@hideConstantImplementations}'); + +/// Implement parsing the hideConstantImplementations dartdoc directive +/// for this [ModelElement]. Used by [Container]. +mixin HideConstantImplementations on DocumentationComment { + bool? _hasHideConstantImplementations; + + /// [true] if the `{@hideConstantImplementations}` dartdoc directive is + /// present in the documentation for this class. + bool get hasHideConstantImplementations { + if (_hasHideConstantImplementations == null) { + buildDocumentationAddition(documentationComment); + } + return _hasHideConstantImplementations!; + } + + /// Hides `{@hideConstantImplementations}` from doc while leaving a note to + /// ourselves to change rendering for these constants. + /// Example: + /// + /// {@hideConstantImplementations} + @override + String buildDocumentationAddition(String rawDocs) { + rawDocs = super.buildDocumentationAddition(rawDocs); + _hasHideConstantImplementations = false; + rawDocs = rawDocs.replaceAllMapped(_hideConstantImplementationsRegExp, + (Match match) { + _hasHideConstantImplementations = true; + return ''; + }); + return rawDocs; + } +} diff --git a/lib/src/model/documentation_comment.dart b/lib/src/model/documentation_comment.dart index 5fc75f065f..abee78f565 100644 --- a/lib/src/model/documentation_comment.dart +++ b/lib/src/model/documentation_comment.dart @@ -97,19 +97,15 @@ mixin DocumentationComment on Documentable, Warnable, Locatable, SourceCode { /// result. String _processCommentWithoutTools(String documentationComment) { var docs = stripComments(documentationComment); - if (!docs.contains('{@')) { - _analyzeCodeBlocks(docs); - return docs; + if (docs.contains('{@')) { + docs = _injectExamples(docs); + docs = _injectYouTube(docs); + docs = _injectAnimations(docs); + // TODO(srawlins): Processing templates here causes #2281. But leaving + // them unprocessed causes #2272. + docs = _stripHtmlAndAddToIndex(docs); } - docs = _injectExamples(docs); - docs = _injectYouTube(docs); - docs = _injectAnimations(docs); - _analyzeCodeBlocks(docs); - - // TODO(srawlins): Processing templates here causes #2281. But leaving them - // unprocessed causes #2272. - docs = _stripHtmlAndAddToIndex(docs); return docs; } @@ -159,10 +155,11 @@ mixin DocumentationComment on Documentable, Warnable, Locatable, SourceCode { 'tool', 'youtube', - // Categorization directives, parsed elsewhere: + // Other directives, parsed by `model/directives/*.dart`: 'api', 'canonicalFor', 'category', + 'hideConstantImplementations', 'image', 'samples', 'subCategory', @@ -845,7 +842,12 @@ mixin DocumentationComment on Documentable, Warnable, Locatable, SourceCode { String? _rawDocs; /// Override this to add more features to the documentation builder in a - /// subclass. + /// subclass. This function is allowed to have side-effects such as caching + /// the presence of dartdoc directives within the class, but implementations + /// must be safe to call multiple times. + /// TODO(jcollins-g): Consider a restructure that avoids relying on + /// side-effects and repeatedly traversing the doc string. + @mustCallSuper String buildDocumentationAddition(String docs) => docs; String _buildDocumentationBaseSync() { diff --git a/lib/src/model/field.dart b/lib/src/model/field.dart index 343243326f..051d2790e0 100644 --- a/lib/src/model/field.dart +++ b/lib/src/model/field.dart @@ -177,4 +177,8 @@ class Field extends ModelElement @override Inheritable? get overriddenElement => null; + + @override + bool get hasHideConstantImplementation => + definingEnclosingContainer.hasHideConstantImplementations; } diff --git a/lib/src/model/getter_setter_combo.dart b/lib/src/model/getter_setter_combo.dart index 2cb754743c..8928978e8f 100644 --- a/lib/src/model/getter_setter_combo.dart +++ b/lib/src/model/getter_setter_combo.dart @@ -61,6 +61,7 @@ mixin GetterSetterCombo on ModelElement { bool get hasConstantValueForDisplay { final element = this.element; if (element is! ConstVariableElement) return false; + if (hasHideConstantImplementation) return false; return element.constantInitializer != null; } @@ -266,6 +267,10 @@ mixin GetterSetterCombo on ModelElement { bool get writeOnly => hasPublicSetter && !hasPublicGetter; + /// True if the @hideConstantImplementations directive is present + /// in the defining enclosing element. + bool get hasHideConstantImplementation; + @override late final Map referenceChildren = { if (hasParameters) ...parameters.explicitOnCollisionWith(this), diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart index 3824c9cb9d..fd38275e38 100644 --- a/lib/src/model/library.dart +++ b/lib/src/model/library.dart @@ -135,7 +135,12 @@ class _LibrarySentinel implements Library { throw UnimplementedError('No members on Library.sentinel are accessible'); } -class Library extends ModelElement with Categorization, TopLevelContainer { +class Library extends ModelElement + with + Categorization, + TopLevelContainer, + CanonicalFor, + HideConstantImplementations { @override final LibraryElement element; @@ -215,7 +220,7 @@ class Library extends ModelElement with Categorization, TopLevelContainer { /// documented with this library, but these ModelElements and names correspond /// to the defining library where each originally came from with respect /// to inheritance and reexporting. Most useful for error reporting. - late final Iterable _allOriginalModelElementNames = + late final Iterable allOriginalModelElementNames = allModelElements.map((e) { if (e is GetterSetterCombo) { Accessor? getter; @@ -305,46 +310,6 @@ class Library extends ModelElement with Categorization, TopLevelContainer { .replaceAll(':', '-') .replaceAll('/', '_'); - Set? _canonicalFor; - - Set get canonicalFor { - if (_canonicalFor == null) { - // TODO(jcollins-g): restructure to avoid using side effects. - buildDocumentationAddition(documentationComment); - } - return _canonicalFor!; - } - - static final _canonicalRegExp = RegExp(r'{@canonicalFor\s([^}]+)}'); - - /// Hides [canonicalFor] from doc while leaving a note to ourselves to - /// help with ambiguous canonicalization determination. - /// - /// Example: - /// - /// {@canonicalFor libname.ClassName} - @override - String buildDocumentationAddition(String rawDocs) { - rawDocs = super.buildDocumentationAddition(rawDocs); - var newCanonicalFor = {}; - var notFoundInAllModelElements = {}; - rawDocs = rawDocs.replaceAllMapped(_canonicalRegExp, (Match match) { - var elementName = match.group(1)!; - newCanonicalFor.add(elementName); - if (!_allOriginalModelElementNames.contains(elementName)) { - notFoundInAllModelElements.add(elementName); - } - return ''; - }); - for (var notFound in notFoundInAllModelElements) { - warn(PackageWarning.ignoredCanonicalFor, message: notFound); - } - // TODO(jcollins-g): warn if a macro/tool _does_ generate an unexpected - // canonicalFor? - _canonicalFor ??= newCanonicalFor; - return rawDocs; - } - /// Libraries are not enclosed by anything. @override ModelElement? get enclosingElement => null; @@ -553,4 +518,24 @@ class Library extends ModelElement with Categorization, TopLevelContainer { @override Iterable get referenceParents => [package]; + + /// Check [canonicalFor] for correctness and warn if it refers to + /// non-existent elements (or those that this Library can not be canonical + /// for). + @override + String buildDocumentationAddition(String rawDocs) { + rawDocs = super.buildDocumentationAddition(rawDocs); + var notFoundInAllModelElements = {}; + for (var elementName in canonicalFor) { + if (!allOriginalModelElementNames.contains(elementName)) { + notFoundInAllModelElements.add(elementName); + } + } + for (var notFound in notFoundInAllModelElements) { + warn(PackageWarning.ignoredCanonicalFor, message: notFound); + } + // TODO(jcollins-g): warn if a macro/tool generates an unexpected + // canonicalFor? + return rawDocs; + } } diff --git a/lib/src/model/model.dart b/lib/src/model/model.dart index 49add5908c..32d16a4f32 100644 --- a/lib/src/model/model.dart +++ b/lib/src/model/model.dart @@ -4,12 +4,14 @@ export 'accessor.dart'; export 'canonicalization.dart'; -export 'categorization.dart'; export 'category.dart'; export 'class.dart'; export 'constructor.dart'; export 'container.dart'; export 'container_member.dart'; +export 'directives/canonical_for.dart'; +export 'directives/categorization.dart'; +export 'directives/hide_constant_implementations.dart'; export 'documentable.dart'; export 'documentation.dart'; export 'documentation_comment.dart'; diff --git a/lib/src/model/top_level_variable.dart b/lib/src/model/top_level_variable.dart index 4f2e3b41d2..d80e6d0a46 100644 --- a/lib/src/model/top_level_variable.dart +++ b/lib/src/model/top_level_variable.dart @@ -80,4 +80,8 @@ class TopLevelVariable extends ModelElement @override Iterable get referenceParents => [definingLibrary]; + + @override + bool get hasHideConstantImplementation => + definingLibrary.hasHideConstantImplementations; } diff --git a/lib/src/version.dart b/lib/src/version.dart index 86535b9003..701cd49e36 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '6.2.2'; +const packageVersion = '6.2.2-dev'; diff --git a/pubspec.yaml b/pubspec.yaml index 6a6e6bec64..316a9d9f4e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dartdoc # Run `dart run grinder build` after updating. -version: 6.2.2 +version: 6.2.2-dev description: A non-interactive HTML documentation generator for Dart source code. repository: https://github.com/dart-lang/dartdoc diff --git a/test/dartdoc_test_base.dart b/test/dartdoc_test_base.dart index d4a492fe06..aa18c6eaad 100644 --- a/test/dartdoc_test_base.dart +++ b/test/dartdoc_test_base.dart @@ -70,9 +70,11 @@ analyzer: packagePath, name, Uri.file('$packagePath/')); } - Future bootPackageWithLibrary(String libraryContent) async { + Future bootPackageWithLibrary(String libraryContent, + {String libraryPreamble = ''}) async { await d.dir('lib', [ d.file('lib.dart', ''' +$libraryPreamble library $libraryName; $libraryContent diff --git a/test/directives/hide_constant_implementations_test.dart b/test/directives/hide_constant_implementations_test.dart new file mode 100644 index 0000000000..6521f07531 --- /dev/null +++ b/test/directives/hide_constant_implementations_test.dart @@ -0,0 +1,65 @@ +// 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. + +import 'package:test/test.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../dartdoc_test_base.dart'; +import '../src/utils.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(HideConstantImplementationsTest); + }); +} + +@reflectiveTest +class HideConstantImplementationsTest extends DartdocTestBase { + @override + String get libraryName => 'hide_constant_implementations'; + @override + String get sdkConstraint => '>=2.18.0 <4.0.0'; + + void test_noDirectiveAllows() async { + var library = await bootPackageWithLibrary(''' +class A { + static const int aConst = 12; +} +'''); + var aConst = library.classes.named('A').constantFields.named('aConst'); + expect(aConst.hasConstantValueForDisplay, isTrue); + } + + void test_directiveDenies() async { + var library = await bootPackageWithLibrary(''' +/// Some documentation. +/// {@hideConstantImplementations} +class A { + static const int aConst = 12; +} +'''); + var aClass = library.classes.named('A'); + var aConst = aClass.constantFields.named('aConst'); + expect(aConst.hasConstantValueForDisplay, isFalse); + expect(aClass.documentation, equals('Some documentation.\n')); + } + + void test_libraryDirectiveImpactsTopLevelOnly() async { + var library = await bootPackageWithLibrary(''' +class A { + static const int aConst = 12; +} + +static const aTopLevelConst = 37; +''', libraryPreamble: ''' +/// Some documentation. +/// {@hideConstantImplementations} +'''); + var aConst = library.classes.named('A').constantFields.named('aConst'); + expect(aConst.hasConstantValueForDisplay, isTrue); + var aTopLevelConst = library.constants.named('aTopLevelConst'); + expect(aTopLevelConst.hasConstantValueForDisplay, isFalse); + expect(library.documentation, equals('Some documentation.\n')); + } +} diff --git a/tool/grind.dart b/tool/grind.dart index 01f476df1b..7c65f2c720 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -72,6 +72,7 @@ Future get cleanFlutterRepo async { if (repoCompleter != null) { return repoCompleter.future; } + _cleanFlutterRepo = repoCompleter; // No await is allowed between check of _cleanFlutterRepo and its assignment, // to prevent reentering this function. @@ -889,6 +890,14 @@ Future>> _buildFlutterDocs( ['pub', 'get'], workingDirectory: p.join(flutterPath, 'dev', 'tools'), ); + try { + await flutterRepo.launcher.runStreamed( + flutterRepo.cacheDart, + ['pub', 'global', 'deactivate', 'snippets'], + ); + } on SubProcessException { + // Ignore failure to deactivate so this works on completely clean bots. + } await flutterRepo.launcher.runStreamed( flutterRepo.cacheDart, ['pub', 'global', 'activate', 'snippets'], diff --git a/tool/subprocess_launcher.dart b/tool/subprocess_launcher.dart index 81cfcf424d..e25d04ba76 100644 --- a/tool/subprocess_launcher.dart +++ b/tool/subprocess_launcher.dart @@ -9,6 +9,16 @@ import 'dart:io'; import 'package:analyzer/file_system/file_system.dart'; import 'package:path/path.dart' as p; +class SubProcessException implements Exception { + final List arguments; + final String executable; + final int exitCode; + final String message; + + SubProcessException( + this.executable, this.arguments, this.message, this.exitCode); +} + /// Keeps track of coverage data automatically for any processes run by this /// [CoverageSubprocessLauncher]. Requires that these be dart processes. class CoverageSubprocessLauncher extends SubprocessLauncher { @@ -261,7 +271,7 @@ class SubprocessLauncher { var exitCode = await process.exitCode; if (exitCode != 0) { - throw ProcessException( + throw SubProcessException( executable, arguments, 'SubprocessLauncher got non-zero exitCode: $exitCode\n\n'