Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
const String kWidgetPreviewDtdUri = '';
const String kWidgetPreviewService = 'widget-preview';
const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
const String kProjectRootPath = r'';
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'package:flutter/widgets.dart';
import 'package:widget_preview_scaffold/src/dtd/dtd_connection_info.dart';
import 'package:widget_preview_scaffold/src/dtd/dtd_services.dart';
import 'package:widget_preview_scaffold/src/dtd/editor_service.dart';
import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart';
Expand All @@ -16,6 +17,7 @@ import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart';
class WidgetPreviewScaffoldInspectorService with WidgetInspectorService {
WidgetPreviewScaffoldInspectorService({required this.dtdServices}) {
WidgetInspectorService.instance = this;
addPubRootDirectories(<String>[kProjectRootPath]);
}

/// The DTD services instance used to communicate with the tool.
Expand Down
4 changes: 4 additions & 0 deletions packages/flutter_tools/lib/src/commands/widget_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../isolated/resident_web_runner.dart';
import '../migrations/widget_preview_gitignore_migration.dart';
import '../project.dart';
import '../resident_runner.dart';
import '../runner/flutter_command.dart';
Expand Down Expand Up @@ -322,6 +323,8 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
previewAnalytics.initializeLaunchStopwatch();
logger.sendInitializingEvent();

await WidgetPreviewGitignoreMigration(rootProject, logger).migrate();

final String? customPreviewScaffoldOutput = stringArg(kWidgetPreviewScaffoldOutputDir);
widgetPreviewScaffold = customPreviewScaffoldOutput != null
? fs.directory(customPreviewScaffoldOutput)
Expand Down Expand Up @@ -414,6 +417,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
dtdUri: _dtdService.dtdUri!,
widgetPreviewServiceName: _dtdService.widgetPreviewService,
widgetPreviewScaffoldStreamName: _dtdService.widgetPreviewScaffoldStream,
projectRootPath: rootProject.directory.absolute.path,
);

final FlutterWidgetPreviews originalPreviews = await _dtdService.getFlutterWidgetPreviews();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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 '../base/file_system.dart';
import '../base/project_migrator.dart';
import '../project.dart';

