blob: 0267d653df6fea396f550fe22967d9e78e16e25a [file] [log] [blame]
// 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);
}