[ Widget Preview ] Cleanup PreviewDetector code (#163050)

Adds some convenience extensions for working with analyzer APIs and
removes a dependency on an implementation library from a private
analyzer package.
This commit is contained in:
Ben Konyi 2025-02-11 12:58:01 -05:00 committed by GitHub
parent 59fd4f9791
commit 9ee42fd1ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4,12 +4,11 @@
import 'dart:async'; import 'dart:async';
// ignore: implementation_imports
import 'package:_fe_analyzer_shared/src/base/syntactic_entity.dart';
import 'package:analyzer/dart/analysis/analysis_context.dart'; import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:watcher/watcher.dart'; import 'package:watcher/watcher.dart';
@ -28,6 +27,27 @@ typedef PreviewPath = ({String path, Uri uri});
/// Represents a set of previews for a given file. /// Represents a set of previews for a given file.
typedef PreviewMapping = Map<PreviewPath, List<String>>; typedef PreviewMapping = Map<PreviewPath, List<String>>;
extension on Token {
/// Convenience getter to identify tokens for private fields and functions.
bool get isPrivate => toString().startsWith('_');
}
extension on Annotation {
/// Convenience getter to identify `@Preview` annotations
bool get isPreview => name.name == 'Preview';
}
/// Convenience getters for examining [String] paths.
extension on String {
bool get isDartFile => endsWith('.dart');
bool get isGeneratedPreviewFile => endsWith(PreviewCodeGenerator.generatedPreviewFilePath);
}
extension on ParsedUnitResult {
/// Convenience method to package [path] and [uri] into a [PreviewPath]
PreviewPath toPreviewPath() => (path: path, uri: uri);
}
class PreviewDetector { class PreviewDetector {
PreviewDetector({required this.fs, required this.logger, required this.onChangeDetected}); PreviewDetector({required this.fs, required this.logger, required this.onChangeDetected});
@ -49,8 +69,7 @@ class PreviewDetector {
final String eventPath = event.path; final String eventPath = event.path;
// Only trigger a reload when changes to Dart sources are detected. We // Only trigger a reload when changes to Dart sources are detected. We
// ignore the generated preview file to avoid getting stuck in a loop. // ignore the generated preview file to avoid getting stuck in a loop.
if (!eventPath.endsWith('.dart') || if (!eventPath.isDartFile || eventPath.isGeneratedPreviewFile) {
eventPath.endsWith(PreviewCodeGenerator.generatedPreviewFilePath)) {
return; return;
} }
logger.printStatus('Detected change in $eventPath.'); logger.printStatus('Detected change in $eventPath.');
@ -110,20 +129,19 @@ class PreviewDetector {
for (final String filePath in context.contextRoot.analyzedFiles()) { for (final String filePath in context.contextRoot.analyzedFiles()) {
logger.printTrace('Checking file: $filePath'); logger.printTrace('Checking file: $filePath');
if (!filePath.endsWith('.dart')) { if (!filePath.isDartFile) {
continue; continue;
} }
final SomeParsedLibraryResult lib = context.currentSession.getParsedLibrary(filePath); final SomeParsedLibraryResult lib = context.currentSession.getParsedLibrary(filePath);
if (lib is ParsedLibraryResult) { if (lib is ParsedLibraryResult) {
for (final ParsedUnitResult unit in lib.units) { for (final ParsedUnitResult libUnit in lib.units) {
final List<String> previewEntries = final List<String> previewEntries = previews[libUnit.toPreviewPath()] ?? <String>[];
previews[(path: unit.path, uri: unit.uri)] ?? <String>[]; for (final CompilationUnitMember entity in libUnit.unit.declarations) {
for (final SyntacticEntity entity in unit.unit.childEntities) { if (entity is FunctionDeclaration && !entity.name.isPrivate) {
if (entity is FunctionDeclaration && !entity.name.toString().startsWith('_')) {
bool foundPreview = false; bool foundPreview = false;
for (final Annotation annotation in entity.metadata) { for (final Annotation annotation in entity.metadata) {
if (annotation.name.name == 'Preview') { if (annotation.isPreview) {
// What happens if the annotation is applied multiple times? // What happens if the annotation is applied multiple times?
foundPreview = true; foundPreview = true;
break; break;
@ -131,7 +149,7 @@ class PreviewDetector {
} }
if (foundPreview) { if (foundPreview) {
logger.printStatus('Found preview at:'); logger.printStatus('Found preview at:');
logger.printStatus('File path: ${unit.uri}'); logger.printStatus('File path: ${libUnit.uri}');
logger.printStatus('Preview function: ${entity.name}'); logger.printStatus('Preview function: ${entity.name}');
logger.printStatus(''); logger.printStatus('');
previewEntries.add(entity.name.toString()); previewEntries.add(entity.name.toString());
@ -139,7 +157,7 @@ class PreviewDetector {
} }
} }
if (previewEntries.isNotEmpty) { if (previewEntries.isNotEmpty) {
previews[(path: unit.path, uri: unit.uri)] = previewEntries; previews[libUnit.toPreviewPath()] = previewEntries;
} }
} }
} else { } else {