blob: b625f08fa61f66dbd5366b20303bb14e3a905df3 [file] [log] [blame]
// 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/comment_references/parser.dart';
import 'package:dartdoc/src/failure.dart';
import 'package:meta/meta.dart';
import '../model/model.dart';
const _validFormats = {'html', 'md'};
/// This class defines an interface to allow [ModelElement]s and [Generator]s
/// to get information about the desired on-disk representation of a single
/// [ModelElement]. None of these getters should be considered valid unless
/// [modelElement.isCanonical] is true. Implementations of this class can
/// define which elements have their own files and which do not, how
/// to lay them out on disk, and how to link different pages in the structure
/// together.
abstract class FileStructure {
factory FileStructure.fromDocumentable(Documentable documentable) {
/// This assumes all remote packages are HTML.
/// Add configurability for remote formats in dartdoc_options if changing
/// that becomes desireable.
var format = documentable.config.format;
if (documentable.package.documentedWhere == DocumentLocation.remote) {
format = 'html';
}
if (!_validFormats.contains(format)) {
throw DartdocFailure('Internal error: unrecognized format: $format');
}
return switch (documentable) {
LibraryContainer() =>
// [LibraryContainer]s are not ModelElements, but have documentation.
FileStructure._fromLibraryContainer(documentable, format),
ModelElement() =>
// This should be the common case.
FileStructure._fromModelElement(documentable, format),
_ => throw UnimplementedError(
'Tried to build a FileStructure for an unknown subtype of Documentable: ${documentable.runtimeType}')
};
}
factory FileStructure._fromLibraryContainer(
LibraryContainer libraryContainer,
String format,
) =>
switch (libraryContainer) {
Category() => FileStructureImpl(format, libraryContainer.name, 'topic'),
Package() => FileStructureImpl(format, 'index', null),
_ => throw UnimplementedError(
'Unrecognized LibraryContainer subtype: ${libraryContainer.runtimeType}')
};
factory FileStructure._fromModelElement(
ModelElement modelElement, String format) {
return switch (modelElement) {
Library() => FileStructureImpl(format, modelElement.dirName, 'library'),
Mixin() => FileStructureImpl(format, modelElement.name, 'mixin'),
Class() => FileStructureImpl(format, modelElement.name, 'class'),
ExtensionType() =>
FileStructureImpl(format, modelElement.name, 'extension-type'),
Operator() => FileStructureImpl(format,
'operator_${operatorNames[modelElement.referenceName]}', null),
GetterSetterCombo() => FileStructureImpl(
format, modelElement.name, modelElement.isConst ? 'constant' : null),
_ => FileStructureImpl(format, modelElement.name, null)
};
}
/// True if an independent file should be created for this `ModelElement`.
bool get hasIndependentFile;
/// Returns a string suitable for use as an in-page anchor for this element in
/// its [ModelElement.enclosingElement] page.
String get htmlId;
/// Returns a link fragment relative to the HTML base for this `modelElement`.
/// Scrubbed of platform-local path separators. Must include an anchor
/// if [hasIndependentFile] is false.
String get href;
/// The file name for an independent file. Only valid if [hasIndependentFile]
/// is `true`. May contain platform-local path separators. Includes
/// the [fileType] and the [modelElement.enclosingElement]'s [dirName].
String get fileName;
/// The directory name that should contain any elements enclosed by
/// [modelElement].
String get dirName;
/// A type (generally "html" or "md") to be appended to the file name.
String get fileType;
}
@visibleForTesting
class FileStructureImpl implements FileStructure {
@override
final String fileType;
/// This is a name for the underlying [Documentable] that is free of
/// characters that can not appear in a path (URI, Unix, or Windows).
String pathSafeName;
/// This is a string to disambiguate the filename of the underlying
/// [Documentable] from other files with the same [pathSafeName] in the
/// same directory and is composed with [pathSafeName] to generate [fileName].
/// It is usually based on [ModelElement.kind], e.g. `'class'`. If null, no
/// disambiguating string will be added.
// TODO(jcollins-g): Legacy layout doesn't always include this; move toward
// always having a disambiguating string.
final String? kindAddition;
FileStructureImpl(this.fileType, this.pathSafeName, this.kindAddition);
@override
/// Initial implementation is bug-for-bug compatible with pre-extraction
/// dartdoc. This means that some types will have kindAdditions, and
/// some will not. See [FileStructure._fromModelElement].
String get fileName {
if (kindAddition != null) {
return '$pathSafeName-$kindAddition.$fileType';
}
return '$pathSafeName.$fileType';
}
@override
bool get hasIndependentFile => true;
@override
// TODO: implement href
String get href => throw UnimplementedError();
@override
// TODO: implement htmlId
String get htmlId => throw UnimplementedError();
@override
// TODO: implement dirName
String get dirName => throw UnimplementedError();
}