blob: dbf4e147558f45aded92318b2392f4379f70269b [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/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/error/error.dart';
import '../analyzer.dart';
import '../ast.dart';
import '../extensions.dart';
const _desc = r'Document all public members.';
// 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 {
PublicMemberApiDocs()
: super(name: LintNames.public_member_api_docs, description: _desc);
@override
DiagnosticCode get diagnosticCode => LinterLintCode.publicMemberApiDocs;
@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
var package = context.package;
if (package != null && !package.canHavePublicApi) {
return;
}
if (!context.isInLibDir) return;
var visitor = _Visitor(this, context);
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.addExtensionDeclaration(this, visitor);
registry.addExtensionTypeDeclaration(this, visitor);
registry.addFieldDeclaration(this, visitor);
registry.addFunctionTypeAlias(this, visitor);
registry.addGenericTypeAlias(this, visitor);
registry.addMixinDeclaration(this, visitor);
registry.addTopLevelVariableDeclaration(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;
final RuleContext context;
_Visitor(this.rule, this.context);
bool check(Declaration node) {
if (node.isInternal) return false;
if (node.documentationComment == null && !isOverridingMember(node)) {
var errorNode = getNodeToAnnotate(node);
rule.reportAtOffset(errorNode.offset, errorNode.length);
return true;
}
return false;
}
void checkMethods(List<ClassMember> members) {
var getters = <String, MethodDeclaration>{};
var setters = <MethodDeclaration>[];
// Non-getters/setters.
var methods = <MethodDeclaration>[];
// Identify getter/setter pairs.
for (var member in members) {
if (member is MethodDeclaration && !member.name.isPrivate) {
if (member.isGetter) {
getters[member.name.lexeme] = member;
} else if (member.isSetter) {
setters.add(member);
} else {
methods.add(member);
}
}
}
// Check all getters, and collect offenders along the way.
var missingDocs = <MethodDeclaration>{};
for (var getter in getters.values) {
if (check(getter)) {
missingDocs.add(getter);
}
}
// But only setters whose getter is missing a doc.
for (var setter in setters) {
var getter = getters[setter.name.lexeme];
if (getter != null && missingDocs.contains(getter)) {
check(setter);
}
}
// Check remaining methods.
methods.forEach(check);
}
/// Whether [node] overrides some other member.
bool isOverridingMember(Declaration node) =>
node.declaredFragment?.element.overriddenMember != null;
@override
void visitClassDeclaration(ClassDeclaration node) {
if (node.declaredFragment?.element == null) return;
_visitMembers(node, node.name, node.members);
}
@override
void visitClassTypeAlias(ClassTypeAlias node) {
if (!node.name.isPrivate) {
check(node);
}
}
@override
void visitCompilationUnit(CompilationUnit node) {
var getters = <String, FunctionDeclaration>{};
var setters = <FunctionDeclaration>[];
// Non-getters/setters.
var functions = <FunctionDeclaration>[];
// Identify getter/setter pairs.
for (var member in node.declarations) {
if (member is FunctionDeclaration) {
var name = member.name;
if (!name.isPrivate && name.lexeme != 'main') {
if (member.isGetter) {
getters[member.name.lexeme] = member;
} else if (member.isSetter) {
setters.add(member);
} else {
functions.add(member);
}
}
}
}
// Check all getters, and collect offenders along the way.
var missingDocs = <FunctionDeclaration>{};
for (var getter in getters.values) {
if (check(getter)) {
missingDocs.add(getter);
}
}
// But only setters whose getter is missing a doc.
for (var setter in setters) {
var getter = getters[setter.name.lexeme];
if (getter != null && missingDocs.contains(getter)) {
check(setter);
}
}
// Check remaining functions.
functions.forEach(check);
super.visitCompilationUnit(node);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
if (node.inPrivateMember || node.name.isPrivate) return;
var parent = node.parent;
if (parent is EnumDeclaration) return;
if (parent != null && parent.isEffectivelyPrivate) return;
check(node);
}
@override
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
if (!node.inPrivateMember && !node.name.isPrivate) {
check(node);
}
}
@override
void visitEnumDeclaration(EnumDeclaration node) {
if (node.name.isPrivate) return;
check(node);
checkMethods(node.members);
}
@override
void visitExtensionDeclaration(ExtensionDeclaration node) {
if (node.name == null || node.name.isPrivate) return;
if (node.isInternal) return;
check(node);
checkMethods(node.members);
}
@override
void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
if (node.declaredFragment?.element == null) return;
_visitMembers(node, node.name, node.members);
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
// TODO(pq): update this to be called from the parent (like with visitMembers)
if (node.isInternal) return;
if (node.inPrivateMember) return;
if (node.isInvalidExtensionTypeField) return;
for (var field in node.fields.variables) {
if (!field.name.isPrivate) {
check(field);
}
}
}
@override
void visitFunctionTypeAlias(FunctionTypeAlias node) {
if (!node.name.isPrivate) {
check(node);
}
}
@override
void visitGenericTypeAlias(GenericTypeAlias node) {
if (!node.name.isPrivate) {
check(node);
}
}
@override
void visitMixinDeclaration(MixinDeclaration node) {
_visitMembers(node, node.name, node.members);
}
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
for (var variable in node.variables.variables) {
if (!variable.name.isPrivate) {
check(variable);
}
}
}
void _visitMembers(Declaration node, Token name, List<ClassMember> members) {
if (name.isPrivate) return;
if (node.isInternal) return;
check(node);
checkMethods(members);
}
}