From f4d994951862d0153efabeb98cc0cf5eb842174e Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Fri, 22 Mar 2019 10:23:22 -0700 Subject: [PATCH] check that public API of dart:ui conforms to web implementation (#8256) --- ci/build.sh | 6 ++ web_sdk/pubspec.yaml | 6 ++ web_sdk/test/api_conform_test.dart | 134 +++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 web_sdk/pubspec.yaml create mode 100644 web_sdk/test/api_conform_test.dart diff --git a/ci/build.sh b/ci/build.sh index 57699445121..cd0024147ba 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -12,3 +12,9 @@ ninja -C out/host_debug_unopt generate_dart_ui # Analyze the dart UI flutter/ci/analyze.sh flutter/ci/licenses.sh + +# Check that dart libraries conform +cd flutter/web_sdk +pub get +cd .. +dart web_sdk/test/api_conform_test.dart diff --git a/web_sdk/pubspec.yaml b/web_sdk/pubspec.yaml new file mode 100644 index 00000000000..46234190eea --- /dev/null +++ b/web_sdk/pubspec.yaml @@ -0,0 +1,6 @@ +name: web_sdk_tests +author: Flutter Authors + +dev_dependencies: + analyzer: any + test: any diff --git a/web_sdk/test/api_conform_test.dart b/web_sdk/test/api_conform_test.dart new file mode 100644 index 00000000000..1a7f8c53a7c --- /dev/null +++ b/web_sdk/test/api_conform_test.dart @@ -0,0 +1,134 @@ +// Copyright 2013 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:analyzer/analyzer.dart'; + +int main() { + // These files just contain imports to the part files; + final CompilationUnit uiUnit = parseDartFile('lib/ui/ui.dart', + parseFunctionBodies: false, suppressErrors: false); + final CompilationUnit webUnit = parseDartFile('lib/stub_ui/ui.dart', + parseFunctionBodies: false, suppressErrors: false); + final Map uiClasses = {}; + final Map webClasses = {}; + + // Gather all public classes from each library. For now we are skiping + // other top level members. + _collectPublicClasses(uiUnit, uiClasses, 'lib/ui/'); + _collectPublicClasses(webUnit, webClasses, 'lib/stub_ui/'); + + if (uiClasses.isEmpty || webClasses.isEmpty) { + print('Warning: did not resolve any classes.'); + } + + bool failed = false; + print('Checking ${uiClasses.length} public classes.'); + for (String className in uiClasses.keys) { + final ClassDeclaration uiClass = uiClasses[className]; + final ClassDeclaration webClass = webClasses[className]; + // If the web class is missing there isn't much left to do here. Print a + // warning and move along. + if (webClass == null) { + failed = true; + print('Warning: lib/ui/ui.dart contained public class $className, but ' + 'this was missing from lib/stub_ui/ui.dart.'); + continue; + } + // Next will check that the public methods exposed in each library are + // identical. + final Map uiMethods = {}; + final Map webMethods = {}; + _collectPublicMethods(uiClass, uiMethods); + _collectPublicMethods(webClass, webMethods); + + for (String methodName in uiMethods.keys) { + final MethodDeclaration uiMethod = uiMethods[methodName]; + final MethodDeclaration webMethod = webMethods[methodName]; + if (webMethod == null) { + failed = true; + print( + 'Warning: lib/ui/ui.dart $className.$methodName is missing from lib/stub_ui/ui.dart.', + ); + continue; + } + if (uiMethod.parameters == null || webMethod.parameters == null) { + continue; + } + if (uiMethod.parameters.parameters.length != webMethod.parameters.parameters.length) { + failed = true; + print( + 'Warning: lib/ui/ui.dart $className.$methodName has a different parameter ' + 'length than in lib/stub_ui/ui.dart.'); + } + // Technically you could re-order named parameters and still be valid, + // but we enforce that they are identical. + for (int i = 0; i < uiMethod.parameters.parameters.length && i < webMethod.parameters.parameters.length; i++) { + final FormalParameter uiParam = uiMethod.parameters.parameters[i]; + final FormalParameter webParam = webMethod.parameters.parameters[i]; + if (webParam.identifier.name != uiParam.identifier.name) { + failed = true; + print('Warning: lib/ui/ui.dart $className.$methodName parameter $i' + ' ${uiParam.identifier.name} has a different name in lib/stub_ui/ui.dart.'); + } + if (uiParam.isPositional && !webParam.isPositional) { + failed = true; + print('Warning: lib/ui/ui.dart $className.$methodName parameter $i' + '${uiParam.identifier.name} is positional, but not in lib/stub_ui/ui.dart.'); + } + if (uiParam.isNamed && !webParam.isNamed) { + failed = true; + print('Warning: lib/ui/ui.dart $className.$methodName parameter $i' + '${uiParam.identifier.name} is named, but not in lib/stub_ui/ui.dart.'); + } + } + } + } + if (failed) { + print('Failure!'); + return 1; + } + print('Success!'); + return 0; +} + +// Collects all public classes defined by the part files of [unit]. +void _collectPublicClasses(CompilationUnit unit, + Map destination, String root) { + for (Directive directive in unit.directives) { + if (directive is! PartDirective) { + continue; + } + final PartDirective partDirective = directive; + final String literalUri = partDirective.uri.toString(); + final CompilationUnit subUnit = parseDartFile( + '$root${literalUri.substring(1, literalUri.length - 1)}', + parseFunctionBodies: false, + suppressErrors: false, + ); + for (CompilationUnitMember member in subUnit.declarations) { + if (member is! ClassDeclaration) { + continue; + } + final ClassDeclaration classDeclaration = member; + if (classDeclaration.name.name.startsWith('_')) { + continue; + } + destination[classDeclaration.name.name] = classDeclaration; + } + } +} + +void _collectPublicMethods(ClassDeclaration classDeclaration, + Map destination) { + for (ClassMember member in classDeclaration.members) { + if (member is! MethodDeclaration) { + continue; + } + final MethodDeclaration method = member; + if (method.name.name.startsWith('_')) { + continue; + } + destination[method.name.name] = method; + } +}