blob: a32bd8e91c4c9312765090732e62fd3144e918ab [file]
// Copyright (c) 2025, 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 '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/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:path/path.dart' as p;
class Analyzer {
late AnalysisContextCollection _collection;
late ResourceProvider resourceProvider;
int _lastCheckedMillis = DateTime.now().millisecondsSinceEpoch;
/// Create an analysis context using the given paths.
///
/// Note that all paths must the absolute. These are assumed to the paths to
/// packages; for these packages, only the code in `lib/` will be analyzed.
Analyzer.packages({
required List<String> includedPaths,
ResourceProvider? resourceProvider,
}) {
this.resourceProvider =
resourceProvider ?? PhysicalResourceProvider.INSTANCE;
_collection = AnalysisContextCollection(
includedPaths: [...includedPaths.map((path) => p.join(path, 'lib'))],
resourceProvider: this.resourceProvider,
);
}
AnalysisContext get context => _collection.contexts.first;
AnalysisContext contextFor(String path) {
return _collection.contextFor(path);
}
Stream<ResolvedLibraryResult> resolvedPublicLibraries() async* {
for (var file in _publicFiles) {
var lib =
await context.currentSession.getResolvedLibrary(file.path)
as ResolvedLibraryResult;
// TODO: Filter out parts?
yield lib;
}
}
Stream<ResolvedLibraryResult> resolvedLibrariesFor(String path) async* {
var context = contextFor(path);
for (var file in context.publicFiles) {
var lib =
await context.currentSession.getResolvedLibrary(file.path)
as ResolvedLibraryResult;
// TODO: Filter out parts?
yield lib;
}
}
/// Check for any changed files and reanalyze changes.
///
/// Returns `true` if any changed files had been detected.
Future<bool> reanalyzeChanges() async {
var hasChanges = false;
var context = _collection.contexts.first;
final currentMillis = DateTime.now().millisecondsSinceEpoch;
for (var path in context.contextRoot.analyzedFiles().where(
(path) => path.endsWith('.dart'),
)) {
var modificationStamp = context.contextRoot.resourceProvider
.getFile(path)
.modificationStamp;
if (modificationStamp > _lastCheckedMillis) {
hasChanges = true;
context.changeFile(path);
}
}
_lastCheckedMillis = currentMillis;
await context.applyPendingFileChanges();
return hasChanges;
}
Iterable<File> get _publicFiles sync* {
for (var context in _collection.contexts) {
var root = context.contextRoot;
var src = root.root.getChildAssumingFolder('src');
var dartFiles =
context.contextRoot
.analyzedFiles()
.where((path) => path.endsWith('.dart'))
.toList()
..sort();
for (var path in dartFiles) {
if (src.contains(path)) continue;
// TODO: Ignore files whose names starts with '_'?
yield root.resourceProvider.getFile(path);
}
}
}
Future<LibraryElementResult> getLibraryByUri(String uri) async {
var result = await context.currentSession.getLibraryByUri(uri);
return result as LibraryElementResult;
}
}
extension AnalysisContextExtension on AnalysisContext {
Iterable<File> get publicFiles sync* {
var root = contextRoot;
var src = root.root.getChildAssumingFolder('src');
var dartFiles =
contextRoot
.analyzedFiles()
.where((path) => path.endsWith('.dart'))
.toList()
..sort();
for (var path in dartFiles) {
if (src.contains(path)) continue;
// TODO: Ignore files whose names starts with '_' ?
yield root.resourceProvider.getFile(path);
}
}
}