Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions lib/resources/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,22 @@ h1 .category {
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;
Expand Down
17 changes: 16 additions & 1 deletion lib/src/element_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,23 @@ abstract class ElementType extends Privacy {

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;
Expand Down Expand Up @@ -120,7 +135,7 @@ class UndefinedElementType extends ElementType {
}

@override
String get nameWithGenerics => name;
String get nameWithGenerics => '$name${nullabilitySuffix}';

/// Assume that undefined elements don't have useful bounds.
@override
Expand Down
3 changes: 3 additions & 0 deletions lib/src/generator/templates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const _partials_html = <String>[
'class',
'constant',
'extension',
'feature_set',
'footer',
'head',
'library',
Expand Down Expand Up @@ -55,6 +56,8 @@ const _partials_md = <String>[
'documentation',
'extension',
'features',
'feature_set',
'footer',
'footer',
'head',
'library',
Expand Down
30 changes: 30 additions & 0 deletions lib/src/model/feature_set.dart
Original file line number Diff line number Diff line change
@@ -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;
}
24 changes: 24 additions & 0 deletions lib/src/model/language_feature.dart
Original file line number Diff line number Diff line change
@@ -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));
}
}
13 changes: 13 additions & 0 deletions lib/src/model/library.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ class Library extends ModelElement with Categorization, TopLevelContainer {

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;
Expand Down
10 changes: 9 additions & 1 deletion lib/src/model/model_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'package:crypto/crypto.dart';
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';
Expand Down Expand Up @@ -148,7 +149,14 @@ ModelElement resolveMultiplyInheritedElement(
/// 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;

Expand Down
47 changes: 47 additions & 0 deletions lib/src/package_meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'dart:io';
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';
Expand Down Expand Up @@ -106,6 +107,14 @@ abstract class PackageMeta {

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();
Expand Down Expand Up @@ -272,6 +281,7 @@ class _FilePackageMeta extends PubPackageMeta {
FileContents _license;
FileContents _changelog;
Map _pubspec;
Map _analysisOptions;

_FilePackageMeta(Directory dir) : super(dir) {
var f = File(path.join(dir.path, 'pubspec.yaml'));
Expand All @@ -280,6 +290,12 @@ class _FilePackageMeta extends PubPackageMeta {
} else {
_pubspec = {};
}
f = File(path.join(dir.path, 'analysis_options.yaml'));
if (f.existsSync()) {
_analysisOptions = loadYaml(f.readAsStringSync());
} else {
_analysisOptions = {};
}
}

bool _setHostedAt = false;
Expand Down Expand Up @@ -360,6 +376,29 @@ class _FilePackageMeta extends PubPackageMeta {
@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 ||
Expand Down Expand Up @@ -423,6 +462,14 @@ class _SdkMeta extends PubPackageMeta {
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;

Expand Down
29 changes: 19 additions & 10 deletions lib/src/render/element_type_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ abstract class ElementTypeRenderer<T extends ElementType> {
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
Expand All @@ -24,7 +31,7 @@ class FunctionTypeElementTypeRendererHtml
buf.write(
ParameterRendererHtml().renderLinkedParams(elementType.parameters));
buf.write(')</span>');
return buf.toString();
return wrapNullabilityParens(elementType, buf.toString());
}

@override
Expand All @@ -39,7 +46,7 @@ class FunctionTypeElementTypeRendererHtml
buf.write('</span>&gt;');
}
}
return buf.toString();
return wrapNullability(elementType, buf.toString());
}
}

Expand All @@ -58,7 +65,7 @@ class ParameterizedElementTypeRendererHtml
buf.write('</span>&gt;');
buf.write('</span>');
}
return buf.toString();
return wrapNullability(elementType, buf.toString());
}

@override
Expand All @@ -72,7 +79,8 @@ class ParameterizedElementTypeRendererHtml
'</span>, <span class="type-parameter">');
buf.write('</span>&gt;');
}
return buf.toString();
buf.write(elementType.nullabilitySuffix);
return wrapNullability(elementType, buf.toString());
}
}

Expand All @@ -92,7 +100,7 @@ class CallableElementTypeRendererHtml
.trim());
buf.write(') → ');
buf.write(elementType.returnType.linkedName);
return buf.toString();
return wrapNullabilityParens(elementType, buf.toString());
}
}

Expand All @@ -108,7 +116,7 @@ class FunctionTypeElementTypeRendererMd
buf.write('(');
buf.write(ParameterRendererMd().renderLinkedParams(elementType.parameters));
buf.write(')');
return buf.toString();
return wrapNullabilityParens(elementType, buf.toString());
}

@override
Expand All @@ -122,7 +130,7 @@ class FunctionTypeElementTypeRendererMd
buf.write('>');
}
}
return buf.toString();
return wrapNullabilityParens(elementType, buf.toString());
}
}

Expand All @@ -138,7 +146,7 @@ class ParameterizedElementTypeRendererMd
buf.writeAll(elementType.typeArguments.map((t) => t.linkedName), ', ');
buf.write('>');
}
return buf.toString();
return wrapNullability(elementType, buf.toString());
}

@override
Expand All @@ -152,7 +160,8 @@ class ParameterizedElementTypeRendererMd
elementType.typeArguments.map((t) => t.nameWithGenerics), ', ');
buf.write('>');
}
return buf.toString();
buf.write(elementType.nullabilitySuffix);
return wrapNullability(elementType, buf.toString());
}
}

Expand All @@ -172,6 +181,6 @@ class CallableElementTypeRendererMd
.trim());
buf.write(') → ');
buf.write(elementType.returnType.linkedName);
return buf.toString();
return wrapNullabilityParens(elementType, buf.toString());
}
}
Loading