blob: 896e077136b66302cbd71b9c6718bb98beb06a4c [file] [log] [blame]
// Copyright (c) 2015, 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.
/// A library containing an abstract documentation generator.
library;
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/generator/generator_backend.dart';
import 'package:dartdoc/src/logging.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/runtime_stats.dart';
import 'package:dartdoc/src/warnings.dart';
abstract class FileWriter {
/// All filenames written by this generator.
Set<String> get writtenFiles;
/// Writes [content] to a file at [filePath].
///
/// If a file is to be overwritten, a warning will be reported on [element].
void write(String filePath, String content, {Warnable? element});
/// Writes [content] to a file at [filePath].
///
/// If a file is to be overwritten, a warning will be reported, unless
/// [allowOverwrite] is `true`.
void writeBytes(
String filePath,
List<int> content, {
bool allowOverwrite = false,
});
}
/// A generator generates documentation for a given package.
///
/// Generators can generate documentation in different formats: HTML, JSON, etc.
class Generator {
final GeneratorBackend _generatorBackend;
Generator(this._generatorBackend);
Future<void> generate(PackageGraph? packageGraph) async {
await _generatorBackend.generateAdditionalFiles();
if (packageGraph == null) {
return;
}
var indexElements = _generateDocs(packageGraph);
var categories = indexElements
.whereType<Categorization>()
.where((e) => e.hasCategorization)
.toList(growable: false);
_generatorBackend.generateCategoryJson(categories);
_generatorBackend.generateSearchIndex(indexElements);
}
Set<String> get writtenFiles => _generatorBackend.writer.writtenFiles;
/// Traverses the [packageGraph] and generates documentation for all contained
/// elements.
List<Documentable> _generateDocs(PackageGraph packageGraph) {
runtimeStats.resetAccumulators({
'writtenCategoryFileCount',
'writtenClassFileCount',
'writtenConstructorFileCount',
'writtenEnumFileCount',
'writtenExtensionFileCount',
'writtenExtensionTypeFileCount',
'writtenFunctionFileCount',
'writtenLibraryFileCount',
'writtenMethodFileCount',
'writtenMixinFileCount',
'writtenPackageFileCount',
'writtenPropertyFileCount',
'writtenSidebarFileCount',
'writtenTopLevelPropertyFileCount',
'writtenTypedefFileCount'
});
_generatorBackend.generatePackage(
packageGraph, packageGraph.defaultPackage);
var indexAccumulator = <Documentable>[];
var multiplePackages = packageGraph.localPackages.length > 1;
void generateConstants(Container container, Library library) {
for (var constant in container.constantFields.whereDocumented) {
if (!constant.isCanonical) continue;
indexAccumulator.add(constant);
_generatorBackend.generateProperty(
packageGraph, library, container, constant);
}
}
void generateConstructors(Constructable constructable, Library library) {
for (var constructor in constructable.constructors.whereDocumented) {
if (!constructor.isCanonical) continue;
indexAccumulator.add(constructor);
_generatorBackend.generateConstructor(
packageGraph, library, constructable, constructor);
}
}
void generateInstanceMethods(Container container, Library library) {
for (var method in container.instanceMethods.whereDocumented) {
if (!method.isCanonical) continue;
indexAccumulator.add(method);
_generatorBackend.generateMethod(
packageGraph, library, container, method);
}
}
void generateInstanceOperators(Container container, Library library) {
for (var operator in container.instanceOperators.whereDocumented) {
if (!operator.isCanonical) continue;
indexAccumulator.add(operator);
_generatorBackend.generateMethod(
packageGraph, library, container, operator);
}
}
void generateInstanceProperties(Container container, Library library) {
for (var property in container.instanceFields.whereDocumented) {
if (!property.isCanonical) continue;
indexAccumulator.add(property);
_generatorBackend.generateProperty(
packageGraph, library, container, property);
}
}
void generateStaticMethods(Container container, Library library) {
for (var method in container.staticMethods.whereDocumented) {
if (!method.isCanonical) continue;
indexAccumulator.add(method);
_generatorBackend.generateMethod(
packageGraph, library, container, method);
}
}
void generateStaticProperties(Container container, Library library) {
for (var property in container.variableStaticFields.whereDocumented) {
if (!property.isCanonical) continue;
indexAccumulator.add(property);
_generatorBackend.generateProperty(
packageGraph, library, container, property);
}
}
for (var package in packageGraph.localPackages) {
if (multiplePackages) {
logInfo('Generating docs for package ${package.name}...');
}
for (var category in package.categories.whereDocumented) {
logInfo('Generating docs for category ${category.name} from '
'${category.package.fullyQualifiedName}...');
indexAccumulator.add(category);
_generatorBackend.generateCategory(packageGraph, category);
}
for (var lib in package.libraries.whereDocumented) {
if (!multiplePackages) {
logInfo('Generating docs for library ${lib.breadcrumbName} from '
'${lib.element.firstFragment.source.uri}...');
}
if (!lib.isAnonymous && !lib.hasDocumentation) {
packageGraph.warnOnElement(lib, PackageWarning.noLibraryLevelDocs);
}
indexAccumulator.add(lib);
_generatorBackend.generateLibrary(packageGraph, lib);
for (var class_ in lib.classesAndExceptions.whereDocumentedIn(lib)) {
indexAccumulator.add(class_);
_generatorBackend.generateClass(packageGraph, lib, class_);
var canonicalLibrary = class_.canonicalLibraryOrThrow;
generateConstants(class_, canonicalLibrary);
generateConstructors(class_, canonicalLibrary);
generateInstanceMethods(class_, canonicalLibrary);
generateInstanceOperators(class_, canonicalLibrary);
generateInstanceProperties(class_, canonicalLibrary);
generateStaticMethods(class_, canonicalLibrary);
generateStaticProperties(class_, canonicalLibrary);
}
for (var extension in lib.extensions.whereDocumentedIn(lib)) {
indexAccumulator.add(extension);
_generatorBackend.generateExtension(packageGraph, lib, extension);
var canonicalLibrary = extension.canonicalLibraryOrThrow;
generateConstants(extension, canonicalLibrary);
generateInstanceMethods(extension, canonicalLibrary);
generateInstanceOperators(extension, canonicalLibrary);
generateInstanceProperties(extension, canonicalLibrary);
generateStaticMethods(extension, canonicalLibrary);
generateStaticProperties(extension, canonicalLibrary);
}
for (var extensionType in lib.extensionTypes.whereDocumentedIn(lib)) {
indexAccumulator.add(extensionType);
_generatorBackend.generateExtensionType(
packageGraph, lib, extensionType);
var canonicalLibrary = extensionType.canonicalLibraryOrThrow;
generateConstants(extensionType, canonicalLibrary);
generateConstructors(extensionType, canonicalLibrary);
generateInstanceMethods(extensionType, canonicalLibrary);
generateInstanceOperators(extensionType, canonicalLibrary);
generateInstanceProperties(extensionType, canonicalLibrary);
generateStaticMethods(extensionType, canonicalLibrary);
generateStaticProperties(extensionType, canonicalLibrary);
}
for (var mixin in lib.mixins.whereDocumentedIn(lib)) {
indexAccumulator.add(mixin);
_generatorBackend.generateMixin(packageGraph, lib, mixin);
var canonicalLibrary = mixin.canonicalLibraryOrThrow;
generateConstants(mixin, canonicalLibrary);
generateInstanceMethods(mixin, canonicalLibrary);
generateInstanceOperators(mixin, canonicalLibrary);
generateInstanceProperties(mixin, canonicalLibrary);
generateStaticMethods(mixin, canonicalLibrary);
generateStaticProperties(mixin, canonicalLibrary);
}
for (var enum_ in lib.enums.whereDocumentedIn(lib)) {
indexAccumulator.add(enum_);
_generatorBackend.generateEnum(packageGraph, lib, enum_);
var canonicalLibrary = enum_.canonicalLibraryOrThrow;
generateConstants(enum_, canonicalLibrary);
generateConstructors(enum_, canonicalLibrary);
generateInstanceMethods(enum_, canonicalLibrary);
generateInstanceOperators(enum_, canonicalLibrary);
generateInstanceProperties(enum_, canonicalLibrary);
generateStaticMethods(enum_, canonicalLibrary);
generateStaticProperties(enum_, canonicalLibrary);
}
for (var constant in lib.constants.whereDocumentedIn(lib)) {
indexAccumulator.add(constant);
_generatorBackend.generateTopLevelProperty(
packageGraph, lib, constant);
}
for (var property in lib.properties.whereDocumentedIn(lib)) {
indexAccumulator.add(property);
_generatorBackend.generateTopLevelProperty(
packageGraph, lib, property);
}
for (var function in lib.functions.whereDocumentedIn(lib)) {
indexAccumulator.add(function);
_generatorBackend.generateFunction(packageGraph, lib, function);
}
for (var typeDef in lib.typedefs.whereDocumentedIn(lib)) {
indexAccumulator.add(typeDef);
_generatorBackend.generateTypeDef(packageGraph, lib, typeDef);
}
}
}
return indexAccumulator;
}
}
List<DartdocOption> createGeneratorOptions(
PackageMetaProvider packageMetaProvider) {
var resourceProvider = packageMetaProvider.resourceProvider;
return [
DartdocOptionArgFile<List<String>>('footer', [], resourceProvider,
optionIs: OptionKind.file,
help:
'Paths to files with content to add to page footers, but possibly '
'outside of dedicated footer elements for the generator (e.g. '
'outside of <footer> for an HTML generator). To add text content '
'to dedicated footer elements, use --footer-text instead.',
mustExist: true,
splitCommas: true),
DartdocOptionArgFile<List<String>>('footerText', [], resourceProvider,
optionIs: OptionKind.file,
help: 'Paths to files with content to add to page footers (next to the '
'package name and version).',
mustExist: true,
splitCommas: true),
DartdocOptionArgFile<List<String>>('header', [], resourceProvider,
optionIs: OptionKind.file,
help: 'Paths to files with content to add to page headers.',
splitCommas: true),
DartdocOptionArgOnly<bool>('prettyIndexJson', false, resourceProvider,
help:
'Generates `index.json` with indentation and newlines. The file is '
'larger, but it\'s also easier to diff.',
negatable: false),
DartdocOptionArgFile<String?>('favicon', null, resourceProvider,
optionIs: OptionKind.file,
help: 'A path to a favicon for the generated docs.',
mustExist: true),
DartdocOptionArgOnly<String?>('relCanonicalPrefix', null, resourceProvider,
help:
'If provided, add a rel="canonical" prefixed with provided value. '
'Consider using if building many versions of the docs for public '
'SEO; learn more at https://goo.gl/gktN6F.'),
];
}