analyzer: Refactor how the mock 'dart:ui' library is discovered

Fixes https://github.com/dart-lang/sdk/issues/62234 by making the
'dart:ui' library discoverable in the same way that the real one is
discoverable: by the sky_engine _embedder.yaml file.

Coupled with this change is the motivation: the stubs are out-of-date,
with `double width` and `double height` parameters on the Preview
constructor. But this has been changed to use a `Size` object, and
`Size` is written in 'dart:ui'. So to write a test that uses `Size`, the
test now needs proper access to 'dart:ui', and we should not write
'package:ui/ui.dart' in our test cases. These changes are in
`.../lib/mock_packages/package_content/flutter/lib/src/widget_previews/widget_previews.dart`
and in `invalid_widget_preview_private_argument_test.dart`.

Change-Id: I6f608eb3e1f431ad576ba8f3572454e42a39f9f1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467680
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Sam Rawlins
2026-01-13 15:32:51 -08:00
committed by Commit Queue
parent 68e2db8ce6
commit 25f0d80c15
18 changed files with 165 additions and 78 deletions
@@ -148,6 +148,7 @@ abstract class AbstractAnalysisServerIntegrationTest extends IntegrationTest
/// updates.
bool _subscribedToServerStatus = false;
@override
String dartSdkPath = path.dirname(path.dirname(Platform.resolvedExecutable));
StreamController<Request> serverToClientRequestsController =
@@ -56,6 +56,9 @@ class AbstractContextTest
List<String> get collectionIncludedPaths => [workspaceRootPath];
@override
String get dartSdkPath => sdkRoot.path;
/// The line terminator being used for test files and to be expected in edits.
String get eol => testEol;
@@ -74,6 +74,8 @@ abstract class ContextResolutionTest with ResourceProviderMixin {
void Function(Notification)? notificationListener;
String get dartSdkPath => sdkRoot.path;
bool get retainDataForTesting => false;
Folder get sdkRoot => newFolder('/sdk');
@@ -21,7 +21,7 @@ void main() {
class DocumentColorPresentationTest extends AbstractLspAnalysisServerTest {
late TestCode code;
late String testFilePath;
final uiImportUri = 'package:ui/ui.dart';
final uiImportUri = 'dart:ui';
Range get colorRange => code.range.range;
set content(String content) => code = TestCode.parse(content);
Uri get testFileUri => pathContext.toUri(testFilePath);
@@ -397,7 +397,7 @@ var white = [!Color.fromRGBO(191, 128, 64, 1)!];
// allow a picker, but we want to ensure the generated code needs to add
// an additional import to reference the `Color` class.
content = '''
import 'package:flutter/material.dart' show Colors;^
^import 'package:flutter/material.dart' show Colors;
const white = [!Colors.white!];
''';
@@ -409,7 +409,7 @@ const white = [!Colors.white!];
start: code.position.position,
end: code.position.position,
),
newText: "${eol}import '$uiImportUri';",
newText: "import '$uiImportUri';$eol$eol",
),
];
@@ -76,6 +76,9 @@ abstract class AbstractLspAnalysisServerTest
DartFixPromptManager? get dartFixPromptManager => null;
@override
String get dartSdkPath => sdkRoot.path;
@override
LspClientCapabilities get editorClientCapabilities =>
server.editorClientCapabilities!;
@@ -91,6 +94,8 @@ abstract class AbstractLspAnalysisServerTest
bool get retainDataForTesting => false;
Folder get sdkRoot => newFolder('/sdk');
AnalysisServerOptions get serverOptions => AnalysisServerOptions();
@override
@@ -269,8 +274,6 @@ abstract class AbstractLspAnalysisServerTest
processRunner = MockProcessRunner();
channel = MockLspServerChannel(debugPrintCommunication);
// Create an SDK in the mock file system.
var sdkRoot = newFolder('/sdk');
createMockSdk(resourceProvider: resourceProvider, root: sdkRoot);
errorNotifier = ErrorNotifier();
@@ -187,12 +187,12 @@ void f() {
"kind": "ENUM",
"enumItems": [
{
"libraryUri": "package:ui/ui.dart",
"libraryUri": "dart:ui",
"className": "FontStyle",
"name": "normal"
},
{
"libraryUri": "package:ui/ui.dart",
"libraryUri": "dart:ui",
"className": "FontStyle",
"name": "italic"
}
@@ -24,6 +24,8 @@ mixin ConfigurationFilesMixin on MockPackagesMixin {
/// This allows `package:vector_math/vector_math_64.dart` imports to resolve.
bool get addVectorMathPackageDep => false;
String get dartSdkPath;
/// The Dart language version of the test package being used for testing.
String get testPackageLanguageVersion => _latestLanguageVersion;
@@ -68,8 +70,8 @@ mixin ConfigurationFilesMixin on MockPackagesMixin {
}
if (flutter) {
var uiLibFolder = addUI();
config.add(name: 'ui', rootPath: uiLibFolder.parent.path);
var skyEnginePath = addSkyEngine(sdkPath: dartSdkPath).parent.path;
config.add(name: 'sky_engine', rootPath: skyEnginePath);
var flutterLibFolder = addFlutter();
config.add(name: 'flutter', rootPath: flutterLibFolder.parent.path);
@@ -403,8 +403,8 @@ class _VisibleOutsideTemplate {
}
if (flutter) {
var uiPath = addUI().parent.path;
config.add(name: 'ui', rootPath: uiPath);
var skyEnginePath = addSkyEngine(sdkPath: sdkRoot.path).parent.path;
config.add(name: 'sky_engine', rootPath: skyEnginePath);
var flutterPath = addFlutter().parent.path;
config.add(name: 'flutter', rootPath: flutterPath);
@@ -80,6 +80,33 @@ Widget extraPrivateName() => Text('Foo');
);
}
test_invalidPrivatePreviewArguments_inArgumentExpression() async {
const String kPrivateTextScaleFactor = '_textScaleFactor';
await assertErrorsInCode(
'''
import 'package:flutter/widget_previews.dart';
import 'package:flutter/widgets.dart';
const double $kPrivateTextScaleFactor = 2.0;
@Preview(textScaleFactor: $kPrivateTextScaleFactor + 1)
Widget numericExpressionWithPrivateDouble() => Text('Foo');
''',
[
error(
diag.invalidWidgetPreviewPrivateArgument,
134,
37,
correctionContains: correctionMessageBuilder(
kPrivateTextScaleFactor,
kPrivateTextScaleFactor.substring(1),
),
),
],
);
}
test_invalidPrivatePreviewArguments_interpolatedInStringArgument() async {
const String kPrivateName = '_privateName';
@@ -108,47 +135,37 @@ Widget privateNameStringInterp() => Text('Foo');
}
test_invalidPrivatePreviewArguments_size() async {
// TODO(srawlins): Update to the new Size argument.
const String kPrivateWidth = '_privateWidth';
const String kPrivateHeight = '_privateHeight';
const String kPrivateSize = '_privateSize';
const String kPrivateTextScaleFactor = '_textScaleFactor';
await assertErrorsInCode(
'''
import 'dart:ui';
import 'package:flutter/widget_previews.dart';
import 'package:flutter/widgets.dart';
const double $kPrivateWidth = 42.0;
const double $kPrivateHeight = 24.0;
const Size $kPrivateSize = Size(42.0, 24.0);
const double $kPrivateTextScaleFactor = 2.0;
@Preview(width: $kPrivateWidth,
height: $kPrivateHeight,
textScaleFactor: $kPrivateTextScaleFactor)
@Preview(
size: $kPrivateSize,
textScaleFactor: $kPrivateTextScaleFactor,
)
Widget privateDoubles() => Text('Foo');
''',
[
error(
diag.invalidWidgetPreviewPrivateArgument,
205,
20,
200,
18,
correctionContains: correctionMessageBuilder(
kPrivateWidth,
kPrivateWidth.substring(1),
kPrivateSize,
kPrivateSize.substring(1),
),
),
error(
diag.invalidWidgetPreviewPrivateArgument,
235,
22,
correctionContains: correctionMessageBuilder(
kPrivateHeight,
kPrivateHeight.substring(1),
),
),
error(
diag.invalidWidgetPreviewPrivateArgument,
267,
222,
33,
correctionContains: correctionMessageBuilder(
kPrivateTextScaleFactor,
@@ -187,33 +204,6 @@ Widget privateThemeData() => Text('Foo');
);
}
test_invalidPrivatePreviewArguments_widthInArgument() async {
const String kPrivateWidth = '_privateWidth';
await assertErrorsInCode(
'''
import 'package:flutter/widget_previews.dart';
import 'package:flutter/widgets.dart';
const double $kPrivateWidth = 42.0;
@Preview(width: $kPrivateWidth + 10)
Widget numericExpressionWithPrivateDouble() => Text('Foo');
''',
[
error(
diag.invalidWidgetPreviewPrivateArgument,
132,
25,
correctionContains: correctionMessageBuilder(
kPrivateWidth,
kPrivateWidth.substring(1),
),
),
],
);
}
test_invalidPrivatePreviewArguments_wrapper() async {
const String kPrivateWrapper = '_privateWrapper';
+3
View File
@@ -1,3 +1,6 @@
## 0.1.9-dev
- Deprecate `MockPackagesMixin.addUI`; This is replaced by `addSkyEngine`.
## 0.1.8
- Require version `10.0.0` of the `analyzer` package.
+2 -1
View File
@@ -34,8 +34,9 @@ package:analyzer_testing/mock_packages/mock_packages.dart:
addKernel (method: Folder Function(), deprecated)
addMeta (method: Folder Function())
addPedantic (method: Folder Function(), deprecated)
addSkyEngine (method: Folder Function({required String sdkPath}))
addTestReflectiveLoader (method: Folder Function())
addUI (method: Folder Function())
addUI (method: Folder Function(), deprecated)
addVectorMath (method: Folder Function())
package:analyzer_testing/package_root.dart:
packageRoot (static getter: String)
@@ -184,11 +184,57 @@ mixin MockPackagesMixin {
return packageFolder.getChildAssumingFolder('lib');
}
@Deprecated(
"Use 'addSkyEngine' to include stubs (mocks) for the 'dart:ui' library",
)
Folder addUI() {
var packageFolder = _addFiles2('ui', mock_ui.units);
return packageFolder.getChildAssumingFolder('lib');
}
/// Adds a mock sky_engine package with an `_embedder.yaml` file, and returns
/// the [Folder] of the sky_engine package's 'lib' directory.
Folder addSkyEngine({required String sdkPath}) {
// Emulate sky engine by writing to disk:
// * a `sky_engine/lib` directory,
// * a `sky_engine/lib/_embedder.yaml` file which points to the Dart SDK
// sources, and
// * the `dart:ui` sources into `sky_engine/lib/ui`.
var packageFolder = _addFiles2('ui', mock_ui.units);
var skyEngineFolder = resourceProvider.getFolder(
resourceProvider.convertPath('$packagesRootPath/sky_engine'),
)..create();
var skyEngineLibFolder = skyEngineFolder.getChildAssumingFolder('lib')
..create();
var embedderFile = skyEngineLibFolder.getChildAssumingFile(
'_embedder.yaml',
);
embedderFile.writeAsStringSync('''
embedded_libs:
"dart:ui": "${packageFolder.path}/lib/ui.dart"
"dart:async": "$sdkPath/lib/async/async.dart"
"dart:collection": "$sdkPath/lib/collection/collection.dart"
"dart:convert": "$sdkPath/lib/convert/convert.dart"
"dart:core": "$sdkPath/lib/core/core.dart"
"dart:developer": "$sdkPath/lib/developer/developer.dart"
"dart:ffi": "$sdkPath/lib/ffi/ffi.dart"
"dart:html": "$sdkPath/lib/html/dart2js/html_dart2js.dart"
"dart:io": "$sdkPath/lib/io/io.dart"
"dart:isolate": "$sdkPath/lib/isolate/isolate.dart"
"dart:js": "$sdkPath/lib/js/js.dart"
"dart:js_interop": "$sdkPath/lib/js/js_interop.dart"
"dart:math": "$sdkPath/lib/math/math.dart"
"dart:typed_data": "$sdkPath/lib/typed_data/typed_data.dart"
"dart:_interceptors": "$sdkPath/lib/_interceptors/interceptors.dart"
"dart:_internal": "$sdkPath/lib/internal/internal.dart"
''');
return skyEngineLibFolder;
}
Folder addVectorMath() {
var packageFolder = _addFiles2('vector_math', mock_vector_math.units);
return packageFolder.getChildAssumingFolder('lib');
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:ui/ui.dart' show TextDirection;
import 'dart:ui' show TextDirection;
export 'package:ui/ui.dart'
export 'dart:ui'
show
BlendMode,
Color,
@@ -5,24 +5,52 @@
import 'package:flutter/material.dart' show Brightness;
import 'package:flutter/widgets.dart';
typedef PreviewTheme = PreviewThemeData Function();
typedef WidgetWrapper = Widget Function(Widget);
typedef PreviewLocalizations = PreviewLocalizationsData Function();
base class Preview {
const Preview({
this.name,
this.width,
this.height,
this.textScaleFactor,
this.wrapper,
this.theme,
this.brightness,
String group = 'Default',
String? name,
Size? size,
double? textScaleFactor,
WidgetWrapper? wrapper,
PreviewTheme? theme,
Brightness? brightness,
PreviewLocalizations? localizations,
}) : this._required(
group: group,
name: name,
size: size,
textScaleFactor: textScaleFactor,
wrapper: wrapper,
theme: theme,
brightness: brightness,
localizations: localizations,
);
const Preview._required({
required this.group,
required this.name,
required this.size,
required this.textScaleFactor,
required this.wrapper,
required this.theme,
required this.brightness,
required this.localizations,
});
final String group;
final String? name;
final double? width;
final double? height;
final Size? size;
final double? textScaleFactor;
final Widget Function(Widget)? wrapper;
final PreviewThemeData Function()? theme;
final WidgetWrapper? wrapper;
final PreviewTheme? theme;
final Brightness? brightness;
final PreviewLocalizations? localizations;
}
base class PreviewThemeData {}
@@ -3,7 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/painting.dart';
import 'package:ui/ui.dart';
import 'dart:ui';
import 'framework.dart';
@@ -640,8 +640,8 @@ class PubPackageResolutionTest with MockPackagesMixin, ResourceProviderMixin {
}
if (addFlutterPackageDep) {
var uiPath = addUI().parent.path;
configCopy.add(name: 'ui', rootPath: uiPath);
var skyEnginePath = addSkyEngine(sdkPath: _sdkRoot.path).parent.path;
configCopy.add(name: 'sky_engine', rootPath: skyEnginePath);
var flutterPath = addFlutter().parent.path;
configCopy.add(name: 'flutter', rootPath: flutterPath);
@@ -22,6 +22,14 @@ class Radius {
const Radius.elliptical(this.x, this.y);
}
class Size {
final double width;
final double height;
const Size(this.width, this.height);
}
enum BlendMode {
clear,
src,
+1 -1
View File
@@ -1,6 +1,6 @@
name: analyzer_testing
description: Testing utilities related to the analyzer and analysis_server_plugin packages.
version: 0.1.8
version: 0.1.9-dev
repository: https://github.com/dart-lang/sdk/tree/main/pkg/analyzer_testing
environment: