Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit b46ca2e

Browse files
committed
fix: handle multiline comments
1 parent 0f3199e commit b46ca2e

File tree

9 files changed

+331
-119
lines changed

9 files changed

+331
-119
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
* feat: support ignoring nesting for [`prefer-conditional-expressions`](https://dartcodemetrics.dev/docs/rules/common/prefer-conditional-expressions).
99
* fix: ignore Providers for ['avoid-returning-widgets'](https://dartcodemetrics.dev/docs/rules/common/avoid-returning-widgets).
1010
* feat: add [`use-setstate-synchronously`](https://dartcodemetrics.dev/docs/rules/flutter/use-setstate-synchronously).
11-
* fix: correctly invalidate edge cases for [`use-setstate-synchronously`](https://dartcodemetrics.dev/docs/rules/flutter/use-setstate-synchronously)
11+
* fix: correctly invalidate edge cases for [`use-setstate-synchronously`](https://dartcodemetrics.dev/docs/rules/flutter/use-setstate-synchronously).
12+
* fix: handle multiline comments for [`format-comment`](https://dartcodemetrics.dev/docs/rules/common/format-comment).
1213

1314
## 5.3.0
1415

lib/src/analyzers/lint_analyzer/rules/rules_list/format_comment/format_comment_rule.dart

Lines changed: 57 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import '../../models/common_rule.dart';
1313
import '../../rule_utils.dart';
1414

1515
part 'config_parser.dart';
16-
part 'models/comment_info.dart';
17-
part 'models/comment_type.dart';
1816
part 'visitor.dart';
1917

2018
class FormatCommentRule extends CommonRule {
@@ -53,61 +51,72 @@ class FormatCommentRule extends CommonRule {
5351
final visitor = _Visitor(
5452
_ignoredPatterns,
5553
_onlyDocComments,
56-
)..checkComments(source.unit.root);
54+
)..checkRegularComments(source.unit.root);
5755

58-
return [
59-
for (final comment in visitor.comments)
60-
createIssue(
56+
source.unit.visitChildren(visitor);
57+
58+
final issues = <Issue>[];
59+
60+
for (final comment in visitor.comments) {
61+
if (comment is _DocCommentInfo) {
62+
issues.add(createIssue(
6163
rule: this,
62-
location: nodeLocation(
63-
node: comment.token,
64-
source: source,
65-
),
64+
location: nodeLocation(node: comment.comment, source: source),
6665
message: _warning,
67-
replacement: _createReplacement(comment),
68-
),
69-
];
66+
replacement: _docCommentReplacement(comment.comment),
67+
));
68+
}
69+
if (comment is _RegularCommentInfo) {
70+
issues.addAll(
71+
comment.tokens
72+
.map((token) => createIssue(
73+
rule: this,
74+
location: nodeLocation(node: token, source: source),
75+
message: _warning,
76+
replacement: _regularCommentReplacement(
77+
token,
78+
comment.tokens.length == 1,
79+
),
80+
))
81+
.toList(),
82+
);
83+
}
84+
}
85+
86+
return issues;
7087
}
7188

72-
Replacement _createReplacement(_CommentInfo commentInfo) {
73-
final commentToken = commentInfo.token;
74-
var resultString = commentToken.toString();
75-
76-
switch (commentInfo.type) {
77-
case _CommentType.base:
78-
String commentText;
79-
80-
final isHasNextComment = commentToken.next != null &&
81-
commentToken.next!.type == TokenType.SINGLE_LINE_COMMENT &&
82-
commentToken.next!.offset ==
83-
commentToken.offset + resultString.length + 1;
84-
final subString = resultString.substring(2, resultString.length);
85-
86-
commentText = isHasNextComment
87-
? subString.trim().capitalize()
88-
: formatComment(subString);
89-
90-
resultString = '// $commentText';
91-
break;
92-
case _CommentType.documentation:
93-
final commentText =
94-
formatComment(resultString.substring(3, resultString.length));
95-
resultString = '/// $commentText';
96-
break;
89+
Replacement? _docCommentReplacement(Comment comment) {
90+
if (comment.tokens.length == 1) {
91+
final commentToken = comment.tokens.first;
92+
final text = commentToken.toString();
93+
final commentText = formatComment(text.substring(3, text.length));
94+
95+
return Replacement(
96+
comment: 'Format comment.',
97+
replacement: '/// $commentText',
98+
);
9799
}
98100

99-
return Replacement(
100-
comment: 'Format comment like sentences',
101-
replacement: resultString,
102-
);
101+
return null;
103102
}
104103

105-
String formatComment(String res) => res.trim().capitalize().replaceEnd();
106-
}
104+
Replacement? _regularCommentReplacement(Token token, bool isSingle) {
105+
if (isSingle) {
106+
final text = token.toString();
107+
final commentText = formatComment(text.substring(2, text.length));
108+
109+
return Replacement(
110+
comment: 'Format comment.',
111+
replacement: '// $commentText',
112+
);
113+
}
114+
115+
return null;
116+
}
107117

108-
const _punctuation = ['.', '!', '?'];
118+
String formatComment(String res) => replaceEnd(res.trim().capitalize());
109119

110-
extension _StringExtension on String {
111-
String replaceEnd() =>
112-
!_punctuation.contains(this[length - 1]) ? '$this.' : this;
120+
String replaceEnd(String text) =>
121+
!_punctuation.contains(text[text.length - 1]) ? '$text.' : text;
113122
}

lib/src/analyzers/lint_analyzer/rules/rules_list/format_comment/models/comment_info.dart

Lines changed: 0 additions & 8 deletions
This file was deleted.

lib/src/analyzers/lint_analyzer/rules/rules_list/format_comment/models/comment_type.dart

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 133 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
part of 'format_comment_rule.dart';
22

3-
const _commentsOperator = {
4-
_CommentType.base: '//',
5-
_CommentType.documentation: '///',
6-
};
3+
const _punctuation = ['.', '!', '?', ':'];
74

5+
final _sentencesRegExp = RegExp(r'(?<=([\.|:](?=\s|\n|$)))');
86
final _regMacrosExp = RegExp('{@(template|macro) .+}');
97
const _macrosEndExp = '{@endtemplate}';
108
const _ignoreExp = 'ignore:';
119
const _ignoreForFileExp = 'ignore_for_file:';
1210

1311
class _Visitor extends RecursiveAstVisitor<void> {
1412
final Iterable<RegExp> _ignoredPatterns;
13+
1514
final bool _onlyDocComments;
1615

1716
// ignore: avoid_positional_boolean_parameters
@@ -21,15 +20,59 @@ class _Visitor extends RecursiveAstVisitor<void> {
2120

2221
Iterable<_CommentInfo> get comments => _comments;
2322

24-
void checkComments(AstNode node) {
23+
@override
24+
void visitComment(Comment node) {
25+
super.visitComment(node);
26+
27+
if (node.isDocumentation) {
28+
final isValid = node.tokens.length == 1
29+
? _hasValidSingleLine(node.tokens.first, _CommentType.doc)
30+
: _hasValidMultiline(node.tokens, _CommentType.doc);
31+
if (!isValid) {
32+
_comments.add(_DocCommentInfo(node));
33+
}
34+
}
35+
}
36+
37+
void checkRegularComments(AstNode node) {
38+
if (_onlyDocComments) {
39+
return;
40+
}
41+
2542
Token? token = node.beginToken;
2643
while (token != null) {
44+
final extractedComments = <Token>[];
45+
2746
Token? commentToken = token.precedingComments;
2847
while (commentToken != null) {
29-
_commentValidation(commentToken);
48+
if (_isRegularComment(commentToken)) {
49+
extractedComments.add(commentToken);
50+
}
3051
commentToken = commentToken.next;
3152
}
3253

54+
if (extractedComments.isNotEmpty) {
55+
final isValid = extractedComments.length > 1
56+
? _hasValidMultiline(extractedComments, _CommentType.regular)
57+
: _hasValidSingleLine(
58+
extractedComments.first,
59+
_CommentType.regular,
60+
);
61+
if (!isValid) {
62+
final notIgnored = extractedComments.where((comment) {
63+
final trimmed = comment
64+
.toString()
65+
.replaceAll(_CommentType.regular.pattern, '')
66+
.trim();
67+
68+
return !_isIgnoreComment(trimmed) && !_isIgnoredPattern(trimmed);
69+
}).toList();
70+
_comments.add(_RegularCommentInfo(notIgnored));
71+
}
72+
73+
extractedComments.clear();
74+
}
75+
3376
if (token == token.next) {
3477
break;
3578
}
@@ -38,48 +81,99 @@ class _Visitor extends RecursiveAstVisitor<void> {
3881
}
3982
}
4083

41-
void _commentValidation(Token commentToken) {
42-
if (commentToken.type == TokenType.SINGLE_LINE_COMMENT) {
43-
final token = commentToken.toString();
44-
if (token.startsWith('///')) {
45-
_checkCommentByType(commentToken, _CommentType.documentation);
46-
} else if (token.startsWith('//') && !_onlyDocComments) {
47-
_checkCommentByType(commentToken, _CommentType.base);
48-
}
49-
}
84+
bool _isRegularComment(Token commentToken) {
85+
final token = commentToken.toString();
86+
87+
return !token.startsWith('///') && token.startsWith('//');
5088
}
5189

52-
void _checkCommentByType(Token commentToken, _CommentType type) {
53-
final commentText =
54-
commentToken.toString().substring(_commentsOperator[type]!.length);
90+
bool _hasValidMultiline(List<Token> commentTokens, _CommentType type) {
91+
final text = _extractText(commentTokens, type);
92+
final sentences = text.split(_sentencesRegExp);
5593

56-
var text = commentText.trim();
94+
return sentences.every(_isValidSentence);
95+
}
5796

58-
final isIgnoreComment =
59-
text.startsWith(_ignoreExp) || text.startsWith(_ignoreForFileExp);
97+
bool _hasValidSingleLine(Token commentToken, _CommentType type) {
98+
final commentText = commentToken.toString().substring(type.pattern.length);
99+
final text = commentText.trim();
60100

61-
final isMacros = _regMacrosExp.hasMatch(text) || text == _macrosEndExp;
101+
if (text.isEmpty ||
102+
_isIgnoreComment(text) ||
103+
_isMacros(text) ||
104+
_isIgnoredPattern(text)) {
105+
return true;
106+
}
62107

63-
final isAnIgnoredPattern = _ignoredPatterns.any(
64-
(regExp) => regExp.hasMatch(text),
65-
);
108+
return _isValidSentence(commentText);
109+
}
66110

67-
{
68-
if (text.isEmpty || isIgnoreComment || isMacros || isAnIgnoredPattern) {
69-
return;
70-
} else {
71-
text = text.trim();
72-
final upperCase = text[0] == text[0].toUpperCase();
73-
final lastSymbol = _punctuation.contains(text[text.length - 1]);
74-
final hasEmptySpace = commentText[0] == ' ';
75-
final incorrectFormat = !upperCase || !hasEmptySpace || !lastSymbol;
76-
final single =
77-
commentToken.previous == null && commentToken.next == null;
111+
bool _isValidSentence(String sentence) {
112+
final trimmedSentence = sentence.trim();
78113

79-
if (incorrectFormat && single) {
80-
_comments.add(_CommentInfo(type, commentToken));
81-
}
114+
final upperCase = trimmedSentence[0] == trimmedSentence[0].toUpperCase();
115+
final lastSymbol =
116+
_punctuation.contains(trimmedSentence[trimmedSentence.length - 1]);
117+
final hasEmptySpace = sentence[0] == ' ';
118+
119+
return upperCase && lastSymbol && hasEmptySpace;
120+
}
121+
122+
String _extractText(List<Token> commentTokens, _CommentType type) {
123+
var result = '';
124+
var shouldSkipNext = false;
125+
for (final token in commentTokens) {
126+
final commentText = token.toString().replaceAll(type.pattern, '');
127+
if (commentText.contains('```')) {
128+
shouldSkipNext = !shouldSkipNext;
129+
} else if (!_shouldSkip(commentText) && !shouldSkipNext) {
130+
result += commentText;
82131
}
83132
}
133+
134+
return result;
135+
}
136+
137+
bool _shouldSkip(String text) {
138+
final trimmed = text.trim();
139+
140+
return _regMacrosExp.hasMatch(text) ||
141+
text.contains(_macrosEndExp) ||
142+
_isIgnoreComment(trimmed) ||
143+
_isIgnoredPattern(trimmed);
84144
}
145+
146+
bool _isIgnoreComment(String text) =>
147+
text.startsWith(_ignoreExp) || text.startsWith(_ignoreForFileExp);
148+
149+
bool _isMacros(String text) =>
150+
_regMacrosExp.hasMatch(text) || text == _macrosEndExp;
151+
152+
bool _isIgnoredPattern(String text) =>
153+
_ignoredPatterns.any((regExp) => regExp.hasMatch(text));
154+
}
155+
156+
abstract class _CommentInfo {
157+
const _CommentInfo();
158+
}
159+
160+
class _DocCommentInfo extends _CommentInfo {
161+
final Comment comment;
162+
163+
const _DocCommentInfo(this.comment);
164+
}
165+
166+
class _RegularCommentInfo extends _CommentInfo {
167+
final List<Token> tokens;
168+
169+
const _RegularCommentInfo(this.tokens);
170+
}
171+
172+
class _CommentType {
173+
final String pattern;
174+
175+
const _CommentType(this.pattern);
176+
177+
static const regular = _CommentType('//');
178+
static const doc = _CommentType('///');
85179
}

test/src/analyzers/lint_analyzer/rules/rules_list/format_comment/examples/example.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ class Test {
77

88
/// With start space without dot
99
function() {
10-
/// with start space with dot.
10+
//Any other comment
1111
}
1212
// ignore:
1313
}
1414

1515
// ignore_for_file:
1616
var a;
17-
18-
/// [WidgetModel] for widget.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
// With start space without dot.
2+
var a;
3+
24
/// With start space without dot.
5+
var a;
36
/* With start space without dot.*/
7+
var a;
48
// TODO: Asdasd:asd:Asdasdasd.
9+
var a;
510
// TODO(vlad): Asdasd:asd:Asdasdasd.
11+
var a;

0 commit comments

Comments
 (0)