| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:io' show Directory; |
| |
| import 'package:analyzer/dart/analysis/analysis_context.dart'; |
| import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/analysis/session.dart'; |
| import 'package:path/path.dart' as path; |
| |
| import '../utils.dart'; |
| |
| /// Analyzes the dart source files in the given `flutterRootDirectory` with the |
| /// given [AnalyzeRule]s. |
| /// |
| /// The `includePath` parameter takes a collection of paths relative to the given |
| /// `flutterRootDirectory`. It specifies the files or directory this function |
| /// should analyze. Defaults to null in which case this function analyzes the |
| /// all dart source files in `flutterRootDirectory`. |
| /// |
| /// The `excludePath` parameter takes a collection of paths relative to the given |
| /// `flutterRootDirectory` that this function should skip analyzing. |
| /// |
| /// If a compilation unit can not be resolved, this function ignores the |
| /// corresponding dart source file and logs an error using [foundError]. |
| Future<void> analyzeWithRules(String flutterRootDirectory, List<AnalyzeRule> rules, { |
| Iterable<String>? includePaths, |
| Iterable<String>? excludePaths, |
| }) async { |
| if (!Directory(flutterRootDirectory).existsSync()) { |
| foundError(<String>['Analyzer error: the specified $flutterRootDirectory does not exist.']); |
| } |
| final Iterable<String> includes = includePaths?.map((String relativePath) => path.canonicalize('$flutterRootDirectory/$relativePath')) |
| ?? <String>[path.canonicalize(flutterRootDirectory)]; |
| final AnalysisContextCollection collection = AnalysisContextCollection( |
| includedPaths: includes.toList(), |
| excludedPaths: excludePaths?.map((String relativePath) => path.canonicalize('$flutterRootDirectory/$relativePath')).toList(), |
| ); |
| |
| final List<String> analyzerErrors = <String>[]; |
| for (final AnalysisContext context in collection.contexts) { |
| final Iterable<String> analyzedFilePaths = context.contextRoot.analyzedFiles(); |
| final AnalysisSession session = context.currentSession; |
| |
| for (final String filePath in analyzedFilePaths) { |
| final SomeResolvedUnitResult unit = await session.getResolvedUnit(filePath); |
| if (unit is ResolvedUnitResult) { |
| for (final AnalyzeRule rule in rules) { |
| rule.applyTo(unit); |
| } |
| } else { |
| analyzerErrors.add('Analyzer error: file $unit could not be resolved. Expected "ResolvedUnitResult", got ${unit.runtimeType}.'); |
| } |
| } |
| } |
| |
| if (analyzerErrors.isNotEmpty) { |
| foundError(analyzerErrors); |
| } |
| for (final AnalyzeRule verifier in rules) { |
| verifier.reportViolations(flutterRootDirectory); |
| } |
| } |
| |
| Future<void> analyzeToolWithRules(String flutterRootDirectory, List<AnalyzeRule> rules) async { |
| final String libPath = path.canonicalize('$flutterRootDirectory/packages/flutter_tools/lib'); |
| if (!Directory(libPath).existsSync()) { |
| foundError(<String>['Analyzer error: the specified $libPath does not exist.']); |
| } |
| final String testPath = path.canonicalize('$flutterRootDirectory/packages/flutter_tools/test'); |
| final AnalysisContextCollection collection = AnalysisContextCollection( |
| includedPaths: <String>[libPath, testPath], |
| ); |
| |
| final List<String> analyzerErrors = <String>[]; |
| for (final AnalysisContext context in collection.contexts) { |
| final Iterable<String> analyzedFilePaths = context.contextRoot.analyzedFiles(); |
| final AnalysisSession session = context.currentSession; |
| |
| for (final String filePath in analyzedFilePaths) { |
| final SomeResolvedUnitResult unit = await session.getResolvedUnit(filePath); |
| if (unit is ResolvedUnitResult) { |
| for (final AnalyzeRule rule in rules) { |
| rule.applyTo(unit); |
| } |
| } else { |
| analyzerErrors.add('Analyzer error: file $unit could not be resolved. Expected "ResolvedUnitResult", got ${unit.runtimeType}.'); |
| } |
| } |
| } |
| |
| if (analyzerErrors.isNotEmpty) { |
| foundError(analyzerErrors); |
| } |
| for (final AnalyzeRule verifier in rules) { |
| verifier.reportViolations(flutterRootDirectory); |
| } |
| } |
| |
| /// An interface that defines a set of best practices, and collects information |
| /// about code that violates the best practices in a [ResolvedUnitResult]. |
| /// |
| /// The [analyzeWithRules] function scans and analyzes the specified |
| /// source directory using the dart analyzer package, and applies custom rules |
| /// defined in the form of this interface on each resulting [ResolvedUnitResult]. |
| /// The [reportViolations] method will be called at the end, once all |
| /// [ResolvedUnitResult]s are parsed. |
| /// |
| /// Implementers can assume each [ResolvedUnitResult] is valid compilable dart |
| /// code, as the caller only applies the custom rules once the code passes |
| /// `flutter analyze`. |
| abstract class AnalyzeRule { |
| /// Applies this rule to the given [ResolvedUnitResult] (typically a file), and |
| /// collects information about violations occurred in the compilation unit. |
| void applyTo(ResolvedUnitResult unit); |
| |
| /// Reports all violations in the resolved compilation units [applyTo] was |
| /// called on, if any. |
| /// |
| /// This method is called once all [ResolvedUnitResult] are parsed. |
| /// |
| /// The implementation typically calls [foundErrors] to report violations. |
| void reportViolations(String workingDirectory); |
| } |