Skip to content

Commit 5ec3686

Browse files
bkonyiCommit Queue
authored andcommitted
[ Linter ] Don't mark functions annotated with @Preview() as unreachable from main
Fixes flutter/flutter#167195 Change-Id: Ie98a0fd5c1b69771b7e9e9cb54c33bfa403381e2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422520 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Ben Konyi <[email protected]>
1 parent 1f4fddd commit 5ec3686

File tree

4 files changed

+261
-9
lines changed

4 files changed

+261
-9
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart' show Brightness;
6+
import 'package:flutter/widgets.dart';
7+
8+
base class Preview {
9+
const Preview({
10+
this.name,
11+
this.width,
12+
this.height,
13+
this.textScaleFactor,
14+
this.wrapper,
15+
this.theme,
16+
this.brightness,
17+
});
18+
19+
final String? name;
20+
final double? width;
21+
final double? height;
22+
final double? textScaleFactor;
23+
final Widget Function(Widget)? wrapper;
24+
final PreviewThemeData Function()? theme;
25+
final Brightness? brightness;
26+
}
27+
28+
base class PreviewThemeData {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
export 'src/widget_previews/widget_previews.dart';

pkg/linter/lib/src/rules/unreachable_from_main.dart

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,8 @@ class _Visitor extends SimpleAstVisitor<void> {
523523
var element = declaration.declaredFragment?.element;
524524
return element != null &&
525525
element.isPublic &&
526-
!element.hasVisibleForTesting;
526+
!element.hasVisibleForTesting &&
527+
!element.hasWidgetPreview;
527528
}).toList();
528529

