-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathrequire_trailing_commas.dart
More file actions
195 lines (162 loc) · 6.11 KB
/
Copy pathrequire_trailing_commas.dart
File metadata and controls
195 lines (162 loc) · 6.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Copyright (c) 2021, 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/analysis_rule.dart';
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/source/line_info.dart';
import 'package:pub_semver/pub_semver.dart';
import '../analyzer.dart';
import '../diagnostic.dart' as diag;
const _desc =
r'Use trailing commas for all parameter lists and argument lists.';
class RequireTrailingCommas extends AnalysisRule {
/// The version when tall-style was introduced in the formatter.
static final Version language37 = Version(3, 7, 0);
RequireTrailingCommas()
: super(name: LintNames.require_trailing_commas, description: _desc);
@override
DiagnosticCode get diagnosticCode => diag.requireTrailingCommas;
@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
// Don't report if tall-style is enforced by the formatter.
var languageVersion = context.libraryElement?.languageVersion.effective;
if (languageVersion != null && languageVersion >= language37) return;
var visitor = _Visitor(this);
registry
..addArgumentList(this, visitor)
..addAssertInitializer(this, visitor)
..addAssertStatement(this, visitor)
..addCompilationUnit(this, visitor)
..addFormalParameterList(this, visitor)
..addListLiteral(this, visitor)
..addSetOrMapLiteral(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
final AnalysisRule rule;
late LineInfo _lineInfo;
_Visitor(this.rule);
@override
void visitArgumentList(ArgumentList node) {
super.visitArgumentList(node);
if (node.arguments.isEmpty) return;
_checkTrailingComma(
openingToken: node.leftParenthesis,
closingToken: node.rightParenthesis,
lastNode: node.arguments.last,
);
}
@override
void visitAssertInitializer(AssertInitializer node) {
super.visitAssertInitializer(node);
_checkTrailingComma(
openingToken: node.leftParenthesis,
closingToken: node.rightParenthesis,
lastNode: node.message ?? node.condition,
);
}
@override
void visitAssertStatement(AssertStatement node) {
super.visitAssertStatement(node);
_checkTrailingComma(
openingToken: node.leftParenthesis,
closingToken: node.rightParenthesis,
lastNode: node.message ?? node.condition,
);
}
@override
void visitCompilationUnit(CompilationUnit node) => _lineInfo = node.lineInfo;
@override
void visitFormalParameterList(FormalParameterList node) {
super.visitFormalParameterList(node);
if (node.parent case PrimaryConstructorDeclaration primary) {
if (primary.parent is ExtensionTypeDeclaration) {
return;
}
}
if (node.parameters.isEmpty) return;
_checkTrailingComma(
openingToken: node.leftParenthesis,
closingToken: node.rightParenthesis,
lastNode: node.parameters.last,
errorToken: node.rightDelimiter ?? node.rightParenthesis,
);
}
@override
void visitListLiteral(ListLiteral node) {
super.visitListLiteral(node);
if (node.elements.isNotEmpty) {
_checkTrailingComma(
openingToken: node.leftBracket,
closingToken: node.rightBracket,
lastNode: node.elements.last,
);
}
}
@override
void visitSetOrMapLiteral(SetOrMapLiteral node) {
super.visitSetOrMapLiteral(node);
if (node.elements.isNotEmpty) {
_checkTrailingComma(
openingToken: node.leftBracket,
closingToken: node.rightBracket,
lastNode: node.elements.last,
);
}
}
void _checkTrailingComma({
required Token openingToken,
required Token closingToken,
required AstNode lastNode,
Token? errorToken,
}) {
errorToken ??= closingToken;
// Early exit if trailing comma is present.
if (lastNode.endToken.next?.type == TokenType.COMMA) return;
// No trailing comma is needed if the function call or declaration, up to
// the closing parenthesis, fits on a single line. Ensuring the left and
// right parenthesis are on the same line is sufficient since `dart format`
// places the left parenthesis right after the identifier (on the same
// line).
if (_isSameLine(openingToken, closingToken)) return;
// Check the last parameter to determine if there are any exceptions.
if (_shouldAllowTrailingCommaException(lastNode)) return;
rule.reportAtToken(errorToken);
}
bool _isSameLine(Token token1, Token token2) =>
_lineInfo.onSameLine(token1.offset, token2.end);
bool _shouldAllowTrailingCommaException(AstNode lastNode) {
// No exceptions are allowed if the last argument is named.
if (lastNode is FormalParameter && lastNode.isNamed) return false;
// No exceptions are allowed if the entire last argument fits on one line.
if (_isSameLine(lastNode.beginToken, lastNode.endToken)) return false;
// Exception is allowed if the last argument is a function literal.
if (lastNode is FunctionExpression && lastNode.body is BlockFunctionBody) {
return true;
}
// Exception is allowed if the last argument is a (multiline) string
// literal.
if (lastNode is StringLiteral) return true;
// Exception is allowed if the last argument is a anonymous function call.
// This case arises a lot in asserts.
if (lastNode is FunctionExpressionInvocation &&
lastNode.function is FunctionExpression &&
_isSameLine(
lastNode.argumentList.leftParenthesis,
lastNode.argumentList.rightParenthesis,
)) {
return true;
}
// Exception is allowed if the last argument is a set, map or list literal.
if (lastNode is SetOrMapLiteral || lastNode is ListLiteral) return true;
return false;
}
}