analyze method
Future<List<EndpointDefinition> >
analyze({
- required CodeAnalysisCollector collector,
- Set<
String> ? changedFiles,
Analyze files in the AnalysisContextCollection.
On the first call, analyzes every Dart file. On subsequent calls, only
re-analyzes files that changed since the last run (tracked via
updateFileContexts and changedFiles), reusing cached results for
unchanged files.
changedFiles is an optional set of files that should have their context
refreshed before analysis. This is useful when only a subset of files have
changed since updateFileContexts was last called (e.g. generated model
files).
Implementation
Future<List<EndpointDefinition>> analyze({
required CodeAnalysisCollector collector,
Set<String>? changedFiles,
}) async {
changedFiles ??= {};
await _refreshContextForFiles(changedFiles);
// On the first run, mark every Dart file as dirty so the single
// code path handles both first and subsequent runs.
if (_fileCache.isEmpty && _nonEndpointTemplateCache.isEmpty) {
changedFiles.addAll(_allAnalyzedDartFiles);
}
// Files to analyze: dirty files + previously errored endpoint files
// (fixing a dependency elsewhere might unblock them).
final filesToAnalyze = <String>{
...changedFiles,
..._fileCache.keys,
};
// Remove deleted files from caches.
for (var path in filesToAnalyze) {
if (!File(path).existsSync()) {
_fileCache.remove(path);
_nonEndpointTemplateCache.remove(path);
}
}
// Phase 1: Resolve only the files that need re-analysis.
List<(ResolvedLibraryResult, String, DartDocTemplateRegistry)>
validLibraries = [];
List<String> erroredFiles = [];
for (var path in filesToAnalyze) {
if (!path.endsWith('.dart') || path.endsWith('_test.dart')) continue;
if (!File(path).existsSync()) continue;
var library = await _resolveLibrary(path);
if (library == null) continue;
var fileTemplates = _extractTemplatesFromLibrary(
library,
path,
collector,
);
var endpointClasses = _getEndpointClasses(library);
if (endpointClasses.isEmpty) {
// Not (or no longer) an endpoint file - cache templates only.
_fileCache.remove(path);
_nonEndpointTemplateCache[path] = fileTemplates;
continue;
}
// It is an endpoint file, not a plain file.
_nonEndpointTemplateCache.remove(path);
var maybeDartErrors = await _getErrorsForFile(library.session, path);
if (maybeDartErrors.isNotEmpty) {
erroredFiles.add(path);
collector.addError(
SourceSpanSeverityException(
'Endpoint analysis skipped due to invalid Dart syntax. Please '
'review and correct the syntax errors.'
'\nFile: $path',
null,
severity: SourceSpanSeverity.error,
),
);
_fileCache[path] = _CachedFileResult(
definitions: [],
templates: fileTemplates,
hadErrors: true,
);
continue;
}
validLibraries.add((library, path, fileTemplates));
}
// Phase 2: Build endpoint class map from ALL files for duplicate detection.
// Errored files have empty definitions so they naturally don't contribute,
// matching the original behavior.
Map<String, int> endpointClassMap = {};
for (var entry in _fileCache.entries) {
if (validLibraries.any((lib) => lib.$2 == entry.key)) continue;
for (var def in entry.value.definitions) {
endpointClassMap.update(
def.className,
(v) => v + 1,
ifAbsent: () => 1,
);
}
}
for (var (library, _, _) in validLibraries) {
for (var cls in _getEndpointClasses(library)) {
endpointClassMap.update(cls.name!, (v) => v + 1, ifAbsent: () => 1);
}
}
var duplicateEndpointClasses = endpointClassMap.entries
.where((entry) => entry.value > 1)
.map((entry) => entry.key)
.toSet();
// Phase 3: Build full template registry from all caches + fresh results.
final templateRegistry = DartDocTemplateRegistry();
for (var entry in _fileCache.entries) {
if (validLibraries.any((lib) => lib.$2 == entry.key)) continue;
templateRegistry.addAll(entry.value.templates);
}
for (var templates in _nonEndpointTemplateCache.values) {
templateRegistry.addAll(templates);
}
// Phase 4: Validate and parse re-analyzed files, update cache.
for (var (library, filePath, fileTemplates) in validLibraries) {
templateRegistry.addAll(fileTemplates);
var severityExceptions = _validateLibrary(
library,
filePath,
duplicateEndpointClasses,
);
collector.addErrors(severityExceptions.values.expand((e) => e).toList());
var failingExceptions = _filterNoFailExceptions(severityExceptions);
var defs = _parseLibrary(
library,
filePath,
failingExceptions,
templateRegistry: templateRegistry,
);
_fileCache[filePath] = _CachedFileResult(
definitions: defs,
templates: fileTemplates,
hadErrors: false,
);
}
// Phase 5: Collect all endpoint definitions from cache.
// _fileCache is a SplayTreeMap so iteration is in sorted key order.
var endpointDefs = <EndpointDefinition>[];
for (var result in _fileCache.values) {
endpointDefs.addAll(result.definitions);
}
endpointDefs.removeWhere((e) => e.filePath.startsWith('package:'));
return endpointDefs;
}