blob: aab78cf9d188f8c8c7f9915ea9ffb526a53b88b9 [file] [log] [blame]
// Copyright (c) 2022, 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 'package:analyzer/src/dart/ast/token.dart'; // ignore: implementation_imports
import '../analyzer.dart';
const _desc = r'Attach library doc comments to library directives.';
class DanglingLibraryDocComments extends LintRule {
DanglingLibraryDocComments()
: super(name: LintNames.dangling_library_doc_comments, description: _desc);
@override
DiagnosticCode get diagnosticCode =>
LinterLintCode.danglingLibraryDocComments;
@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
var visitor = _Visitor(this);
registry.addCompilationUnit(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
final DanglingLibraryDocComments rule;
_Visitor(this.rule);
@override
void visitCompilationUnit(CompilationUnit node) {
if (node.directives.isNotEmpty) {
// Only consider a doc comment on the first directive. Doc comments on
// other directives do not have the appearance of documenting the library.
var firstDirective = node.directives.first;
if (firstDirective is LibraryDirective) {
// Given the presence of library directive, don't worry about later doc
// comments in the library.
return;
}
if (firstDirective is PartOfDirective) {
// Don't worry about possible "library doc comments" in a part.
return;
}
var docComment = firstDirective.documentationComment;
if (docComment != null) {
rule.reportAtToken(docComment.beginToken);
return;
}
return;
}
if (node.declarations.isEmpty) {
// Without any declarations, we only need to check for a doc comment as
// the last thing in a file.
Token? endComment = node.endToken.precedingComments;
while (endComment is CommentToken) {
if (endComment is DocumentationCommentToken) {
rule.reportAtToken(endComment);
}
endComment = endComment.next;
}
return;
}
var firstDeclaration = node.declarations.first;
var docComment = firstDeclaration.documentationComment;
if (docComment == null) {
return;
}
var lineInfo = node.lineInfo;
if (docComment.tokens.length > 1) {
for (var i = 0; i < docComment.tokens.length - 1; i++) {
var commentToken = docComment.tokens[i];
var followingCommentToken = docComment.tokens[i + 1];
var commentEndLine = lineInfo.getLocation(commentToken.end).lineNumber;
var followingCommentLine = lineInfo
.getLocation(followingCommentToken.offset)
.lineNumber;
if (followingCommentLine > commentEndLine + 1) {
// There is a blank line within the declaration's doc comments.
rule.reportAtToken(commentToken);
return;
}
}
}
// We must walk through the comments following the doc comment, tracking
// pairs of consecutive comments so as to check whether any two are
// separated by a blank line.
var commentToken = docComment.endToken;
var followingCommentToken = commentToken.next;
while (followingCommentToken != null) {
// Any blank line between the doc comment and following comments makes
// the doc comment look dangling.
var commentEndLine = lineInfo.getLocation(commentToken.end).lineNumber;
var followingCommentLine = lineInfo
.getLocation(followingCommentToken.offset)
.lineNumber;
if (followingCommentLine > commentEndLine + 1) {
// There is a blank line between the declaration's doc comment and the
// declaration.
rule.reportAtNode(docComment);
return;
}
commentToken = followingCommentToken;
followingCommentToken = followingCommentToken.next;
}
var commentEndLine = lineInfo.getLocation(commentToken.end).lineNumber;
// The syntactic entity to which a comment is "attached" is the
// [Comment]'s `parent`, not its `endToken`'s `next` [Token].
var tokenAfterDocComment =
(docComment.endToken as DocumentationCommentToken).parent;
if (tokenAfterDocComment == null) {
// We shouldn't get here as the doc comment is attached to
// [firstDeclaration].
return;
}
var declarationStartLine = lineInfo
.getLocation(tokenAfterDocComment.offset)
.lineNumber;
if (declarationStartLine > commentEndLine + 1) {
// There is a blank line between the declaration's doc comment and the
// declaration.
rule.reportAtNode(docComment);
}
}
}