@@ -17,7 +17,7 @@ import 'package:args/args.dart';
17
17
import 'package:path/path.dart' as path;
18
18
import 'package:process_runner/process_runner.dart' ;
19
19
20
- String _linterOutputHeader = '''
20
+ const String _linterOutputHeader = '''
21
21
┌──────────────────────────┐
22
22
│ Engine Clang Tidy Linter │
23
23
└──────────────────────────┘
@@ -26,6 +26,8 @@ more information on addressing these issues please see:
26
26
https://github.com/flutter/flutter/wiki/Engine-Clang-Tidy-Linter
27
27
''' ;
28
28
29
+ const String issueUrlPrefix = 'https://github.com/flutter/flutter/issues' ;
30
+
29
31
class Command {
30
32
Directory directory = Directory ('' );
31
33
String command = '' ;
@@ -109,23 +111,59 @@ File buildFileAsRepoFile(String buildFile, Directory repoPath) {
109
111
return result;
110
112
}
111
113
112
- Future <String > shouldIgnoreFile (File file) async {
113
- if (path.split (file.path).contains ('third_party' )) {
114
- return 'third_party' ;
115
- } else {
116
- final RegExp exp = RegExp (r'//\s*FLUTTER_NOLINT' );
117
- await for (String line
118
- in file.openRead ().transform (utf8.decoder).transform (const LineSplitter ())) {
119
- if (exp.hasMatch (line)) {
120
- return 'FLUTTER_NOLINT' ;
121
- } else if (line.isNotEmpty && line[0 ] != '\n ' && line[0 ] != '/' ) {
122
- // Quick out once we find a line that isn't empty or a comment. The
123
- // FLUTTER_NOLINT must show up before the first real code.
124
- return '' ;
125
- }
114
+ /// Lint actions to apply to a file.
115
+ enum LintAction {
116
+ /// Run the linter over the file.
117
+ lint,
118
+
119
+ /// Ignore files under third_party/.
120
+ skipThirdParty,
121
+
122
+ /// Ignore due to a well-formed FLUTTER_NOLINT comment.
123
+ skipNoLint,
124
+
125
+ /// Fail due to a malformed FLUTTER_NOLINT comment.
126
+ failMalformedNoLint,
127
+ }
128
+
129
+ bool isThirdPartyFile (File file) {
130
+ return path.split (file.path).contains ('third_party' );
131
+ }
132
+
133
+ Future <LintAction > getLintAction (File file) async {
134
+ if (isThirdPartyFile (file)) {
135
+ return LintAction .skipThirdParty;
136
+ }
137
+
138
+ // Check for FlUTTER_NOLINT at top of file.
139
+ final RegExp exp = RegExp ('\/\/\\ s*FLUTTER_NOLINT(: $issueUrlPrefix /\\ d+)?' );
140
+ final Stream <String > lines = file.openRead ()
141
+ .transform (utf8.decoder)
142
+ .transform (const LineSplitter ());
143
+ await for (String line in lines) {
144
+ final RegExpMatch match = exp.firstMatch (line);
145
+ if (match != null ) {
146
+ return match.group (1 ) != null
147
+ ? LintAction .skipNoLint
148
+ : LintAction .failMalformedNoLint;
149
+ } else if (line.isNotEmpty && line[0 ] != '\n ' && line[0 ] != '/' ) {
150
+ // Quick out once we find a line that isn't empty or a comment. The
151
+ // FLUTTER_NOLINT must show up before the first real code.
152
+ return LintAction .lint;
126
153
}
127
- return '' ;
128
154
}
155
+ return LintAction .lint;
156
+ }
157
+
158
+ WorkerJob createLintJob (Command command, String checks, String tidyPath) {
159
+ final String tidyArgs = calcTidyArgs (command);
160
+ final List <String > args = < String > [command.file.path, checks, '--' ];
161
+ args.addAll (tidyArgs? .split (' ' ) ?? < String > []);
162
+ return WorkerJob (
163
+ < String > [tidyPath, ...args],
164
+ workingDirectory: command.directory,
165
+ name: 'clang-tidy on ${command .file .path }' ,
166
+ );
129
167
}
130
168
131
169
void _usage (ArgParser parser, {int exitCode = 1 }) {
@@ -226,19 +264,23 @@ void main(List<String> arguments) async {
226
264
final List <WorkerJob > jobs = < WorkerJob > [];
227
265
for (Command command in changedFileBuildCommands) {
228
266
final String relativePath = path.relative (command.file.path, from: repoPath.parent.path);
229
- final String ignoreReason = await shouldIgnoreFile (command.file);
230
- if (ignoreReason.isEmpty) {
231
- final String tidyArgs = calcTidyArgs (command);
232
- final List <String > args = < String > [command.file.path, checks, '--' ];
233
- args.addAll (tidyArgs? .split (' ' ) ?? < String > []);
234
- print ('🔶 linting $relativePath ' );
235
- jobs.add (WorkerJob (
236
- < String > [tidyPath, ...args],
237
- workingDirectory: command.directory,
238
- name: 'clang-tidy on ${command .file .path }' ,
239
- ));
240
- } else {
241
- print ('🔷 ignoring $relativePath ($ignoreReason )' );
267
+ final LintAction action = await getLintAction (command.file);
268
+ switch (action) {
269
+ case LintAction .skipNoLint:
270
+ print ('🔷 ignoring $relativePath (FLUTTER_NOLINT)' );
271
+ break ;
272
+ case LintAction .failMalformedNoLint:
273
+ print ('❌ malformed opt-out $relativePath ' );
274
+ print (' Required format: // FLUTTER_NOLINT: $issueUrlPrefix /ISSUE_ID' );
275
+ exitCode = 1 ;
276
+ break ;
277
+ case LintAction .lint:
278
+ print ('🔶 linting $relativePath ' );
279
+ jobs.add (createLintJob (command, checks, tidyPath));
280
+ break ;
281
+ case LintAction .skipThirdParty:
282
+ print ('🔷 ignoring $relativePath (third_party)' );
283
+ break ;
242
284
}
243
285
}
244
286
final ProcessPool pool = ProcessPool ();
@@ -260,5 +302,7 @@ void main(List<String> arguments) async {
260
302
print ('\n ' );
261
303
if (exitCode == 0 ) {
262
304
print ('No lint problems found.' );
305
+ } else {
306
+ print ('Lint problems found.' );
263
307
}
264
308
}
0 commit comments