Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
[tools] Require implementation package README warning
Ensures that the README in the example of a federated plugin
implementation package explains that it is intended as a testbed, not a
real example to follow.

Updates the existing READMEs accordingly to fix violations of the new
check.

Fixes flutter/flutter#111904
  • Loading branch information
stuartmorgan-g committed Sep 21, 2022
commit d51f25e06b855d4470764f4be0c0b7ce48c5f3e3
10 changes: 8 additions & 2 deletions packages/url_launcher/url_launcher_linux/example/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# url_launcher_example
# Platform Implementation Test App

Demonstrates how to use the url_launcher plugin.
This is a test app for manual testing and automated integration testing
of this platform implementation. It is not intended to demonstrate actual use of
this package, since the intent is that plugin clients use the app-facing
package.

Unless you are making changes to this implementation package, this example is
very unlikely to be relevant.
6 changes: 6 additions & 0 deletions script/tool/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## NEXT

* Adds `readme-check` validation that the example/README.md for a federated
plugin's implementation packages has a warning about the intended use of the
example instead of the template boilerplate.

## 0.10.0

* Improves the logic in `version-check` to determine what changes don't require
Expand Down
67 changes: 59 additions & 8 deletions script/tool/lib/src/readme_check_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:file/file.dart';
import 'package:git/git.dart';
import 'package:http/http.dart';
import 'package:platform/platform.dart';
import 'package:yaml/yaml.dart';

Expand Down Expand Up @@ -104,11 +105,8 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
errors.add(blockValidationError);
}

if (_containsTemplateBoilerplate(readmeLines)) {
printError('${indentation}The boilerplate section about getting started '
'with Flutter should not be left in.');
errors.add('Contains template boilerplate');
}
errors.addAll(_validateBoilerplate(readmeLines,
mainPackage: mainPackage, isExample: isExample));

// Check if this is the main readme for a plugin, and if so enforce extra
// checks.
Expand Down Expand Up @@ -284,10 +282,63 @@ ${indentation * 2}Please use standard capitalizations: ${sortedListString(expect
return null;
}

/// Returns true if the README still has the boilerplate from the
/// `flutter create` templates.
bool _containsTemplateBoilerplate(List<String> readmeLines) {
/// Validates [readmeLines], outputing error messages for any issue and
/// returning an array of error summaries (if any).
///
/// Returns an empty array if validation passes.
List<String> _validateBoilerplate(
List<String> readmeLines, {
required RepositoryPackage mainPackage,
required bool isExample,
}) {
final List<String> errors = <String>[];

if (_containsTemplateFlutterBoilerplate(readmeLines)) {
printError('${indentation}The boilerplate section about getting started '
'with Flutter should not be left in.');
errors.add('Contains template boilerplate');
}

// Enforce a repository-standard message in implementation plugin examples,
// since they aren't typical examples, which has been a source of
// confusion for plugin clients who find them.
if (isExample && mainPackage.isPlatformImplementation) {
if (_containsExampleBoilerplate(readmeLines)) {
printError('${indentation}The boilerplate should not be left in for a '
"federated plugin implementation package's example.");
errors.add('Contains template boilerplate');
}
if (!_containsImplementationExampleExplanation(readmeLines)) {
printError('${indentation}The example README for a platform '
'implementation package should warn readers about its intended '
'use. Please copy the example README from another implementation '
'package in this repository.');
errors.add('Missing implementation package example warning');
}
}

return errors;
}

/// Returns true if the README still has unwanted parts of the boilerplate
/// from the `flutter create` templates.
bool _containsTemplateFlutterBoilerplate(List<String> readmeLines) {
return readmeLines.any((String line) =>
line.contains('For help getting started with Flutter'));
}

/// Returns true if the README still has the generic description of an
/// example from the `flutter create` templates.
bool _containsExampleBoilerplate(List<String> readmeLines) {
return readmeLines
.any((String line) => line.contains('Demonstrates how to use the'));
}

/// Returns true if the README contains the repository-standard explanation of
/// the purpose of a federated plugin implementation's example.
bool _containsImplementationExampleExplanation(List<String> readmeLines) {
return readmeLines.contains('# Platform Implementation Test App') &&
readmeLines
.any((String line) => line.contains('This is a test app for'));
}
}
141 changes: 141 additions & 0 deletions script/tool/test/readme_check_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,147 @@ samples, guidance on mobile development, and a full API reference.
);
});

test(
'fails when a plugin implementation package example README has the '
'template boilerplate', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin_ios', packagesDir.childDirectory('a_plugin'));
package.getExamples().first.readmeFile.writeAsStringSync('''
# a_plugin_ios_example

Demonstrates how to use the a_plugin_ios plugin.
''');

Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['readme-check'], errorHandler: (Error e) {
commandError = e;
});

expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The boilerplate should not be left in for a federated plugin '
"implementation package's example."),
contains('Contains template boilerplate'),
]),
);
});

test(
'allows the template boilerplate in the example README for packages '
'other than plugin implementation packages', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir.childDirectory('a_plugin'),
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
},
);
// Write a README with an OS support table so that the main README check
// passes.
package.readmeFile.writeAsStringSync('''
# a_plugin

| | Android |
|----------------|---------|
| **Support** | SDK 19+ |

A great plugin.
''');
package.getExamples().first.readmeFile.writeAsStringSync('''
# a_plugin_example

Demonstrates how to use the a_plugin plugin.
''');

final List<String> output =
await runCapturingPrint(runner, <String>['readme-check']);

expect(
output,
containsAll(<Matcher>[
contains(' Checking README.md...'),
contains(' Checking example/README.md...'),
]),
);
});

test(
'fails when a plugin implementation package example README does not have '
'the repo-standard message', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin_ios', packagesDir.childDirectory('a_plugin'));
package.getExamples().first.readmeFile.writeAsStringSync('''
# a_plugin_ios_example

Some random description.
''');

Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['readme-check'], errorHandler: (Error e) {
commandError = e;
});

expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The example README for a platform implementation package '
'should warn readers about its intended use. Please copy the '
'example README from another implementation package in this '
'repository.'),
contains('Missing implementation package example warning'),
]),
);
});

test('passes for a plugin implementation package with the expected content',
() async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir.childDirectory('a_plugin'),
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
},
);
// Write a README with an OS support table so that the main README check
// passes.
package.readmeFile.writeAsStringSync('''
# a_plugin

| | Android |
|----------------|---------|
| **Support** | SDK 19+ |

A great plugin.
''');
package.getExamples().first.readmeFile.writeAsStringSync('''
# Platform Implementation Test App

This is a test app for manual testing and automated integration testing
of this platform implementation. It is not intended to demonstrate actual use of
this package, since the intent is that plugin clients use the app-facing
package.

Unless you are making changes to this implementation package, this example is
very unlikely to be relevant.
''');

final List<String> output =
await runCapturingPrint(runner, <String>['readme-check']);

expect(
output,
containsAll(<Matcher>[
contains(' Checking README.md...'),
contains(' Checking example/README.md...'),
]),
);
});

test(
'fails when multi-example top-level example directory README still has '
'application template boilerplate', () async {
Expand Down