| 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: 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`. |
| 88 | abstract 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 | |