/// Adds `.widget_preview/` to the .gitignore file.
class WidgetPreviewGitignoreMigration extends ProjectMigrator {
WidgetPreviewGitignoreMigration(FlutterProject project, super.logger)
: _gitignoreFile = project.gitignoreFile;

final File _gitignoreFile;

@override
Future<void> migrate() async {
if (!_gitignoreFile.existsSync()) {
logger.printTrace('.gitignore file not found, skipping widget preview .gitignore migration.');
return;
}

final String originalContent = _gitignoreFile.readAsStringSync();

// Skip if .gitignore is already migrated.
if (originalContent.contains('.widget_preview/')) {
return;
}

logger.printTrace('.gitignore does not ignore .widget_preview/ directory, updating.');

final newContent = StringBuffer(originalContent);
if (!originalContent.endsWith('\n')) {
newContent.writeln();
}
newContent
..writeln('# Widget Preview related')
..writeln('.widget_preview/');
Comment thread
bkonyi marked this conversation as resolved.

_gitignoreFile.writeAsStringSync(newContent.toString());
}
}
5 changes: 1 addition & 4 deletions packages/flutter_tools/lib/src/project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,7 @@ class FlutterProject {

/// The location of the generated scaffolding project for hosting widget
/// previews from this project.
// TODO(bkonyi): don't create this project in $TMP.
// See https://github.com/flutter/flutter/issues/179036
late final Directory widgetPreviewScaffold = directory.fileSystem.systemTempDirectory
.createTempSync('widget_preview_scaffold');
late final Directory widgetPreviewScaffold = directory.childDirectory('.widget_preview');

/// The directory containing the generated code for this project.
Directory get generated => directory.absolute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,15 @@ class LspPreviewDetector {
// Only process one FileSystemEntity at a time so we don't invalidate an AnalysisSession that's
// in use when we call context.changeFile(...).
await mutex.runGuarded(() async {
await _fileAddedOrUpdated(filePath: event.path);
final String eventPath = event.path;
// Ignore any files under .dart_tool, .widget_preview, or ephemeral directories created by
// the tool (e.g., build/, plugin directories, etc.).
if (eventPath.doesContainDartTool ||
eventPath.doesContainWidgetPreview ||
project.ephemeralDirectories.any((dir) => eventPath.contains(dir.path))) {
return;
}
await _fileAddedOrUpdated(filePath: eventPath);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class PreviewCodeGenerator {
required Uri dtdUri,
required String widgetPreviewServiceName,
required String widgetPreviewScaffoldStreamName,
required String projectRootPath,
}) {
final emitter = cb.DartEmitter.scoped(useNullSafetySyntax: true);
final lib = cb.Library(
Expand Down Expand Up @@ -87,6 +88,13 @@ class PreviewCodeGenerator {
..type = cb.refer('String')
..assignment = cb.literalString(widgetPreviewScaffoldStreamName).code;
}),
cb.Field((b) {
b
..name = 'kProjectRootPath'
..modifier = cb.FieldModifier.constant
..type = cb.refer('String')
..assignment = cb.literalString(projectRootPath, raw: true).code;
}),
]),
);
final File generatedDtdConnectionInfoFile = fs.file(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ class PreviewDetector {
onPackageConfigChangeDetected?.call(event.path);
return;
}
// Ignore any files under .dart_tool or ephemeral directories created by
// Ignore any files under .dart_tool, .widget_preview, or ephemeral directories created by
// the tool (e.g., build/, plugin directories, etc.).
if (eventPath.doesContainDartTool ||
eventPath.doesContainWidgetPreview ||
project.ephemeralDirectories.any((dir) => eventPath.contains(dir.path))) {
return;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/flutter_tools/lib/src/widget_preview/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ extension AnnotationExtension on Annotation {

/// Convenience getters for examining [String] paths.
extension StringExtension on String {
static final RegExp _pathSeparator = RegExp(r'[/\\]');

bool get isDartFile => endsWith('.dart');
bool get isPubspec => endsWith('pubspec.yaml');
bool get doesContainDartTool => contains('.dart_tool');
bool get doesContainWidgetPreview => split(_pathSeparator).contains('.widget_preview');
}

extension LibraryElementExtension on LibraryElement {
Expand Down
3 changes: 3 additions & 0 deletions packages/flutter_tools/templates/app/.gitignore.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release

# Widget Preview related
.widget_preview/
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
const String kWidgetPreviewDtdUri = '';
const String kWidgetPreviewService = 'widget-preview';
const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
const String kProjectRootPath = r'';
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'package:flutter/widgets.dart';
import 'package:widget_preview_scaffold/src/dtd/dtd_connection_info.dart';
import 'package:widget_preview_scaffold/src/dtd/dtd_services.dart';
import 'package:widget_preview_scaffold/src/dtd/editor_service.dart';
import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart';
Expand All @@ -16,6 +17,7 @@ import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart';
class WidgetPreviewScaffoldInspectorService with WidgetInspectorService {
WidgetPreviewScaffoldInspectorService({required this.dtdServices}) {
WidgetInspectorService.instance = this;
addPubRootDirectories(<String>[kProjectRootPath]);
}

/// The DTD services instance used to communicate with the tool.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ List<_i1.WidgetPreview> previews() => [];
dtdUri: dtdUri,
widgetPreviewServiceName: 'widget-preview-service',
widgetPreviewScaffoldStreamName: 'widget-preview-stream',
projectRootPath: project.directory.absolute.path,
);

final expectedDtdConnectionInfo =
Expand All @@ -472,6 +473,8 @@ List<_i1.WidgetPreview> previews() => [];
const String kWidgetPreviewDtdUri = '$dtdUri';
const String kWidgetPreviewService = 'widget-preview-service';
const String kWidgetPreviewScaffoldStream = 'widget-preview-stream';
const String kProjectRootPath =
r'${project.directory.absolute.path}';
''';
expect(generatedDtdConnectionInfoFile.readAsStringSync(), expectedDtdConnectionInfo);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/migrations/widget_preview_gitignore_migration.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';

import '../../src/common.dart';

void main() {
group('Widget Preview .gitignore migration', () {
late MemoryFileSystem memoryFileSystem;
late BufferLogger testLogger;
late FakeFlutterProject mockProject;
late File gitignoreFile;

setUp(() {
memoryFileSystem = MemoryFileSystem.test();
gitignoreFile = memoryFileSystem.file('.gitignore');

testLogger = BufferLogger(
terminal: Terminal.test(),
outputPreferences: OutputPreferences.test(),
);

mockProject = FakeFlutterProject(fileSystem: memoryFileSystem);
});

testWithoutContext('skipped if .gitignore file is missing', () async {
final migration = WidgetPreviewGitignoreMigration(mockProject, testLogger);
await migration.migrate();
expect(gitignoreFile.existsSync(), isFalse);

expect(
testLogger.traceText,
contains('.gitignore file not found, skipping widget preview .gitignore migration.'),
);
expect(testLogger.warningText, isEmpty);
});

testWithoutContext('skipped if already migrated', () async {
const gitignoreFileContents = '''
.DS_Store
.widget_preview/
''';

gitignoreFile.writeAsStringSync(gitignoreFileContents);

final DateTime updatedAt = gitignoreFile.lastModifiedSync();

final migration = WidgetPreviewGitignoreMigration(mockProject, testLogger);
await migration.migrate();

expect(gitignoreFile.lastModifiedSync(), updatedAt);
expect(gitignoreFile.readAsStringSync(), gitignoreFileContents);
expect(testLogger.warningText, isEmpty);
});

testWithoutContext('migrates project to ignore .widget_preview directory', () async {
gitignoreFile.writeAsStringSync(
'.DS_Store\n'
'.atom/\n',
);

final migration = WidgetPreviewGitignoreMigration(mockProject, testLogger);
await migration.migrate();

expect(
gitignoreFile.readAsStringSync(),
'.DS_Store\n'
'.atom/\n'
'# Widget Preview related\n'
'.widget_preview/\n',
);

expect(
testLogger.traceText,
contains('.gitignore does not ignore .widget_preview/ directory, updating.'),
);
});
});
}

class FakeFlutterProject extends Fake implements FlutterProject {
FakeFlutterProject({required MemoryFileSystem fileSystem})
: gitignoreFile = fileSystem.file('.gitignore');

@override
File gitignoreFile;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import 'project.dart';

class BasicProject extends Project {
BasicProject({super.name});

@override
final pubspec = '''
name: test
String get pubspec =>
'''
name: $name
environment:
sdk: ^3.7.0-0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ abstract class Project {
/// Creates a flutter Project for testing.
///
/// If passed, `indexHtml` is used as the contents of the web/index.html file.
Project({this.indexHtml = _kDefaultHtml});
Project({this.indexHtml = _kDefaultHtml, String? name}) : name = name ?? 'test';

final String name;

late Directory dir;

Expand All @@ -37,7 +39,7 @@ abstract class Project {
String get generatedFile => '';
DeferredComponentsConfig? get deferredComponents => null;

Uri get mainDart => Uri.parse('package:test/main.dart');
Uri get mainDart => Uri.parse('package:$name/main.dart');

/// The contents for the index.html file of this `Project`.
///
Expand Down Expand Up @@ -67,7 +69,7 @@ abstract class Project {
writeFile(fileSystem.path.join(dir.path, 'web', 'index.html'), indexHtml);
writeFile(fileSystem.path.join(dir.path, 'web', 'flutter.js'), '');
writeFile(fileSystem.path.join(dir.path, 'web', 'flutter_service_worker.js'), '');
writePackageConfigFiles(directory: dir, mainLibName: 'test');
writePackageConfigFiles(directory: dir, mainLibName: name);
await getPackages(dir.path);
}

Expand Down
Loading
Loading