blob: 0c86014b5583cacf14bf4f9a1799398234374f16 [file] [log] [blame] [edit]
// 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 'dart:async';
import 'dart:io' as io;
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/overlay_file_system.dart';
import 'package:analyzer/source/error_processor.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
import 'models.dart';
/// Helper to collect diagnostics (hook point for timing/metrics if needed).
Future<List<HarnessDiagnostic>> collectAllDiagnostics(
ABEngine engine,
List<String> files,
) async {
return await engine.collect(files);
}
/// Thin wrapper around AnalysisContextCollection with policy flags.
class ABEngine {
final String label;
final OverlayResourceProvider overlay;
final List<String> roots;
/// If `true`, a new [AnalysisContextCollectionImpl] is created before any
/// analysis, without using any caching.
///
/// Not compatible with [withFineDependencies].
final bool rebuildEveryStep;
/// If `true`, [AnalysisContextCollectionImpl] is created with the
/// corresponding flag.
final bool withFineDependencies;
AnalysisContextCollectionImpl? _collection;
ABEngine({
required this.label,
required this.overlay,
required this.roots,
required this.rebuildEveryStep,
required this.withFineDependencies,
});
AnalysisContextCollection get collection {
return _collection ??= AnalysisContextCollectionImpl(
includedPaths: roots,
resourceProvider: overlay,
withFineDependencies: withFineDependencies,
);
}
/// Collect diagnostics for [files].
///
/// If [rebuildEveryStep] is true, instantiates a fresh collection to
/// force conservative recomputation.
Future<List<HarnessDiagnostic>> collect(List<String> files) async {
if (rebuildEveryStep) {
_collection = null;
}
var out = <HarnessDiagnostic>[];
for (var file in files) {
var analysisContext = collection.contextFor(file);
var analysisSession = analysisContext.currentSession;
var errorsResult = await analysisSession.getErrors(file);
if (errorsResult is ErrorsResult) {
for (var diagnostic in errorsResult.diagnostics) {
// Filter TODOs, not interesting for comparison.
if (diagnostic.diagnosticCode.type == DiagnosticType.TODO) {
continue;
}
var severityName = _getProcessedSeverity(errorsResult, diagnostic);
if (severityName == null) {
continue;
}
out.add(
HarnessDiagnostic(
path: file,
code: diagnostic.diagnosticCode.name,
severity: severityName,
offset: diagnostic.offset,
length: diagnostic.length,
message: diagnostic.message,
),
);
}
}
}
return out;
}
/// Notify that [path] has changed (overlay updated).
Future<void> notifyChange(String path) async {
if (_collection case var collection?) {
var analysisContext = collection.contextFor(path);
analysisContext.changeFile(path);
await analysisContext.applyPendingFileChanges();
}
}
void resetPerformance() {
_collection?.scheduler.accumulatedPerformance = OperationPerformanceImpl(
'<scheduler>',
);
}
void writePerformanceTo(String path) {
if (_collection case var collection?) {
var scheduler = collection.scheduler;
var buffer = StringBuffer();
scheduler.accumulatedPerformance.write(buffer: buffer);
scheduler.accumulatedPerformance = OperationPerformanceImpl(
'<scheduler>',
);
io.File(path).writeAsStringSync(buffer.toString());
}
}
/// Returns the name of the severity of the [diagnostic], after applying
/// processors from the [errorsResult], or `null` if the diagnostic should
/// be filtered.
String? _getProcessedSeverity(
ErrorsResult errorsResult,
Diagnostic diagnostic,
) {
var processor = ErrorProcessor.getProcessor(
errorsResult.analysisOptions,
diagnostic,
);
var severityName = diagnostic.diagnosticCode.severity.name;
if (processor != null) {
var severity = processor.severity;
if (severity == null || severity.name == 'NONE') {
return null;
}
severityName = severity.name;
}
return severityName;
}
}
/// Separate selector context used only for site discovery / validation.
/// We provide resolved units for semantic kinds (e.g., alpha) without warming A/B.
class SiteSelector {
final OverlayResourceProvider overlay;
final List<String> roots;
late AnalysisContextCollection _collection;
SiteSelector(this.overlay, this.roots) {
_collection = AnalysisContextCollection(
includedPaths: roots,
resourceProvider: overlay,
);
}
/// Notify that [path] has changed (overlay updated).
Future<void> notifyChange(String path) async {
var analysisContext = _collection.contextFor(path);
analysisContext.changeFile(path);
await analysisContext.applyPendingFileChanges();
}
CompilationUnit parsedUnit(String path) {
var analysisContext = _collection.contextFor(path);
var analysisSession = analysisContext.currentSession;
var result = analysisSession.getParsedUnit(path);
result as ParsedUnitResult;
return result.unit;
}
Future<CompilationUnit?> resolvedUnit(String path) async {
var analysisContext = _collection.contextFor(path);
var analysisSession = analysisContext.currentSession;
var result = await analysisSession.getResolvedUnit(path);
return result.ifTypeOrNull<ResolvedUnitResult>()?.unit;
}
}