Skip to content

Commit f4d9949

Browse files
check that public API of dart:ui conforms to web implementation (flutter#8256)
1 parent d21cca7 commit f4d9949

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

ci/build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ ninja -C out/host_debug_unopt generate_dart_ui
1212
# Analyze the dart UI
1313
flutter/ci/analyze.sh
1414
flutter/ci/licenses.sh
15+
16+
# Check that dart libraries conform
17+
cd flutter/web_sdk
18+
pub get
19+
cd ..
20+
dart web_sdk/test/api_conform_test.dart

web_sdk/pubspec.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: web_sdk_tests
2+
author: Flutter Authors <flutter-dev@googlegroups.com>
3+
4+
dev_dependencies:
5+
analyzer: any
6+
test: any

web_sdk/test/api_conform_test.dart

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2013 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:analyzer/analyzer.dart';
6+
7+
int main() {
8+
// These files just contain imports to the part files;
9+
final CompilationUnit uiUnit = parseDartFile('lib/ui/ui.dart',
10+
parseFunctionBodies: false, suppressErrors: false);
11+
final CompilationUnit webUnit = parseDartFile('lib/stub_ui/ui.dart',
12+
parseFunctionBodies: false, suppressErrors: false);
13+
final Map<String, ClassDeclaration> uiClasses = <String, ClassDeclaration>{};
14+
final Map<String, ClassDeclaration> webClasses = <String, ClassDeclaration>{};
15+
16+
// Gather all public classes from each library. For now we are skiping
17+
// other top level members.
18+
_collectPublicClasses(uiUnit, uiClasses, 'lib/ui/');
19+
_collectPublicClasses(webUnit, webClasses, 'lib/stub_ui/');
20+
21+
if (uiClasses.isEmpty || webClasses.isEmpty) {
22+
print('Warning: did not resolve any classes.');
23+
}
24+
25+
bool failed = false;
26+
print('Checking ${uiClasses.length} public classes.');
27+
for (String className in uiClasses.keys) {
28+
final ClassDeclaration uiClass = uiClasses[className];
29+
final ClassDeclaration webClass = webClasses[className];
30+
// If the web class is missing there isn't much left to do here. Print a
31+
// warning and move along.
32+
if (webClass == null) {
33+
failed = true;
34+
print('Warning: lib/ui/ui.dart contained public class $className, but '
35+
'this was missing from lib/stub_ui/ui.dart.');
36+
continue;
37+
}
38+
// Next will check that the public methods exposed in each library are
39+
// identical.
40+
final Map<String, MethodDeclaration> uiMethods = <String, MethodDeclaration>{};
41+
final Map<String, MethodDeclaration> webMethods = <String, MethodDeclaration>{};
42+
_collectPublicMethods(uiClass, uiMethods);
43+
_collectPublicMethods(webClass, webMethods);
44+
45+
for (String methodName in uiMethods.keys) {
46+
final MethodDeclaration uiMethod = uiMethods[methodName];
47+
final MethodDeclaration webMethod = webMethods[methodName];
48+
if (webMethod == null) {
49+
failed = true;
50+
print(
51+
'Warning: lib/ui/ui.dart $className.$methodName is missing from lib/stub_ui/ui.dart.',
52+
);
53+
continue;
54+
}
55+
if (uiMethod.parameters == null || webMethod.parameters == null) {
56+
continue;
57+
}
58+
if (uiMethod.parameters.parameters.length != webMethod.parameters.parameters.length) {
59+
failed = true;
60+
print(
61+
'Warning: lib/ui/ui.dart $className.$methodName has a different parameter '
62+
'length than in lib/stub_ui/ui.dart.');
63+
}
64+
// Technically you could re-order named parameters and still be valid,
65+
// but we enforce that they are identical.
66+
for (int i = 0; i < uiMethod.parameters.parameters.length && i < webMethod.parameters.parameters.length; i++) {
67+
final FormalParameter uiParam = uiMethod.parameters.parameters[i];
68+
final FormalParameter webParam = webMethod.parameters.parameters[i];
69+
if (webParam.identifier.name != uiParam.identifier.name) {
70+
failed = true;
71+
print('Warning: lib/ui/ui.dart $className.$methodName parameter $i'
72+
' ${uiParam.identifier.name} has a different name in lib/stub_ui/ui.dart.');
73+
}
74+
if (uiParam.isPositional && !webParam.isPositional) {
75+
failed = true;
76+
print('Warning: lib/ui/ui.dart $className.$methodName parameter $i'
77+
'${uiParam.identifier.name} is positional, but not in lib/stub_ui/ui.dart.');
78+
}
79+
if (uiParam.isNamed && !webParam.isNamed) {
80+
failed = true;
81+
print('Warning: lib/ui/ui.dart $className.$methodName parameter $i'
82+
'${uiParam.identifier.name} is named, but not in lib/stub_ui/ui.dart.');
83+
}
84+
}
85+
}
86+
}
87+
if (failed) {
88+
print('Failure!');
89+
return 1;
90+
}
91+
print('Success!');
92+
return 0;
93+
}
94+
95+
// Collects all public classes defined by the part files of [unit].
96+
void _collectPublicClasses(CompilationUnit unit,
97+
Map<String, ClassDeclaration> destination, String root) {
98+
for (Directive directive in unit.directives) {
99+
if (directive is! PartDirective) {
100+
continue;
101+
}
102+
final PartDirective partDirective = directive;
103+
final String literalUri = partDirective.uri.toString();
104+
final CompilationUnit subUnit = parseDartFile(
105+
'$root${literalUri.substring(1, literalUri.length - 1)}',
106+
parseFunctionBodies: false,
107+
suppressErrors: false,
108+
);
109+
for (CompilationUnitMember member in subUnit.declarations) {
110+
if (member is! ClassDeclaration) {
111+
continue;
112+
}
113+
final ClassDeclaration classDeclaration = member;
114+
if (classDeclaration.name.name.startsWith('_')) {
115+
continue;
116+
}
117+
destination[classDeclaration.name.name] = classDeclaration;
118+
}
119+
}
120+
}
121+
122+
void _collectPublicMethods(ClassDeclaration classDeclaration,
123+
Map<String, MethodDeclaration> destination) {
124+
for (ClassMember member in classDeclaration.members) {
125+
if (member is! MethodDeclaration) {
126+
continue;
127+
}
128+
final MethodDeclaration method = member;
129+
if (method.name.name.startsWith('_')) {
130+
continue;
131+
}
132+
destination[method.name.name] = method;
133+
}
134+
}

0 commit comments

Comments
 (0)