blob: dceca73d573506c97c6a7fb52e00c33cb9408ef2 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. 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:collection';
import 'dart:io' as io;
import 'package:analyzer/dart/analysis/context_locator.dart' as api;
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/file_system.dart'
show File, Folder, ResourceProvider, ResourceUriResolver;
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/context/packages.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'
as api;
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/lint/io.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/project.dart';
import 'package:analyzer/src/lint/registry.dart';
import 'package:analyzer/src/services/lint.dart';
import 'package:analyzer/src/source/package_map_resolver.dart';
import 'package:analyzer/src/task/options.dart';
import 'package:analyzer/src/util/sdk.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
AnalysisOptionsProvider _optionsProvider = AnalysisOptionsProvider();
Source createSource(Uri sourceUri) {
return PhysicalResourceProvider.INSTANCE
.getFile(sourceUri.toFilePath())
.createSource(sourceUri);
}
/// Print the given message and exit with the given [exitCode]
void printAndFail(String message, {int exitCode = 15}) {
print(message);
io.exit(exitCode);
}
AnalysisOptionsImpl _buildAnalyzerOptions(LinterOptions options) {
AnalysisOptionsImpl analysisOptions = AnalysisOptionsImpl();
if (options.analysisOptions != null) {
YamlMap map =
_optionsProvider.getOptionsFromString(options.analysisOptions);
applyToAnalysisOptions(analysisOptions, map);
}
analysisOptions.hint = false;
analysisOptions.lint = options.enableLints;
analysisOptions.enableTiming = options.enableTiming;
analysisOptions.lintRules = options.enabledLints.toList(growable: false);
return analysisOptions;
}
class DriverOptions {
/// The maximum number of sources for which AST structures should be kept
/// in the cache. The default is 512.
int cacheSize = 512;
/// The path to the dart SDK.
String? dartSdkPath;
/// Whether to show lint warnings.
bool enableLints = true;
/// Whether to gather timing data during analysis.
bool enableTiming = false;
/// The path to a `.packages` configuration file
String? packageConfigPath;
/// The path to the package root.
@Deprecated('https://github.com/dart-lang/sdk/issues/41197')
String? packageRootPath;
/// Whether to use Dart's Strong Mode analyzer.
bool strongMode = true;
/// The mock SDK (to speed up testing) or `null` to use the actual SDK.
DartSdk? mockSdk;
/// Return `true` is the parser is able to parse asserts in the initializer
/// list of a constructor.
@deprecated
bool get enableAssertInitializer => true;
/// Set whether the parser is able to parse asserts in the initializer list of
/// a constructor to match [enable].
@deprecated
set enableAssertInitializer(bool enable) {
// Ignored because the option is now always enabled.
}
/// Whether to use Dart 2.0 features.
@deprecated
bool get previewDart2 => true;
@deprecated
set previewDart2(bool value) {}
}
class LintDriver {
/// The sources which have been analyzed so far. This is used to avoid
/// analyzing a source more than once, and to compute the total number of
/// sources analyzed for statistics.
final Set<Source> _sourcesAnalyzed = HashSet<Source>();
final LinterOptions options;
LintDriver(this.options);
/// Return the number of sources that have been analyzed so far.
int get numSourcesAnalyzed => _sourcesAnalyzed.length;
List<UriResolver> get resolvers {
// TODO(brianwilkerson) Use the context builder to compute all of the resolvers.
ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE;
DartSdk sdk = options.mockSdk ??
FolderBasedDartSdk(
resourceProvider, resourceProvider.getFolder(sdkDir));
List<UriResolver> resolvers = [DartUriResolver(sdk)];
var packageUriResolver = _getPackageUriResolver();
if (packageUriResolver != null) {
resolvers.add(packageUriResolver);
}
// File URI resolver must come last so that files inside "/lib" are
// are analyzed via "package:" URI's.
resolvers.add(ResourceUriResolver(resourceProvider));
return resolvers;
}
ResourceProvider get resourceProvider => options.resourceProvider;
String get sdkDir {
// In case no SDK has been specified, fall back to inferring it.
return options.dartSdkPath ?? getSdkPath();
}
Future<List<AnalysisErrorInfo>> analyze(Iterable<io.File> files) async {
AnalysisEngine.instance.instrumentationService = StdInstrumentation();
SourceFactory sourceFactory = SourceFactory(resolvers);
PerformanceLog log = PerformanceLog(null);
AnalysisDriverScheduler scheduler = AnalysisDriverScheduler(log);
AnalysisDriver analysisDriver = AnalysisDriver(
scheduler,
log,
resourceProvider,
MemoryByteStore(),
FileContentOverlay(),
null,
sourceFactory,
_buildAnalyzerOptions(options),
packages: Packages.empty,
);
_setAnalysisDriverAnalysisContext(analysisDriver, files);
analysisDriver.results.listen((_) {});
analysisDriver.exceptions.listen((_) {});
scheduler.start();
List<Source> sources = [];
for (io.File file in files) {
File sourceFile =
resourceProvider.getFile(p.normalize(file.absolute.path));
Source source = sourceFile.createSource();
var uri = sourceFactory.restoreUri(source);
if (uri != null) {
// Ensure that we analyze the file using its canonical URI (e.g. if
// it's in "/lib", analyze it using a "package:" URI).
source = sourceFile.createSource(uri);
}
sources.add(source);
analysisDriver.addFile(source.fullName);
}
DartProject project = await DartProject.create(analysisDriver, sources);
Registry.ruleRegistry.forEach((lint) {
if (lint is ProjectVisitor) {
(lint as ProjectVisitor).visit(project);
}
});
List<AnalysisErrorInfo> errors = [];
for (Source source in sources) {
var errorsResult = await analysisDriver.getErrors2(source.fullName);
if (errorsResult is ErrorsResult) {
errors.add(
AnalysisErrorInfoImpl(
errorsResult.errors,
errorsResult.lineInfo,
),
);
_sourcesAnalyzed.add(source);
}
}
return errors;
}
void registerLinters(AnalysisContext context) {
if (options.enableLints) {
setLints(context, options.enabledLints.toList(growable: false));
}
}
PackageMapUriResolver? _getPackageUriResolver() {
var packageConfigPath = options.packageConfigPath;
if (packageConfigPath != null) {
var resourceProvider = PhysicalResourceProvider.INSTANCE;
var pathContext = resourceProvider.pathContext;
packageConfigPath = pathContext.absolute(packageConfigPath);
packageConfigPath = pathContext.normalize(packageConfigPath);
try {
var packages = parsePackagesFile(
resourceProvider,
resourceProvider.getFile(packageConfigPath),
);
var packageMap = <String, List<Folder>>{};
for (var package in packages.packages) {
packageMap[package.name] = [package.libFolder];
}
return PackageMapUriResolver(resourceProvider, packageMap);
} catch (e) {
printAndFail(
'Unable to read package config data from $packageConfigPath: $e',
);
}
}
return null;
}
void _setAnalysisDriverAnalysisContext(
AnalysisDriver analysisDriver,
Iterable<io.File> files,
) {
if (files.isEmpty) {
return;
}
var rootPath = p.normalize(files.first.absolute.path);
var apiContextRoots = api.ContextLocator(
resourceProvider: resourceProvider,
).locateRoots(
includedPaths: [rootPath],
excludedPaths: [],
);
if (apiContextRoots.isEmpty) {
return;
}
analysisDriver.configure(
analysisContext: api.DriverBasedAnalysisContext(
resourceProvider,
apiContextRoots.first,
analysisDriver,
),
);
}
}
/// Prints logging information comments to the [outSink] and error messages to
/// [errorSink].
class StdInstrumentation extends NoopInstrumentationService {
@override
void logError(String message, [Object? exception]) {
errorSink.writeln(message);
if (exception != null) {
errorSink.writeln(exception);
}
}
@override
void logException(dynamic exception,
[StackTrace? stackTrace,
List<InstrumentationServiceAttachment>? attachments]) {
errorSink.writeln(exception);
errorSink.writeln(stackTrace);
}
@override
void logInfo(String message, [Object? exception]) {
outSink.writeln(message);
if (exception != null) {
outSink.writeln(exception);
}
}
}