Refine detection for NNBD cases and tag generated pages with "Null safety" (#2221)
* Beginnings
* wow, this mostly works
* Very simple SDK NNBD detection
* Basic templates and tagging for nnbd interfaces
* dartfmt, update tests
* Fix duplicitous comment
* Update with review comments
diff --git a/lib/resources/styles.css b/lib/resources/styles.css
index 711fde7..8f81e1c 100644
--- a/lib/resources/styles.css
+++ b/lib/resources/styles.css
@@ -460,6 +460,22 @@
vertical-align: middle;
}
+.feature {
+ display: inline-block;
+ background: white;
+ border: 1px solid #0175c2;
+ border-radius: 20px;
+ color: #0175c2;
+
+ font-size: 12px;
+ padding: 1px 6px;
+ margin: 0 8px 0 0;
+}
+
+h1 .feature {
+ vertical-align: middle;
+}
+
.source-link {
padding: 18px 4px;
vertical-align: middle;
diff --git a/lib/src/element_type.dart b/lib/src/element_type.dart
index 14728c2..fe684cb 100644
--- a/lib/src/element_type.dart
+++ b/lib/src/element_type.dart
@@ -74,8 +74,23 @@
String get name;
+ /// Name with generics and nullability indication.
String get nameWithGenerics;
+ /// Return a dartdoc nullability suffix for this type.
+ String get nullabilitySuffix {
+ if (library.isNNBD && !type.isVoid && !type.isBottom) {
+ /// If a legacy type appears inside the public interface of a
+ /// NNBD library, we pretend it is nullable for the purpose of
+ /// documentation (since star-types are not supposed to be public).
+ if (type.nullabilitySuffix == NullabilitySuffix.question ||
+ type.nullabilitySuffix == NullabilitySuffix.star) {
+ return '?';
+ }
+ }
+ return '';
+ }
+
List<Parameter> get parameters => [];
DartType get instantiatedType;
@@ -120,7 +135,7 @@
}
@override
- String get nameWithGenerics => name;
+ String get nameWithGenerics => '$name${nullabilitySuffix}';
/// Assume that undefined elements don't have useful bounds.
@override
diff --git a/lib/src/generator/templates.dart b/lib/src/generator/templates.dart
index 31e5049..ec90e16 100644
--- a/lib/src/generator/templates.dart
+++ b/lib/src/generator/templates.dart
@@ -22,6 +22,7 @@
'class',
'constant',
'extension',
+ 'feature_set',
'footer',
'head',
'library',
@@ -55,6 +56,8 @@
'documentation',
'extension',
'features',
+ 'feature_set',
+ 'footer',
'footer',
'head',
'library',
diff --git a/lib/src/model/feature_set.dart b/lib/src/model/feature_set.dart
new file mode 100644
index 0000000..acfeb1d
--- /dev/null
+++ b/lib/src/model/feature_set.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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/language_feature.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+/// [ModelElement]s can have different language features that can alter
+/// the user interpretation of the interface.
+mixin FeatureSet {
+ PackageGraph get packageGraph;
+ Library get library;
+
+ /// A list of language features that both apply to this [ModelElement] and
+ /// make sense to display in context.
+ Iterable<LanguageFeature> get displayedLanguageFeatures sync* {
+ // TODO(jcollins-g): Implement mixed-mode handling and the tagging of
+ // legacy interfaces.
+ if (isNNBD) {
+ yield LanguageFeature(
+ 'Null safety', packageGraph.rendererFactory.featureRenderer);
+ }
+ }
+
+ bool get hasFeatureSet => displayedLanguageFeatures.isNotEmpty;
+
+ // TODO(jcollins-g): This is an approximation and not strictly true for
+ // inheritance/reexports.
+ bool get isNNBD => library.isNNBD;
+}
diff --git a/lib/src/model/language_feature.dart b/lib/src/model/language_feature.dart
new file mode 100644
index 0000000..0c3036f
--- /dev/null
+++ b/lib/src/model/language_feature.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, 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/render/feature_renderer.dart';
+
+const Map<String, String> _featureDescriptions = {
+ 'Null safety': 'Supports the null safety language feature.',
+};
+
+/// An abstraction for a language feature; used to render tags to notify
+/// the user that the documentation should be specially interpreted.
+class LanguageFeature {
+ String get featureDescription => _featureDescriptions[name];
+ String get featureLabel => _featureRenderer.renderFeatureLabel(this);
+
+ final String name;
+
+ final FeatureRenderer _featureRenderer;
+
+ LanguageFeature(this.name, this._featureRenderer) {
+ assert(_featureDescriptions.containsKey(name));
+ }
+}
diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart
index 83d8357..407726b 100644
--- a/lib/src/model/library.dart
+++ b/lib/src/model/library.dart
@@ -107,6 +107,19 @@
List<String> _allOriginalModelElementNames;
+ /// Return true if this library is in a package configured to be treated as
+ /// as non-nullable by default and is itself NNBD.
+ // TODO(jcollins-g): packageMeta.allowsNNBD may be redundant after package
+ // allow list support is published in analyzer
+ bool get _allowsNNBD =>
+ element.isNonNullableByDefault && packageMeta.allowsNNBD;
+
+ /// Return true if this library should be documented as non-nullable.
+ /// A library may be NNBD but not documented that way.
+ @override
+ bool get isNNBD =>
+ config.enableExperiment.contains('non-nullable') && _allowsNNBD;
+
bool get isInSdk => element.isInSdk;
final Package _package;
diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart
index 3d2a9fa..fd27a53 100644
--- a/lib/src/model/model_element.dart
+++ b/lib/src/model/model_element.dart
@@ -21,6 +21,7 @@
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/logging.dart';
+import 'package:dartdoc/src/model/feature_set.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart' as utils;
import 'package:dartdoc/src/render/model_element_renderer.dart';
@@ -148,7 +149,14 @@
/// ModelElement will reference itself as part of the "wrong" [Library]
/// from the public interface perspective.
abstract class ModelElement extends Canonicalization
- with Privacy, Warnable, Locatable, Nameable, SourceCodeMixin, Indexable
+ with
+ Privacy,
+ Warnable,
+ Locatable,
+ Nameable,
+ SourceCodeMixin,
+ Indexable,
+ FeatureSet
implements Comparable, Documentable {
final Element _element;
diff --git a/lib/src/package_meta.dart b/lib/src/package_meta.dart
index a03a2c2..cbddb32 100644
--- a/lib/src/package_meta.dart
+++ b/lib/src/package_meta.dart
@@ -10,6 +10,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:dartdoc/dartdoc.dart';
import 'package:path/path.dart' as path;
+import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
import 'logging.dart';
@@ -106,6 +107,14 @@
String get homepage;
+ /// If true, libraries in this package can be be read as non-nullable by
+ /// default. Whether they will be will be documented that way will depend
+ /// on the experiment flag.
+ ///
+ /// A package property, as this depends in part on the pubspec version
+ /// constraint and/or the package allow list.
+ bool get allowsNNBD;
+
FileContents getReadmeContents();
FileContents getLicenseContents();
@@ -272,6 +281,7 @@
FileContents _license;
FileContents _changelog;
Map _pubspec;
+ Map _analysisOptions;
_FilePackageMeta(Directory dir) : super(dir) {
var f = File(path.join(dir.path, 'pubspec.yaml'));
@@ -280,6 +290,12 @@
} else {
_pubspec = {};
}
+ f = File(path.join(dir.path, 'analysis_options.yaml'));
+ if (f.existsSync()) {
+ _analysisOptions = loadYaml(f.readAsStringSync());
+ } else {
+ _analysisOptions = {};
+ }
}
bool _setHostedAt = false;
@@ -360,6 +376,29 @@
@override
String get homepage => _pubspec['homepage'];
+ /// This is a magic range that triggers detection of [allowsNNBD].
+ static final _nullableRange =
+ VersionConstraint.parse('>=2.9.0-dev.0 <2.10.0') as VersionRange;
+
+ @override
+ bool get allowsNNBD {
+ // TODO(jcollins-g): override/add to with allow list once that exists
+ var sdkConstraint;
+ if (_pubspec?.containsKey('sdk') ?? false) {
+ // TODO(jcollins-g): VersionConstraint.parse returns [VersionRange]s right
+ // now, but the interface doesn't guarantee that.
+ sdkConstraint = VersionConstraint.parse(_pubspec['sdk']) as VersionRange;
+ }
+ if (sdkConstraint == _nullableRange &&
+ (_analysisOptions['analyzer']?.containsKey('enable-experiment') ??
+ false) &&
+ _analysisOptions['analyzer']['enable-experiment']
+ .contains('non-nullable')) {
+ return true;
+ }
+ return false;
+ }
+
@override
bool get requiresFlutter =>
_pubspec['environment']?.containsKey('flutter') == true ||
@@ -423,6 +462,14 @@
sdkReadmePath = path.join(dir.path, 'lib', 'api_readme.md');
}
+ /// 2.9.0-9.0.dev is the first unforked SDK, and therefore the first version
+ /// of the SDK it makes sense to allow NNBD documentation for.
+ static final _sdkNullableRange = VersionConstraint.parse('>=2.9.0-9.0.dev');
+
+ @override
+ // TODO(jcollins-g): There should be a better way to determine this.
+ bool get allowsNNBD => _sdkNullableRange.allows(Version.parse(version));
+
@override
String get hostedAt => null;
diff --git a/lib/src/render/element_type_renderer.dart b/lib/src/render/element_type_renderer.dart
index 9ec41d4..f88e3d8 100644
--- a/lib/src/render/element_type_renderer.dart
+++ b/lib/src/render/element_type_renderer.dart
@@ -9,6 +9,13 @@
String renderLinkedName(T elementType);
String renderNameWithGenerics(T elementType) => '';
+
+ String wrapNullabilityParens(T elementType, String inner) =>
+ elementType.nullabilitySuffix.isEmpty
+ ? inner
+ : '($inner${elementType.nullabilitySuffix})';
+ String wrapNullability(T elementType, String inner) =>
+ '$inner${elementType.nullabilitySuffix}';
}
// Html implementations
@@ -24,7 +31,7 @@
buf.write(
ParameterRendererHtml().renderLinkedParams(elementType.parameters));
buf.write(')</span>');
- return buf.toString();
+ return wrapNullabilityParens(elementType, buf.toString());
}
@override
@@ -39,7 +46,7 @@
buf.write('</span>>');
}
}
- return buf.toString();
+ return wrapNullability(elementType, buf.toString());
}
}
@@ -58,7 +65,7 @@
buf.write('</span>>');
buf.write('</span>');
}
- return buf.toString();
+ return wrapNullability(elementType, buf.toString());
}
@override
@@ -72,7 +79,8 @@
'</span>, <span class="type-parameter">');
buf.write('</span>>');
}
- return buf.toString();
+ buf.write(elementType.nullabilitySuffix);
+ return wrapNullability(elementType, buf.toString());
}
}
@@ -92,7 +100,7 @@
.trim());
buf.write(') → ');
buf.write(elementType.returnType.linkedName);
- return buf.toString();
+ return wrapNullabilityParens(elementType, buf.toString());
}
}
@@ -108,7 +116,7 @@
buf.write('(');
buf.write(ParameterRendererMd().renderLinkedParams(elementType.parameters));
buf.write(')');
- return buf.toString();
+ return wrapNullabilityParens(elementType, buf.toString());
}
@override
@@ -122,7 +130,7 @@
buf.write('>');
}
}
- return buf.toString();
+ return wrapNullabilityParens(elementType, buf.toString());
}
}
@@ -138,7 +146,7 @@
buf.writeAll(elementType.typeArguments.map((t) => t.linkedName), ', ');
buf.write('>');
}
- return buf.toString();
+ return wrapNullability(elementType, buf.toString());
}
@override
@@ -152,7 +160,8 @@
elementType.typeArguments.map((t) => t.nameWithGenerics), ', ');
buf.write('>');
}
- return buf.toString();
+ buf.write(elementType.nullabilitySuffix);
+ return wrapNullability(elementType, buf.toString());
}
}
@@ -172,6 +181,6 @@
.trim());
buf.write(') → ');
buf.write(elementType.returnType.linkedName);
- return buf.toString();
+ return wrapNullabilityParens(elementType, buf.toString());
}
}
diff --git a/lib/src/render/feature_renderer.dart b/lib/src/render/feature_renderer.dart
new file mode 100644
index 0000000..5cdf718
--- /dev/null
+++ b/lib/src/render/feature_renderer.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, 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/language_feature.dart';
+
+abstract class FeatureRenderer {
+ String renderFeatureLabel(LanguageFeature feature);
+}
+
+class FeatureRendererHtml extends FeatureRenderer {
+ static final FeatureRendererHtml _instance = FeatureRendererHtml._();
+ factory FeatureRendererHtml() {
+ return _instance;
+ }
+
+ FeatureRendererHtml._();
+
+ @override
+ String renderFeatureLabel(LanguageFeature feature) {
+ var spanClasses = <String>[];
+ spanClasses.add('feature');
+ spanClasses
+ .add('feature-${feature.name.split(' ').join('-').toLowerCase()}');
+
+ var buf = StringBuffer();
+ buf.write(
+ '<span class="${spanClasses.join(' ')}" title="${feature.featureDescription}">${feature.name}</span>');
+ return buf.toString();
+ }
+}
+
+class FeatureRendererMd extends FeatureRenderer {
+ static final FeatureRendererMd _instance = FeatureRendererMd._();
+ factory FeatureRendererMd() {
+ return _instance;
+ }
+
+ FeatureRendererMd._();
+ @override
+ String renderFeatureLabel(LanguageFeature feature) {
+ return '*\<${feature.name}\>*';
+ }
+}
diff --git a/lib/src/render/renderer_factory.dart b/lib/src/render/renderer_factory.dart
index eaead50..9f13aec 100644
--- a/lib/src/render/renderer_factory.dart
+++ b/lib/src/render/renderer_factory.dart
@@ -7,6 +7,7 @@
import 'package:dartdoc/src/render/documentation_renderer.dart';
import 'package:dartdoc/src/render/element_type_renderer.dart';
import 'package:dartdoc/src/render/enum_field_renderer.dart';
+import 'package:dartdoc/src/render/feature_renderer.dart';
import 'package:dartdoc/src/render/model_element_renderer.dart';
import 'package:dartdoc/src/render/parameter_renderer.dart';
import 'package:dartdoc/src/render/template_renderer.dart';
@@ -31,6 +32,8 @@
DocumentationRenderer get documentationRenderer;
+ FeatureRenderer get featureRenderer;
+
ElementTypeRenderer<FunctionTypeElementType>
get functionTypeElementTypeRenderer;
@@ -96,6 +99,9 @@
@override
TypedefRenderer get typedefRenderer => TypedefRendererHtml();
+
+ @override
+ FeatureRenderer get featureRenderer => FeatureRendererHtml();
}
class MdRenderFactory extends RendererFactory {
@@ -143,4 +149,7 @@
@override
TypedefRenderer get typedefRenderer => TypedefRendererMd();
+
+ @override
+ FeatureRenderer get featureRenderer => FeatureRendererMd();
}
diff --git a/lib/templates/html/_feature_set.html b/lib/templates/html/_feature_set.html
new file mode 100644
index 0000000..006a8c6
--- /dev/null
+++ b/lib/templates/html/_feature_set.html
@@ -0,0 +1,5 @@
+{{#hasFeatureSet}}
+ {{#displayedLanguageFeatures}}
+ {{{featureLabel}}}
+ {{/displayedLanguageFeatures}}
+{{/hasFeatureSet}}
\ No newline at end of file
diff --git a/lib/templates/html/class.html b/lib/templates/html/class.html
index 2fd0090..e0d9981 100644
--- a/lib/templates/html/class.html
+++ b/lib/templates/html/class.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-class">{{{nameWithGenerics}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-class">{{{nameWithGenerics}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{/self}}
{{#clazz}}
diff --git a/lib/templates/html/constant.html b/lib/templates/html/constant.html
index 5248e7d..d3c68ca 100644
--- a/lib/templates/html/constant.html
+++ b/lib/templates/html/constant.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-constant">{{{name}}}</span> {{kind}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-constant">{{{name}}}</span> {{kind}} {{>feature_set}}</h1></div>
{{/self}}
<section class="multi-line-signature">
diff --git a/lib/templates/html/constructor.html b/lib/templates/html/constructor.html
index 3fc1a9f..4c6a3b6 100644
--- a/lib/templates/html/constructor.html
+++ b/lib/templates/html/constructor.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-constructor">{{{nameWithGenerics}}}</span> {{kind}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-constructor">{{{nameWithGenerics}}}</span> {{kind}} {{>feature_set}}</h1></div>
{{/self}}
{{#constructor}}
diff --git a/lib/templates/html/enum.html b/lib/templates/html/enum.html
index f42561c..16dfbd8 100644
--- a/lib/templates/html/enum.html
+++ b/lib/templates/html/enum.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-enum">{{{name}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-enum">{{{name}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{/self}}
{{#eNum}}
diff --git a/lib/templates/html/extension.html b/lib/templates/html/extension.html
index 7434dbf..8942e9a 100644
--- a/lib/templates/html/extension.html
+++ b/lib/templates/html/extension.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-class">{{{nameWithGenerics}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-class">{{{nameWithGenerics}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{/self}}
{{#extension}}
diff --git a/lib/templates/html/function.html b/lib/templates/html/function.html
index a8d9c85..4995929 100644
--- a/lib/templates/html/function.html
+++ b/lib/templates/html/function.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-function">{{{nameWithGenerics}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-function">{{{nameWithGenerics}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{/self}}
{{#function}}
diff --git a/lib/templates/html/library.html b/lib/templates/html/library.html
index 6e08376..074f158 100644
--- a/lib/templates/html/library.html
+++ b/lib/templates/html/library.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-library">{{{name}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-library">{{{name}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{/self}}
{{#library}}
diff --git a/lib/templates/html/method.html b/lib/templates/html/method.html
index d88bc73..f0995f2 100644
--- a/lib/templates/html/method.html
+++ b/lib/templates/html/method.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-method">{{{nameWithGenerics}}}</span> {{kind}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-method">{{{nameWithGenerics}}}</span> {{kind}} {{>feature_set}}</h1></div>
{{/self}}
{{#method}}
diff --git a/lib/templates/html/mixin.html b/lib/templates/html/mixin.html
index d7500f5..6864710 100644
--- a/lib/templates/html/mixin.html
+++ b/lib/templates/html/mixin.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-mixin">{{{nameWithGenerics}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-mixin">{{{nameWithGenerics}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{/self}}
{{#mixin}}
diff --git a/lib/templates/html/property.html b/lib/templates/html/property.html
index 1372a37..e15ba48 100644
--- a/lib/templates/html/property.html
+++ b/lib/templates/html/property.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-property">{{name}}</span> {{kind}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-property">{{name}}</span> {{kind}} {{>feature_set}}</h1></div>
{{/self}}
{{#self}}
diff --git a/lib/templates/html/top_level_constant.html b/lib/templates/html/top_level_constant.html
index ccd3f66..73d5618 100644
--- a/lib/templates/html/top_level_constant.html
+++ b/lib/templates/html/top_level_constant.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-top-level-constant">{{{name}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-top-level-constant">{{{name}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
<section class="multi-line-signature">
{{>name_summary}}
diff --git a/lib/templates/html/top_level_property.html b/lib/templates/html/top_level_property.html
index 00bbf31..367f932 100644
--- a/lib/templates/html/top_level_property.html
+++ b/lib/templates/html/top_level_property.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-top-level-property">{{{name}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-top-level-property">{{{name}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{#hasNoGetterSetter}}
<section class="multi-line-signature">
diff --git a/lib/templates/html/typedef.html b/lib/templates/html/typedef.html
index 2fcbe00..31318e3 100644
--- a/lib/templates/html/typedef.html
+++ b/lib/templates/html/typedef.html
@@ -8,7 +8,7 @@
<div id="dartdoc-main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
{{#self}}
- <div>{{>source_link}}<h1><span class="kind-typedef">{{{nameWithGenerics}}}</span> {{kind}} {{>categorization}}</h1></div>
+ <div>{{>source_link}}<h1><span class="kind-typedef">{{{nameWithGenerics}}}</span> {{kind}} {{>feature_set}} {{>categorization}}</h1></div>
{{/self}}
<section class="multi-line-signature">
diff --git a/lib/templates/md/_feature_set.md b/lib/templates/md/_feature_set.md
new file mode 100644
index 0000000..006a8c6
--- /dev/null
+++ b/lib/templates/md/_feature_set.md
@@ -0,0 +1,5 @@
+{{#hasFeatureSet}}
+ {{#displayedLanguageFeatures}}
+ {{{featureLabel}}}
+ {{/displayedLanguageFeatures}}
+{{/hasFeatureSet}}
\ No newline at end of file
diff --git a/lib/templates/md/class.md b/lib/templates/md/class.md
index e98847f..5976aab 100644
--- a/lib/templates/md/class.md
+++ b/lib/templates/md/class.md
@@ -5,6 +5,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{/self}}
{{#clazz}}
diff --git a/lib/templates/md/constant.md b/lib/templates/md/constant.md
index 3f5abcb..984f7e7 100644
--- a/lib/templates/md/constant.md
+++ b/lib/templates/md/constant.md
@@ -4,6 +4,7 @@
# {{{name}}} {{kind}}
{{>source_link}}
+{{>feature_set}}
{{/self}}
{{#property}}
diff --git a/lib/templates/md/constructor.md b/lib/templates/md/constructor.md
index ce89771..8e67ab4 100644
--- a/lib/templates/md/constructor.md
+++ b/lib/templates/md/constructor.md
@@ -4,6 +4,7 @@
# {{{nameWithGenerics}}} {{kind}}
{{>source_link}}
+{{>feature_set}}
{{/self}}
{{#constructor}}
diff --git a/lib/templates/md/enum.md b/lib/templates/md/enum.md
index ce48fa3..d1a14bb 100644
--- a/lib/templates/md/enum.md
+++ b/lib/templates/md/enum.md
@@ -4,7 +4,7 @@
# {{{name}}} {{kind}}
{{>source_link}}
-{{>categorization}}
+{{>feature_set}}
{{/self}}
{{#eNum}}
diff --git a/lib/templates/md/extension.md b/lib/templates/md/extension.md
index be62c54..f31b5c2 100644
--- a/lib/templates/md/extension.md
+++ b/lib/templates/md/extension.md
@@ -7,6 +7,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{/self}}
{{#extension}}
diff --git a/lib/templates/md/function.md b/lib/templates/md/function.md
index 63193ff..7937404 100644
--- a/lib/templates/md/function.md
+++ b/lib/templates/md/function.md
@@ -5,6 +5,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{/self}}
{{#function}}
diff --git a/lib/templates/md/library.md b/lib/templates/md/library.md
index f338b40..a4811ab 100644
--- a/lib/templates/md/library.md
+++ b/lib/templates/md/library.md
@@ -5,6 +5,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{/self}}
{{#library}}
diff --git a/lib/templates/md/method.md b/lib/templates/md/method.md
index 0d03ab4..e3e7f19 100644
--- a/lib/templates/md/method.md
+++ b/lib/templates/md/method.md
@@ -4,6 +4,7 @@
# {{{nameWithGenerics}}} {{kind}}
{{>source_link}}
+{{>feature_set}}
{{/self}}
{{#method}}
diff --git a/lib/templates/md/mixin.md b/lib/templates/md/mixin.md
index e81cdbc..b78510f 100644
--- a/lib/templates/md/mixin.md
+++ b/lib/templates/md/mixin.md
@@ -5,6 +5,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{/self}}
{{#mixin}}
diff --git a/lib/templates/md/property.md b/lib/templates/md/property.md
index 351e51e..222c5e2 100644
--- a/lib/templates/md/property.md
+++ b/lib/templates/md/property.md
@@ -4,6 +4,7 @@
# {{name}} {{kind}}
{{>source_link}}
+{{>feature_set}}
{{/self}}
{{#self}}
diff --git a/lib/templates/md/top_level_constant.md b/lib/templates/md/top_level_constant.md
index 1f79709..d89d2a6 100644
--- a/lib/templates/md/top_level_constant.md
+++ b/lib/templates/md/top_level_constant.md
@@ -5,6 +5,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{>name_summary}} = {{{ constantValue }}} {{!two spaces intentional}}
{{>features}}
diff --git a/lib/templates/md/top_level_property.md b/lib/templates/md/top_level_property.md
index 3a06ef7..43a5ffc 100644
--- a/lib/templates/md/top_level_property.md
+++ b/lib/templates/md/top_level_property.md
@@ -5,6 +5,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{#hasNoGetterSetter}}
{{{ linkedReturnType }}} {{>name_summary}} {{!two spaces intentional}}
diff --git a/lib/templates/md/typedef.md b/lib/templates/md/typedef.md
index 3636e88..bed210a 100644
--- a/lib/templates/md/typedef.md
+++ b/lib/templates/md/typedef.md
@@ -5,6 +5,7 @@
{{>source_link}}
{{>categorization}}
+{{>feature_set}}
{{/self}}
{{#typeDef}}
diff --git a/test/model_special_cases_test.dart b/test/model_special_cases_test.dart
index 71fa09d..56387d3 100644
--- a/test/model_special_cases_test.dart
+++ b/test/model_special_cases_test.dart
@@ -35,12 +35,14 @@
// if we haven't figured out how to switch on NNBD outside of `dev` builds
// as specified in #2148.
final _nnbdExperimentAllowed =
- VersionRange(min: Version.parse('2.9.0'), includeMin: true);
+ VersionRange(min: Version.parse('2.9.0-9.0.dev'), includeMin: true);
// Experimental features not yet enabled by default. Move tests out of this block
// when the feature is enabled by default.
group('Experiments', () {
- Library lateFinalWithoutInitializer, nnbdClassMemberDeclarations;
+ Library lateFinalWithoutInitializer,
+ nnbdClassMemberDeclarations,
+ optOutOfNnbd;
Class b;
setUpAll(() async {
@@ -50,10 +52,18 @@
nnbdClassMemberDeclarations = (await utils.testPackageGraphExperiments)
.libraries
.firstWhere((lib) => lib.name == 'nnbd_class_member_declarations');
+ optOutOfNnbd = (await utils.testPackageGraphExperiments)
+ .libraries
+ .firstWhere((lib) => lib.name == 'opt_out_of_nnbd');
b = nnbdClassMemberDeclarations.allClasses
.firstWhere((c) => c.name == 'B');
});
+ test('isNNBD is set correctly for libraries', () {
+ expect(lateFinalWithoutInitializer.isNNBD, isTrue);
+ expect(optOutOfNnbd.isNNBD, isFalse);
+ });
+
test('method parameters with required', () {
var m1 = b.instanceMethods.firstWhere((m) => m.name == 'm1');
var p1 = m1.allParameters.firstWhere((p) => p.name == 'p1');
diff --git a/testing/test_package_experiments/lib/nnbd_class_member_declarations.dart b/testing/test_package_experiments/lib/nnbd_class_member_declarations.dart
index 6250e5b..47e989c 100644
--- a/testing/test_package_experiments/lib/nnbd_class_member_declarations.dart
+++ b/testing/test_package_experiments/lib/nnbd_class_member_declarations.dart
@@ -15,3 +15,13 @@
m2(int sometimes, we, [String have, double optionals]);
}
+/// Test nullable parameters, factories, members
+abstract class C {
+ factory C.factory1(int? param, {Object? param2}) => null;
+
+ int? testField;
+
+ List<int?> get testFieldNullableParameter;
+
+ List<Map<String, num?>>? method1();
+}
diff --git a/testing/test_package_experiments/lib/nullable_elements.dart b/testing/test_package_experiments/lib/nullable_elements.dart
new file mode 100644
index 0000000..054330d
--- /dev/null
+++ b/testing/test_package_experiments/lib/nullable_elements.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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.
+
+library nullable_elements;
+
+int? publicNullable;
+
+String? get nullableGetter => null;
+
+String? _nullableSetter;
+
+void set nullableSetter(String? value) {
+ _nullableSetter = value;
+}
+
+void some(int? nullable, String? parameters) {}
+
+class NullableMembers {
+ final Map<String, Map>? initialized;
+
+ /// Nullable constructor parameters.
+ NullableMembers(this.initialized);
+
+ Iterable<BigInt>? nullableField;
+
+ operator *(NullableMembers? nullableOther) => this;
+
+ int? methodWithNullables(String? foo) => foo?.length;
+}
\ No newline at end of file
diff --git a/testing/test_package_experiments/lib/opt_out_of_nnbd.dart b/testing/test_package_experiments/lib/opt_out_of_nnbd.dart
new file mode 100644
index 0000000..0b9584a
--- /dev/null
+++ b/testing/test_package_experiments/lib/opt_out_of_nnbd.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.
+
+// @dart=2.8
+
+library opt_out_of_nnbd;
+
+String notOptedIn = false ? 'hi' : null;
diff --git a/testing/test_package_experiments/pubspec.yaml b/testing/test_package_experiments/pubspec.yaml
index bda7ea2..37a98a5 100644
--- a/testing/test_package_experiments/pubspec.yaml
+++ b/testing/test_package_experiments/pubspec.yaml
@@ -1,4 +1,4 @@
name: test_package_experiments
version: 0.0.1
-sdk: '>=2.9.0 <2.10.0'
+sdk: '>=2.9.0-dev.0 <2.10.0'
description: Experimental flags are tested here.
diff --git a/tool/grind.dart b/tool/grind.dart
index 4fde62e..b1ca818 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -241,7 +241,10 @@
// Passing parameters to dartfmt for directories to search results in
// filenames being stripped of the dirname so we have to filter here.
void addFileToFix(String fileName) {
- if (path.split(fileName).first == 'testing') return;
+ var pathComponents = path.split(fileName);
+ if (pathComponents.isNotEmpty && pathComponents.first == 'testing') {
+ return;
+ }
filesToFix.add(fileName);
}