Skip to content

Commit e6d292d

Browse files
committed
[flutter_tools] Filter invalid imports during widget preview LSP code generation
Filters out namespaces for files outside lib/ (those without package: or dart: URIs, except internal scaffold helpers) in PreviewPrefixedAllocator to avoid emitting invalid relative imports in the generated preview scaffold.
1 parent af7b9c2 commit e6d292d

2 files changed

Lines changed: 67 additions & 7 deletions

File tree

‎packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart‎

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,14 @@ extension on DartObject {
438438

439439
class PreviewPrefixedAllocator implements cb.Allocator {
440440
static const _doNotPrefix = ['dart:core'];
441+
static const _kImportPrefix = '_i';
442+
static const _kPackageSchemePrefix = 'package:';
443+
static const _kDartSchemePrefix = 'dart:';
444+
445+
static const _internalHelperUris = <String>{
446+
PreviewCodeGenerator._kWidgetPreviewLibraryUri,
447+
PreviewCodeGenerator._kUtilsUri,
448+
};
441449

442450
final _imports = <String, int>{};
443451
static const _kInitialKey = 1;
@@ -451,7 +459,7 @@ class PreviewPrefixedAllocator implements cb.Allocator {
451459
return symbol!;
452460
}
453461
url = _fixUrl(url);
454-
return '_i${_imports.putIfAbsent(url, _nextKey)}.$symbol';
462+
return '$_kImportPrefix${_imports.putIfAbsent(url, _nextKey)}.$symbol';
455463
}
456464

457465
void populateKnownImportPrefixes(Map<String, String> imports) {
@@ -460,17 +468,31 @@ class PreviewPrefixedAllocator implements cb.Allocator {
460468
'Attempted to populated known import prefixes when prefixes have been allocated',
461469
);
462470
}
463-
_imports.addAll({
464-
for (final MapEntry(:key, :value) in imports.entries) key: int.parse(value.substring(2)),
465-
});
466-
_keys += _imports.length;
471+
for (final MapEntry(:key, :value) in imports.entries) {
472+
final int prefixKey = int.parse(value.substring(_kImportPrefix.length));
473+
if (prefixKey >= _keys) {
474+
_keys = prefixKey + 1;
475+
}
476+
final String url = _fixUrl(key);
477+
if (_isValidImport(url)) {
478+
_imports[url] = prefixKey;
479+
}
480+
}
467481
}
468482

483+
/// Only packages, Dart SDK libraries, and the internal helper files are
484+
/// valid imports for the generated preview scaffold. Previews and libraries
485+
/// outside of `lib/` cannot be imported into the preview scaffold.
486+
static bool _isValidImport(String uri) =>
487+
uri.startsWith(_kPackageSchemePrefix) ||
488+
uri.startsWith(_kDartSchemePrefix) ||
489+
_internalHelperUris.contains(uri);
490+
469491
int _nextKey() => _keys++;
470492

471493
@override
472494
Iterable<cb.Directive> get imports =>
473-
_imports.keys.map((u) => cb.Directive.import(u, as: '_i${_imports[u]}'));
495+
_imports.keys.map((u) => cb.Directive.import(u, as: '$_kImportPrefix${_imports[u]}'));
474496
}
475497

476498
/// Applies hardcoded fixes to [url].

‎packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:code_builder/code_builder.dart' as cb;
56
import 'package:dart_style/dart_style.dart';
67
import 'package:file/memory.dart';
78
import 'package:file_testing/file_testing.dart';
@@ -476,6 +477,7 @@ List<_i1.WidgetPreview> previews() => [];
476477
'package:foo_project/preview.dart': '_i3',
477478
'preview.dart': '_i4',
478479
'package:flutter/src/widget_previews/widget_previews.dart': '_i5',
480+
'dart:ui': '_i6',
479481
},
480482
previews: <FlutterWidgetPreviewDetails>[
481483
FlutterWidgetPreviewDetails(
@@ -517,8 +519,8 @@ List<_i1.WidgetPreview> previews() => [];
517519
import 'widget_preview.dart' as _i1;
518520
import 'utils.dart' as _i2;
519521
import 'package:foo_project/preview.dart' as _i3;
520-
import 'preview.dart' as _i4;
521522
import 'package:flutter/src/widget_previews/widget_previews.dart' as _i5;
523+
import 'dart:ui' as _i6;
522524
523525
List<_i1.WidgetPreview> previews() => [
524526
_i2.buildWidgetPreview(
@@ -567,4 +569,40 @@ const String kProjectRootPath = r'${project.directory.absolute.path}';
567569
},
568570
);
569571
});
572+
573+
group('PreviewPrefixedAllocator', () {
574+
test('filters out non-package/non-SDK imports and allocates non-colliding prefixes', () {
575+
final allocator = PreviewPrefixedAllocator();
576+
allocator.populateKnownImportPrefixes(<String, String>{
577+
'widget_preview.dart': '_i1',
578+
'utils.dart': '_i2',
579+
'package:foo/foo.dart': '_i3',
580+
'preview.dart': '_i4',
581+
'package:flutter/widgets.dart': '_i5',
582+
'dart:ui': '_i6',
583+
'root.dart': '_i7',
584+
});
585+
586+
expect(allocator.imports.map((cb.Directive d) => d.url).toList(), <String>[
587+
'widget_preview.dart',
588+
'utils.dart',
589+
'package:foo/foo.dart',
590+
'package:flutter/widgets.dart',
591+
'dart:ui',
592+
]);
593+
594+
// Allocating a new reference must not collide with existing or skipped prefixes.
595+
// Maximum prefix from the populate call was _i7, so the next allocated key must be >= _i8.
596+
expect(allocator.allocate(cb.refer('Bar', 'package:bar/bar.dart')), '_i8.Bar');
597+
});
598+
599+
test('throws StateError when prefixes have already been allocated', () {
600+
final allocator = PreviewPrefixedAllocator();
601+
allocator.allocate(cb.refer('Foo', 'package:foo/foo.dart'));
602+
expect(
603+
() => allocator.populateKnownImportPrefixes(<String, String>{'dart:ui': '_i1'}),
604+
throwsStateError,
605+
);
606+
});
607+
});
570608
}

0 commit comments

Comments
 (0)