| // Copyright 2014 The Flutter Authors. 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:collection'; |
| |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/source/source.dart'; |
| import 'package:collection/collection.dart'; |
| |
| import 'dependency_graph.dart'; |
| |
| extension TokenExtension on Token { |
| /// Convenience getter to identify tokens for private fields and functions. |
| bool get isPrivate => toString().startsWith('_'); |
| |
| /// Convenience getter to identify WidgetBuilder types. |
| bool get isWidgetBuilder => toString() == 'WidgetBuilder'; |
| |
| /// Convenience getter to identify Widget types. |
| bool get isWidget => toString() == 'Widget'; |
| } |
| |
| extension on InterfaceType { |
| bool isType({required String typeName, required Uri uri}) { |
| if (getDisplayString() == typeName && element.library.uri == uri) { |
| return true; |
| } |
| return allSupertypes.firstWhereOrNull((e) { |
| return e.getDisplayString() == typeName && e.element.library.uri == uri; |
| }) != |
| null; |
| } |
| } |
| |
| extension AnnotationExtension on Annotation { |
| static final Uri widgetPreviewsLibraryUri = Uri.parse( |
| 'package:flutter/src/widget_previews/widget_previews.dart', |
| ); |
| |
| bool _isPreviewType(String typeName) { |
| final Element? element = elementAnnotation!.element; |
| if (element is ConstructorElement) { |
| final InterfaceType type = element.enclosingElement.thisType; |
| return type.isType(typeName: typeName, uri: widgetPreviewsLibraryUri); |
| } |
| return false; |
| } |
| |
| /// Convenience getter to identify `@Preview` annotations |
| bool get isPreview => _isPreviewType('Preview'); |
| |
| /// Convenience getter to identify `@MultiPreview` annotations |
| bool get isMultiPreview => _isPreviewType('MultiPreview'); |
| } |
| |
| /// Convenience getters for examining [String] paths. |
| extension StringExtension on String { |
| bool get isDartFile => endsWith('.dart'); |
| bool get isPubspec => endsWith('pubspec.yaml'); |
| bool get doesContainDartTool => contains('.dart_tool'); |
| } |
| |
| extension LibraryElementExtension on LibraryElement { |
| /// Convenience method to package path and [uri] into a [PreviewPath] |
| PreviewPath toPreviewPath() => (path: firstFragment.source.fullName, uri: uri); |
| } |
| |
| extension ParsedUnitResultExtension on ParsedUnitResult { |
| /// Convenience method to package [path] and [uri] into a [PreviewPath] |
| PreviewPath toPreviewPath() => (path: path, uri: uri); |
| } |
| |
| extension SourceExtension on Source { |
| /// Convenience method to package [fullName] and [uri] into a [PreviewPath] |
| PreviewPath toPreviewPath() => (path: fullName, uri: uri); |
| } |
| |
| /// Used to protect global state accessed in blocks containing calls to |
| /// asynchronous methods. |
| /// |
| /// Originally from DDS: |
| /// https://github.com/dart-lang/sdk/blob/3fe58da3cfe2c03fb9ee691a7a4709082fad3e73/pkg/dds/lib/src/utils/mutex.dart |
| class PreviewDetectorMutex { |
| /// Executes a block of code containing asynchronous calls atomically. |
| /// |
| /// If no other asynchronous context is currently executing within |
| /// [criticalSection], it will immediately be called. Otherwise, the |
| /// caller will be suspended and entered into a queue to be resumed once the |
| /// lock is released. |
| Future<T> runGuarded<T>(FutureOr<T> Function() criticalSection) async { |
| try { |
| await _acquireLock(); |
| return await criticalSection(); |
| } finally { |
| _releaseLock(); |
| } |
| } |
| |
| Future<void> _acquireLock() async { |
| if (!_locked) { |
| _locked = true; |
| return; |
| } |
| |
| final request = Completer<void>(); |
| _outstandingRequests.add(request); |
| await request.future; |
| } |
| |
| void _releaseLock() { |
| if (_outstandingRequests.isNotEmpty) { |
| final Completer<void> request = _outstandingRequests.removeFirst(); |
| request.complete(); |
| return; |
| } |
| // Only release the lock if no other requests are pending to prevent races |
| // between the next request from the queue to be handled and incoming |
| // requests. |
| _locked = false; |
| } |
| |
| var _locked = false; |
| final _outstandingRequests = Queue<Completer<void>>(); |
| } |