34fb48bb8a
Migrates libraries dependent only on already migrated libraries. Change-Id: I0e85ee8dbc2afce031b92e0009e71c206a55af28 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/179502 Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Jens Johansen <jensj@google.com>
193 lines
6.1 KiB
Dart
193 lines
6.1 KiB
Dart
// Copyright (c) 2020, 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.
|
|
|
|
// @dart = 2.9
|
|
|
|
import 'package:kernel/class_hierarchy.dart';
|
|
import 'package:kernel/kernel.dart';
|
|
|
|
/// Provides support for "single widget reloads" in Flutter, by determining if
|
|
/// a partial component contains single change to the class body of a
|
|
/// StatelessWidget, StatefulWidget, or State subtype.
|
|
class WidgetCache {
|
|
/// Create a [WidgetCache] from a [Component] containing the flutter
|
|
/// framework.
|
|
WidgetCache(Component fullComponent) {
|
|
Library frameworkLibrary;
|
|
for (Library library in fullComponent.libraries) {
|
|
if (library?.importUri == _frameworkLibrary) {
|
|
frameworkLibrary = library;
|
|
break;
|
|
}
|
|
}
|
|
if (frameworkLibrary == null) {
|
|
return;
|
|
}
|
|
_locatedClassDeclarations(frameworkLibrary);
|
|
_frameworkTypesLocated =
|
|
_statefulWidget != null && _state != null && _statelessWidget != null;
|
|
}
|
|
|
|
static const String _stateClassName = 'State';
|
|
static const String _statefulWidgetClassName = 'StatefulWidget';
|
|
static const String _statelessWidgetClassName = 'StatelessWidget';
|
|
|
|
Class _statelessWidget;
|
|
Class _state;
|
|
Class _statefulWidget;
|
|
bool _frameworkTypesLocated = false;
|
|
|
|
static final Uri _frameworkLibrary =
|
|
Uri.parse('package:flutter/src/widgets/framework.dart');
|
|
|
|
/// Mark [uri] as invalidated.
|
|
void invalidate(Uri uri) {
|
|
_invalidatedLibraries.add(uri);
|
|
}
|
|
|
|
/// Reset the invalidated libraries.
|
|
void reset() {
|
|
_invalidatedLibraries.clear();
|
|
}
|
|
|
|
final List<Uri> _invalidatedLibraries = <Uri>[];
|
|
|
|
/// Determine if any changes to [partialComponent] were located entirely
|
|
/// within the class body of a single `StatefulWidget`, `StatelessWidget` or
|
|
/// `State` subtype.
|
|
///
|
|
/// Returns the class name if located, otherwise `null`.
|
|
String checkSingleWidgetTypeModified(
|
|
Component lastGoodComponent,
|
|
Component partialComponent,
|
|
ClassHierarchy classHierarchy,
|
|
) {
|
|
if (!_frameworkTypesLocated ||
|
|
lastGoodComponent == null ||
|
|
_invalidatedLibraries.length != 1) {
|
|
return null;
|
|
}
|
|
Uri importUri = _invalidatedLibraries[0];
|
|
Library library;
|
|
for (Library candidateLibrary in partialComponent.libraries) {
|
|
if (candidateLibrary.importUri == importUri) {
|
|
library = candidateLibrary;
|
|
break;
|
|
}
|
|
}
|
|
if (library == null) {
|
|
return null;
|
|
}
|
|
List<int> oldSource = lastGoodComponent.uriToSource[library.fileUri].source;
|
|
List<int> newSource = partialComponent.uriToSource[library.fileUri].source;
|
|
// Library was added and does not exist in the old component.
|
|
if (oldSource == null) {
|
|
return null;
|
|
}
|
|
int newStartIndex = 0;
|
|
int newEndIndex = newSource.length - 1;
|
|
int oldStartIndex = 0;
|
|
int oldEndIndex = oldSource.length - 1;
|
|
|
|
while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) {
|
|
if (newSource[newStartIndex] != oldSource[oldStartIndex]) {
|
|
break;
|
|
}
|
|
newStartIndex += 1;
|
|
oldStartIndex += 1;
|
|
}
|
|
while (newEndIndex > newStartIndex && oldEndIndex > oldStartIndex) {
|
|
if (newSource[newEndIndex] != oldSource[oldEndIndex]) {
|
|
break;
|
|
}
|
|
newEndIndex -= 1;
|
|
oldEndIndex -= 1;
|
|
}
|
|
|
|
Class newClass =
|
|
_locateContainingClass(library, newStartIndex, newEndIndex);
|
|
if (newClass == null) {
|
|
return null;
|
|
}
|
|
|
|
Library oldLibrary =
|
|
lastGoodComponent.libraries.firstWhere((Library library) {
|
|
return library.importUri == importUri;
|
|
});
|
|
|
|
Class oldClass =
|
|
_locateContainingClass(oldLibrary, oldStartIndex, oldEndIndex);
|
|
|
|
if (oldClass == null || oldClass.name != newClass.name) {
|
|
return null;
|
|
}
|
|
|
|
// Update the class references to stateless, stateful, and state classes.
|
|
for (Library library in classHierarchy.knownLibraries) {
|
|
if (library?.importUri == _frameworkLibrary) {
|
|
_locatedClassDeclarations(library);
|
|
}
|
|
}
|
|
|
|
if (classHierarchy.isSubclassOf(newClass, _statelessWidget) ||
|
|
classHierarchy.isSubclassOf(newClass, _statefulWidget)) {
|
|
if (classHierarchy.isExtended(newClass) ||
|
|
classHierarchy.isUsedAsMixin(newClass)) {
|
|
return null;
|
|
}
|
|
return newClass.name;
|
|
}
|
|
|
|
// For changes to State classes, locate the name of the corresponding
|
|
// StatefulWidget that is provided as a type parameter. If the bounds are
|
|
// StatefulWidget itself, fail as that indicates the type was not
|
|
// specified.
|
|
Supertype stateSuperType =
|
|
classHierarchy.getClassAsInstanceOf(newClass, _state);
|
|
if (stateSuperType != null) {
|
|
if (stateSuperType.typeArguments.length != 1) {
|
|
return null;
|
|
}
|
|
DartType widgetType = stateSuperType.typeArguments[0];
|
|
if (widgetType is InterfaceType) {
|
|
Class statefulWidgetType = widgetType.classNode;
|
|
if (statefulWidgetType.name == _statefulWidgetClassName) {
|
|
return null;
|
|
}
|
|
if (classHierarchy.isExtended(statefulWidgetType) ||
|
|
classHierarchy.isUsedAsMixin(statefulWidgetType)) {
|
|
return null;
|
|
}
|
|
return statefulWidgetType.name;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Locate the that fully contains the edit range, or null.
|
|
Class _locateContainingClass(
|
|
Library library, int startOffset, int endOffset) {
|
|
for (Class classDeclaration in library.classes) {
|
|
if (classDeclaration.startFileOffset <= startOffset &&
|
|
classDeclaration.fileEndOffset >= endOffset) {
|
|
return classDeclaration;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void _locatedClassDeclarations(Library library) {
|
|
for (Class classDeclaration in library.classes) {
|
|
if (classDeclaration.name == _statelessWidgetClassName) {
|
|
_statelessWidget = classDeclaration;
|
|
} else if (classDeclaration.name == _statefulWidgetClassName) {
|
|
_statefulWidget = classDeclaration;
|
|
} else if (classDeclaration.name == _stateClassName) {
|
|
_state = classDeclaration;
|
|
}
|
|
}
|
|
}
|
|
}
|