blob: 5d13c221460968f5563c4ca8fe2e024925c04742 [file] [log] [blame]
// Copyright (c) 2016, 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_resolution_map.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:linter/src/analyzer.dart';
import 'package:linter/src/ast.dart';
const _desc = r'Document all public members.';
const _details = r'''
**DO** document all public members.
All non-overriding public members should be documented with `///` doc-style
comments.
**GOOD:**
```
/// A good thing.
abstract class Good {
/// Start doing your thing.
void start() => _start();
_start();
}
```
**BAD:**
```
class Bad {
void meh() { }
}
```
In case a public member overrides a member it is up to the declaring member
to provide documentation. For example, in the following, `Sub` needn't
document `init` (though it certainly may, if there's need).
**GOOD:**
```
/// Base of all things.
abstract class Base {
/// Initialize the base.
void init();
}
/// A sub base.
class Sub extends Base {
@override
void init() { ... }
}
```
Note that consistent with `dartdoc`, an exception to the rule is made when
documented getters have corresponding undocumented setters. In this case the
setters inherit the docs from the getters.
''';
// TODO(devoncarew): This lint is very slow - we should profile and optimize it.
// TODO(devoncarew): Longer term, this lint could benefit from being more aware
// of the actual API surface area of a package - including that defined by
// exports - and linting against that.
class PublicMemberApiDocs extends LintRule implements NodeLintRule {
PublicMemberApiDocs()
: super(
name: 'public_member_api_docs',
description: _desc,
details: _details,
group: Group.style);
@override
void registerNodeProcessors(NodeLintRegistry registry,
[LinterContext context]) {
final visitor = new _Visitor(this);
registry.addClassDeclaration(this, visitor);
registry.addClassTypeAlias(this, visitor);
registry.addCompilationUnit(this, visitor);
registry.addConstructorDeclaration(this, visitor);
registry.addEnumConstantDeclaration(this, visitor);
registry.addEnumDeclaration(this, visitor);
registry.addFieldDeclaration(this, visitor);
registry.addFunctionTypeAlias(this, visitor);
registry.addTopLevelVariableDeclaration(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor {
InheritanceManager manager;
bool isInLibFolder;
final LintRule rule;
_Visitor(this.rule);
bool check(Declaration node) {
if (node.documentationComment == null && !isOverridingMember(node)) {
rule.reportLint(getNodeToAnnotate(node));
return true;
}
return false;
}
ExecutableElement getOverriddenMember(Element member) {
if (member == null || manager == null) {
return null;
}
ClassElement classElement =
member.getAncestor((element) => element is ClassElement);
if (classElement == null) {
return null;
}
return manager.lookupInheritance(classElement, member.name);
}
bool isOverridingMember(Declaration node) =>
getOverriddenMember(node.declaredElement) != null;
@override
visitClassDeclaration(ClassDeclaration node) {
if (!isInLibFolder) return;
if (isPrivate(node.name)) return;
check(node);
// Check methods
Map<String, MethodDeclaration> getters = <String, MethodDeclaration>{};
List<MethodDeclaration> setters = <MethodDeclaration>[];
// Non-getters/setters.
List<MethodDeclaration> methods = <MethodDeclaration>[];
// Identify getter/setter pairs.
for (ClassMember member in node.members) {
if (member is MethodDeclaration && !isPrivate(member.name)) {
if (member.isGetter) {
getters[member.name.name] = member;
} else if (member.isSetter) {
setters.add(member);
} else {
methods.add(member);
}
}
}
// Check all getters, and collect offenders along the way.
Set<MethodDeclaration> missingDocs = Set<MethodDeclaration>();
for (MethodDeclaration getter in getters.values) {
if (check(getter)) {
missingDocs.add(getter);
}
}
// But only setters whose getter is missing a doc.
for (MethodDeclaration setter in setters) {
MethodDeclaration getter = getters[setter.name.name];
if (getter == null) {
// Look for an inherited getter.
ExecutableElement getter =
manager.lookupMember(node.declaredElement, setter.name.name);
if (getter is PropertyAccessorElement) {
if (getter.documentationComment != null) {
continue;
}
}
check(setter);
} else if (missingDocs.contains(getter)) {
check(setter);
}
}
// Check remaining methods.
methods.forEach(check);
}
@override
visitClassTypeAlias(ClassTypeAlias node) {
if (!isInLibFolder) return;
if (!isPrivate(node.name)) {
check(node);
}
}
@override
visitCompilationUnit(CompilationUnit node) {
// Ignore this compilation unit if its not in the lib/ folder.
isInLibFolder = isDefinedInLib(node);
if (!isInLibFolder) return;
LibraryElement library = node == null
? null
: resolutionMap.elementDeclaredByCompilationUnit(node)?.library;
manager = library == null ? null : new InheritanceManager(library);
Map<String, FunctionDeclaration> getters = <String, FunctionDeclaration>{};
List<FunctionDeclaration> setters = <FunctionDeclaration>[];
// Check functions.
// Non-getters/setters.
List<FunctionDeclaration> functions = <FunctionDeclaration>[];
// Identify getter/setter pairs.
for (CompilationUnitMember member in node.declarations) {
if (member is FunctionDeclaration) {
Identifier name = member.name;
if (!isPrivate(name) && name.name != 'main') {
if (member.isGetter) {
getters[member.name.name] = member;
} else if (member.isSetter) {
setters.add(member);
} else {
functions.add(member);
}
}
}
}
// Check all getters, and collect offenders along the way.
Set<FunctionDeclaration> missingDocs = Set<FunctionDeclaration>();
for (FunctionDeclaration getter in getters.values) {
if (check(getter)) {
missingDocs.add(getter);
}
}
// But only setters whose getter is missing a doc.
for (FunctionDeclaration setter in setters) {
FunctionDeclaration getter = getters[setter.name.name];
if (getter != null && missingDocs.contains(getter)) {
check(setter);
}
}
// Check remaining functions.
functions.forEach(check);
super.visitCompilationUnit(node);
}
@override
visitConstructorDeclaration(ConstructorDeclaration node) {
if (!isInLibFolder) return;
if (!inPrivateMember(node) && !isPrivate(node.name)) {
check(node);
}
}
@override
visitEnumConstantDeclaration(EnumConstantDeclaration node) {
if (!isInLibFolder) return;
if (!inPrivateMember(node) && !isPrivate(node.name)) {
check(node);
}
}
@override
visitEnumDeclaration(EnumDeclaration node) {
if (!isInLibFolder) return;
if (!isPrivate(node.name)) {
check(node);
}
}
@override
visitFieldDeclaration(FieldDeclaration node) {
if (!isInLibFolder) return;
if (!inPrivateMember(node)) {
for (VariableDeclaration field in node.fields.variables) {
if (!isPrivate(field.name)) {
check(field);
}
}
}
}
@override
visitFunctionTypeAlias(FunctionTypeAlias node) {
if (!isInLibFolder) return;
if (!isPrivate(node.name)) {
check(node);
}
}
@override
visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
if (!isInLibFolder) return;
for (VariableDeclaration decl in node.variables.variables) {
if (!isPrivate(decl.name)) {
check(decl);
}
}
}
}