blob: 759cdd070a03ac6e99f80efdfe79e8d270d5b487 [file] [log] [blame] [edit]
// Copyright (c) 2019, 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 'dart:convert';
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/parameter.dart';
/// Render HTML in an extended vertical format using an `<ol>` tag.
class ParameterRendererHtmlList extends ParameterRendererHtml {
const ParameterRendererHtmlList();
@override
bool get isBlock => true;
@override
String listItem(String item) => '<li>$item</li>\n';
@override
// TODO(jcollins-g): consider comma separated lists and more advanced CSS.
String orderedList(
String listItems, {
bool multiLine = false,
String openingBracket = '',
String closingBracket = '',
}) {
var classes = [
'parameter-list',
if (!multiLine) 'single-line',
];
return '$openingBracket'
'<ol class="${classes.join(' ')}"> $listItems</ol>'
'$closingBracket';
}
}
/// Render HTML suitable for a single, wrapped line.
class ParameterRendererHtml extends ParameterRenderer {
const ParameterRendererHtml();
@override
bool get isBlock => false;
@override
String listItem(String item) => item;
@override
String orderedList(
String listItems, {
bool multiLine = false,
String openingBracket = '',
String closingBracket = '',
}) =>
'$openingBracket$listItems$closingBracket';
@override
String annotation(String annotation) => '<span>$annotation</span>';
@override
String covariant(String covariant) => '<span>$covariant</span>';
@override
String defaultValue(String defaultValue) {
var escaped =
const HtmlEscape(HtmlEscapeMode.unknown).convert(defaultValue);
return '<span class="default-value">$escaped</span>';
}
@override
String parameter(String parameter, String id) =>
'<span class="parameter" id="$id">$parameter</span>';
@override
String parameterName(String parameterName) =>
'<span class="parameter-name">$parameterName</span>';
@override
String typeName(String typeName) =>
'<span class="type-annotation">$typeName</span>';
@override
String required(String required) => '<span>$required</span>';
}
abstract class ParameterRenderer {
const ParameterRenderer();
bool get isBlock;
String listItem(String item);
String orderedList(
String listItems, {
bool multiLine = false,
String openingBracket = '',
String closingBracket = '',
});
String annotation(String annotation);
String covariant(String covariant);
String defaultValue(String defaultValue);
String parameter(String parameter, String id);
String parameterName(String parameterName);
String typeName(String typeName);
String required(String required);
String renderLinkedParams(List<Parameter> parameters,
{bool showMetadata = true}) {
if (parameters.isEmpty) {
return '';
}
var positionalParams = parameters
.where((Parameter p) => p.isRequiredPositional)
.toList(growable: false);
var optionalPositionalParams = parameters
.where((Parameter p) => p.isOptionalPositional)
.toList(growable: false);
var namedParams =
parameters.where((Parameter p) => p.isNamed).toList(growable: false);
var isMultiline =
isBlock && (namedParams.isNotEmpty || parameters.length > 3);
var buffer = StringBuffer();
// If there are no required parameters, but optional positional or named
// parameters, then we start with an opening bracket.
var openingBracket = '';
// If there are any optional positional or named parameters, then we end
// with a closing bracket.
var closingBracket = '';
if (positionalParams.isEmpty) {
if (optionalPositionalParams.isNotEmpty) {
openingBracket = '[';
} else if (namedParams.isNotEmpty) {
openingBracket = '{';
}
} else {
// Determine whether the line should end with an opening delimiter for
// optional parameters.
String trailingText;
if (optionalPositionalParams.isNotEmpty) {
trailingText = ', [';
} else if (namedParams.isNotEmpty) {
trailingText = ', {';
} else if (isMultiline) {
trailingText = ', ';
} else {
trailingText = '';
}
for (var p in positionalParams) {
var suffix = identical(p, positionalParams.last) ? trailingText : ', ';
buffer.write(
_renderParameter(p, showMetadata: showMetadata, suffix: suffix));
}
}
if (optionalPositionalParams.isNotEmpty) {
closingBracket = ']';
for (var p in optionalPositionalParams) {
var suffix = isMultiline || !identical(p, optionalPositionalParams.last)
? ', '
: '';
buffer.write(
_renderParameter(p, showMetadata: showMetadata, suffix: suffix));
}
}
if (namedParams.isNotEmpty) {
closingBracket = '}';
for (var p in namedParams) {
var suffix = isMultiline || !identical(p, namedParams.last) ? ', ' : '';
buffer.write(
_renderParameter(p, showMetadata: showMetadata, suffix: suffix));
}
}
return orderedList(
buffer.toString(),
multiLine: isMultiline,
openingBracket: openingBracket,
closingBracket: closingBracket,
);
}
String _renderParameter(
Parameter param, {
required bool showMetadata,
required String suffix,
}) {
final buffer = StringBuffer();
final modelType = param.modelType;
if (showMetadata && param.hasAnnotations) {
for (final a in param.annotations) {
buffer.write('${annotation(a.linkedNameWithParameters)} ');
}
}
if (param.isRequiredNamed) {
buffer.write('${required('required')} ');
}
if (param.isCovariant) {
buffer.write('${covariant('covariant')} ');
}
if (modelType is Callable) {
final returnTypeName = modelType.isTypedef
? modelType.linkedName
: modelType.returnType.linkedName;
buffer.write(typeName(returnTypeName));
buffer.write(' ${parameterName(param.name)}');
// Writes out the generic type parameters for a function type.
// TODO(kallentu): Pull this type parameter generation into a helper for
// other renderers that also do this same work.
if (modelType is FunctionTypeElementType) {
if (modelType.typeFormals.isNotEmpty) {
if (!modelType.typeFormals.every((t) => t.name == 'dynamic')) {
buffer
..write('&lt;<wbr><span class="type-parameter">')
..writeAll(modelType.typeFormals.map((t) => t.name),
'</span>, <span class="type-parameter">')
..write('</span>&gt;');
}
}
}
if (!modelType.isTypedef && modelType is DefinedElementType) {
buffer.write('(');
buffer.write(renderLinkedParams(
(modelType as DefinedElementType).modelElement.parameters,
showMetadata: showMetadata,
));
buffer.write(')');
buffer.write(modelType.nullabilitySuffix);
}
if (!modelType.isTypedef) {
buffer.write('(');
buffer.write(renderLinkedParams(
modelType.parameters,
showMetadata: showMetadata,
));
buffer.write(')');
buffer.write(modelType.nullabilitySuffix);
}
} else {
final linkedTypeName = modelType.linkedName;
if (linkedTypeName.isNotEmpty) {
buffer.write(typeName(linkedTypeName));
if (param.name.isNotEmpty) {
buffer.write(' ');
}
}
if (param.name.isNotEmpty) {
buffer.write(parameterName(param.name));
}
}
if (param.hasDefaultValue) {
buffer.write(' = ');
buffer.write(defaultValue(param.defaultValue!));
}
buffer.write(suffix);
var wrappedParameter = parameter(buffer.toString(), param.htmlId);
return listItem(wrappedParameter);
}
}