529530
for (var member in unusedMembers) {
@@ -556,10 +557,18 @@ class _Visitor extends SimpleAstVisitor<void> {
556557

557558
bool _isEntryPoint(Declaration e) =>
558559
e is FunctionDeclaration &&
559-
(e.name.lexeme == 'main' || e.metadata.any(_isPragmaVmEntry));
560+
(e.name.lexeme == 'main' || e.metadata.any(_isExemptingAnnotation));
560561

561-
bool _isPragmaVmEntry(Annotation annotation) {
562-
if (!annotation.isPragma) return false;
562+
bool _isExemptingAnnotation(Annotation annotation) {
563+
if (annotation.isPragma) {
564+
return _isValidVmEntryPoint(annotation);
565+
} else if (annotation.isWidgetPreview) {
566+
return true;
567+
}
568+
return false;
569+
}
570+
571+
bool _isValidVmEntryPoint(Annotation annotation) {
563572
var value = annotation.elementAnnotation?.computeConstantValue();
564573
if (value == null) return false;
565574
var name = value.getField('name');
@@ -569,27 +578,91 @@ class _Visitor extends SimpleAstVisitor<void> {
569578
}
570579
}
571580

581+
extension on Metadata {
582+
bool get hasWidgetPreview {
583+
var annotations = this.annotations;
584+
for (var i = 0; i < annotations.length; i++) {
585+
var annotation = annotations[i];
586+
if (annotation.isWidgetPreview) {
587+
return true;
588+
}
589+
}
590+
return false;
591+
}
592+
}
593+
594+
extension on ElementAnnotation {
595+
/// The URI of the Flutter widget previews library.
596+
static final Uri _flutterWidgetPreviewLibraryUri = Uri.parse(
597+
'package:flutter/src/widget_previews/widget_previews.dart',
598+
);
599+
600+
bool get isWidgetPreview {
601+
var element2 = this.element2;
602+
return element2 is ConstructorElement2 &&
603+
element2.enclosingElement2.name3 == 'Preview' &&
604+
element2.library2.uri == _flutterWidgetPreviewLibraryUri;
605+
}
606+
}
607+
608+
extension on LibraryElement2 {
609+
bool get isWidgetPreviews =>
610+
uri ==
611+
Uri.parse('package:flutter/src/widget_previews/widget_previews.dart');
612+
}
613+
572614
extension on Element2 {
573615
bool get hasVisibleForTesting => switch (this) {
574616
Annotatable(:var metadata2) => metadata2.hasVisibleForTesting,
575617
_ => false,
576618
};
619+
bool get hasWidgetPreview => switch (this) {
620+
Annotatable(:var metadata2) =>
621+
// Widget previews can be applied to public:
622+
// - Constructors (generative and factory)
623+
// - Top-level functions
624+
// - Static member functions
625+
(this is ConstructorElement2 ||
626+
this is TopLevelFunctionElement ||
627+
(this is ExecutableElement2 &&
628+
(this as ExecutableElement2).isStatic)) &&
629+
!isPrivate &&
630+
metadata2.hasWidgetPreview,
631+
_ => false,
632+
};
577633
bool get isPragma => (library2?.isDartCore ?? false) && name3 == 'pragma';
634+
bool get isWidgetPreview =>
635+
(library2?.isWidgetPreviews ?? false) && name3 == 'Preview';
578636
}
579637

580638
extension on Annotation {
581639
bool get isPragma {
640+
DartType? type = _elementType;
641+
if (type == null) {
642+
// Dunno what this is.
643+
return false;
644+
}
645+
return type is InterfaceType && type.element3.isPragma;
646+
}
647+
648+
bool get isWidgetPreview {
649+
DartType? type = _elementType;
650+
if (type == null) {
651+
// Dunno what this is.
652+
return false;
653+
}
654+
return type is InterfaceType && type.element3.isWidgetPreview;
655+
}
656+
657+
DartType? get _elementType {
582658
var element = elementAnnotation?.element2;
583-
DartType type;
659+
DartType? type;
584660
if (element is ConstructorElement2) {
585661
type = element.returnType;
586662
} else if (element is GetterElement) {
587663
type = element.returnType;
588-
} else {
589-
// Dunno what this is.
590-
return false;
591664
}
592-
return type is InterfaceType && type.element3.isPragma;
665+
return type;
593666
}
594667
}
595668

pkg/linter/test/rules/unreachable_from_main_test.dart

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ void main() {
1414

1515
@reflectiveTest
1616
class UnreachableFromMainTest extends LintRuleTest {
17+
@override
18+
bool get addFlutterPackageDep => true;
19+
1720
@override
1821
bool get addMetaPackageDep => true;
1922

@@ -1411,13 +1414,17 @@ void f() {}
14111414

14121415
test_topLevelFunction_vmEntryPoint() async {
14131416
await assertNoDiagnostics(r'''
1417+
void main() {}
1418+
14141419
@pragma('vm:entry-point')
14151420
void f6() {}
14161421
''');
14171422
}
14181423

14191424
test_topLevelFunction_vmEntryPoint_const() async {
14201425
await assertNoDiagnostics(r'''
1426+
void main() {}
1427+
14211428
const entryPoint = pragma('vm:entry-point');
14221429
@entryPoint
14231430
void f6() {}
@@ -1630,4 +1637,143 @@ typedef T = String;
16301637
[lint(24, 1)],
16311638
);
16321639
}
1640+
1641+
test_widgetPreview_classInstanceMethod() async {
1642+
await assertDiagnostics(
1643+
r'''
1644+
import 'package:flutter/widgets.dart';
1645+
import 'package:flutter/widget_previews.dart';
1646+
1647+
void main() {}
1648+
1649+
class B {
1650+
// Widget previews can't be defined with instance methods.
1651+
@Preview()
1652+
Widget foo() => Text('');
1653+
}
1654+
''',
1655+
[lint(109, 1), lint(196, 3)],
1656+
);
1657+
}
1658+
1659+
test_widgetPreview_classStaticMethod() async {
1660+
await assertDiagnostics(
1661+
r'''
1662+
import 'package:flutter/widgets.dart';
1663+
import 'package:flutter/widget_previews.dart';
1664+
1665+
void main() {}
1666+
1667+
class B {
1668+
@Preview()
1669+
static Widget foo() => Text('');
1670+
}
1671+
''',
1672+
[lint(109, 1)],
1673+
);
1674+
}
1675+
1676+
test_widgetPreview_constructor() async {
1677+
await assertDiagnostics(
1678+
r'''
1679+
import 'package:flutter/widget_previews.dart';
1680+
1681+
void main() {}
1682+
1683+
class B {
1684+
@Preview()
1685+
const B();
1686+
}
1687+
''',
1688+
[lint(70, 1)],
1689+
);
1690+
}
1691+
1692+
test_widgetPreview_factoryConstructor() async {
1693+
await assertDiagnostics(
1694+
r'''
1695+
import 'package:flutter/widget_previews.dart';
1696+
1697+
void main() {}
1698+
1699+
class B {
1700+
@Preview()
1701+
factory B.foo() => B();
1702+
1703+
const B();
1704+
}
1705+
''',
1706+
[lint(70, 1), lint(122, 1)],
1707+
);
1708+
}
1709+
1710+
test_widgetPreview_privatePreview() async {
1711+
await assertDiagnostics(
1712+
r'''
1713+
// Widget previews can't be defined with private functions.
1714+
import 'package:flutter/widget_previews.dart';
1715+
import 'package:flutter/widgets.dart';
1716+
void main() {}
1717+
1718+
class B {
1719+
@Preview()
1720+
factory B._foo() => B._();
1721+
1722+
@Preview()
1723+
B._();
1724+
1725+
@Preview()
1726+
static Widget _bar() => Text('');
1727+
}
1728+
1729+
@Preview()
1730+
void _f6() {}
1731+
''',
1732+
[
1733+
lint(168, 1),
1734+
error(WarningCode.UNUSED_ELEMENT, 197, 4),
1735+
error(WarningCode.UNUSED_ELEMENT, 267, 4),
1736+
error(WarningCode.UNUSED_ELEMENT, 306, 3),
1737+
],
1738+
);
1739+
}
1740+
1741+
test_widgetPreview_topLevelFunction() async {
1742+
await assertNoDiagnostics(r'''
1743+
import 'package:flutter/widget_previews.dart';
1744+
1745+
void main() {}
1746+
1747+
@Preview()
1748+
void f6() {}
1749+
''');
1750+
}
1751+
1752+
test_widgetPreview_topLevelFunction_const() async {
1753+
await assertNoDiagnostics(r'''
1754+
import 'package:flutter/widget_previews.dart';
1755+
void main() {}
1756+
1757+
const preview = Preview();
1758+
@preview
1759+
void f6() {}
1760+
''');
1761+
}
1762+
1763+
test_widgetPreview_topLevelFunction_customPreviewClass() async {
1764+
await assertDiagnostics(
1765+
r'''
1766+
void main() {}
1767+
1768+
class Preview {
1769+
const Preview();
1770+
}
1771+
1772+
// This isn't from package:flutter/widget_previews.dart and shouldn't be exempt.
1773+
@Preview()
1774+
void f6() {}
1775+
''',
1776+
[lint(22, 7), lint(40, 7), lint(151, 2)],
1777+
);
1778+
}
16331779
}

0 commit comments

Comments
 (0)