1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:io' show Directory; |
6 | |
7 | import 'package:analyzer/dart/analysis/analysis_context.dart' ; |
8 | import 'package:analyzer/dart/analysis/analysis_context_collection.dart' ; |
9 | import 'package:analyzer/dart/analysis/results.dart' ; |
10 | import 'package:analyzer/dart/analysis/session.dart' ; |
11 | import 'package:path/path.dart' as path; |
12 | |
13 | import '../utils.dart'; |
14 | |
15 | /// Analyzes the dart source files in the given `flutterRootDirectory` with the |
16 | /// given [AnalyzeRule]s. |
17 | /// |
18 | /// The `includePath` parameter takes a collection of paths relative to the given |
19 | /// `flutterRootDirectory`. It specifies the files or directory this function |
20 | /// should analyze. Defaults to null in which case this function analyzes the |
21 | /// all dart source files in `flutterRootDirectory`. |
22 | /// |
23 | /// The `excludePath` parameter takes a collection of paths relative to the given |
24 | /// `flutterRootDirectory` that this function should skip analyzing. |
25 | /// |
26 | /// If a compilation unit can not be resolved, this function ignores the |
27 | /// corresponding dart source file and logs an error using [foundError]. |
28 | Future<void> analyzeWithRules( |
29 | String flutterRootDirectory, |
30 | List<AnalyzeRule> rules, { |
31 | Iterable<String>? includePaths, |
32 | Iterable<String>? excludePaths, |
33 | }) async { |
34 | if (!Directory(flutterRootDirectory).existsSync()) { |
35 | foundError(<String>['Analyzer error: the specified $flutterRootDirectory does not exist.' ]); |
36 | } |
37 | final Iterable<String> includes = |
38 | includePaths?.map( |
39 | (String relativePath) => path.canonicalize(' $flutterRootDirectory/ $relativePath' ), |
40 | ) ?? |
41 | <String>[path.canonicalize(flutterRootDirectory)]; |
42 | final AnalysisContextCollection collection = AnalysisContextCollection( |
43 | includedPaths: includes.toList(), |
44 | excludedPaths: |
45 | excludePaths |
46 | ?.map((String relativePath) => path.canonicalize(' $flutterRootDirectory/ $relativePath' )) |
47 | .toList(), |
48 | ); |
49 | |
50 | final List<String> analyzerErrors = <String>[]; |
51 | for (final AnalysisContext context in collection.contexts) { |
52 | final Iterable<String> analyzedFilePaths = context.contextRoot.analyzedFiles(); |
53 | final AnalysisSession session = context.currentSession; |
54 | |
55 | for (final String filePath in analyzedFilePaths) { |
56 | final SomeResolvedUnitResult unit = await session.getResolvedUnit(filePath); |
57 | if (unit is ResolvedUnitResult) { |
58 | for (final AnalyzeRule rule in rules) { |
59 | rule.applyTo(unit); |
60 | } |
61 | } else { |
62 | analyzerErrors.add( |
63 | 'Analyzer error: file $unit could not be resolved. Expected "ResolvedUnitResult", got ${unit.runtimeType}.' , |
64 | ); |
65 | } |
66 | } |
67 | } |
68 | |
69 | if (analyzerErrors.isNotEmpty) { |
70 | foundError(analyzerErrors); |
71 | } |
72 | for (final AnalyzeRule verifier in rules) { |
73 | verifier.reportViolations(flutterRootDirectory); |
74 | } |
75 | } |
76 | |
77 | /// An interface that defines a set of best practices, and collects information |
78 | /// about code that violates the best practices in a [ResolvedUnitResult]. |
79 | /// |
80 | /// The [analyzeWithRules] function scans and analyzes the specified |
81 | /// source directory using the dart analyzer package, and applies custom rules |
82 | /// defined in the form of this interface on each resulting [ResolvedUnitResult]. |
83 | /// The [reportViolations] method will be called at the end, once all |
84 | /// [ResolvedUnitResult]s are parsed. |
85 | /// |
86 | /// Implementers can assume each [ResolvedUnitResult] is valid compilable dart |
87 | /// code, as the caller only applies the custom rules once the code passes |
88 | /// `flutter analyze`. |
89 | abstract class AnalyzeRule { |
90 | /// Applies this rule to the given [ResolvedUnitResult] (typically a file), and |
91 | /// collects information about violations occurred in the compilation unit. |
92 | void applyTo(ResolvedUnitResult unit); |
93 | |
94 | /// Reports all violations in the resolved compilation units [applyTo] was |
95 | /// called on, if any. |
96 | /// |
97 | /// This method is called once all [ResolvedUnitResult] are parsed. |
98 | /// |
99 | /// The implementation typically calls [foundErrors] to report violations. |
100 | void reportViolations(String workingDirectory); |
101 | } |
102 | |