| // 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 { |
| InheritanceManager2 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; |
| } |
| Uri libraryUri = classElement.library.source.uri; |
| return manager |
| .getInherited(classElement.type, new Name(libraryUri, member.name)) |
| ?.element; |
| } |
| |
| 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 = <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) { |
| Uri libraryUri = node.declaredElement.library.source.uri; |
| // Look for an inherited getter. |
| ExecutableElement getter = manager |
| .getMember(node.declaredElement.type, |
| new Name(libraryUri, setter.name.name)) |
| ?.element; |
| 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 InheritanceManager2(library.context.typeSystem); |
| |
| 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 = <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); |
| } |
| } |
| } |
| } |