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
5import 'dart:io' show Directory;
6
7import 'package:analyzer/dart/analysis/analysis_context.dart';
8import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
9import 'package:analyzer/dart/analysis/results.dart';
10import 'package:analyzer/dart/analysis/session.dart';
11import 'package:path/path.dart' as path;
12
13import '../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].
28Future<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: excludePaths
45 ?.map((String relativePath) => path.canonicalize('$flutterRootDirectory/$relativePath'))
46 .toList(),
47 );
48
49 final List<String> analyzerErrors = <String>[];
50 for (final AnalysisContext context in collection.contexts) {
51 final Iterable<String> analyzedFilePaths = context.contextRoot.analyzedFiles();
52 final AnalysisSession session = context.currentSession;
53
54 for (final String filePath in analyzedFilePaths) {
55 final SomeResolvedUnitResult unit = await session.getResolvedUnit(filePath);
56 if (unit is ResolvedUnitResult) {
57 for (final AnalyzeRule rule in rules) {
58 rule.applyTo(unit);
59 }
60 } else {
61 analyzerErrors.add(
62 'Analyzer error: file $unit could not be resolved. Expected "ResolvedUnitResult", got ${unit.runtimeType}.',
63 );
64 }
65 }
66 }
67
68 if (analyzerErrors.isNotEmpty) {
69 foundError(analyzerErrors);
70 }
71 for (final AnalyzeRule verifier in rules) {
72 verifier.reportViolations(flutterRootDirectory);
73 }
74}
75
76/// An interface that defines a set of best practices, and collects information
77/// about code that violates the best practices in a [ResolvedUnitResult].
78///
79/// The [analyzeWithRules] function scans and analyzes the specified
80/// source directory using the dart analyzer package, and applies custom rules
81/// defined in the form of this interface on each resulting [ResolvedUnitResult].
82/// The [reportViolations] method will be called at the end, once all
83/// [ResolvedUnitResult]s are parsed.
84///
85/// Implementers can assume each [ResolvedUnitResult] is valid compilable dart
86/// code, as the caller only applies the custom rules once the code passes
87/// `flutter analyze`.
88abstract class AnalyzeRule {
89 /// Applies this rule to the given [ResolvedUnitResult] (typically a file), and
90 /// collects information about violations occurred in the compilation unit.
91 void applyTo(ResolvedUnitResult unit);
92
93 /// Reports all violations in the resolved compilation units [applyTo] was
94 /// called on, if any.
95 ///
96 /// This method is called once all [ResolvedUnitResult] are parsed.
97 ///
98 /// The implementation typically calls [foundErrors] to report violations.
99 void reportViolations(String workingDirectory);
100}
101