Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[flutter_plugin_tools] Validate code blocks in readme-check #5436

Merged
merged 7 commits into from
Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 7 additions & 1 deletion .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@ task:
always:
format_script: ./script/tool_runner.sh format --fail-on-change
pubspec_script: ./script/tool_runner.sh pubspec-check
readme_script: ./script/tool_runner.sh readme-check
readme_script:
- ./script/tool_runner.sh readme-check
# Re-run with --require-excerpts, skipping packages that still need
# to be converted. Once https://github.com/flutter/flutter/issues/102679
# has been fixed, this can be removed and there can just be a single
# run with --require-excerpts and no exclusions.
- ./script/tool_runner.sh readme-check --require-excerpts --exclude=script/configs/temp_exclude_excerpt.yaml
license_script: dart $PLUGIN_TOOL license-check
- name: federated_safety
# This check is only meaningful for PRs, as it validates changes
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ If editing `Info.plist` as text, add:

Change the minimum Android sdk version to 21 (or higher) in your `android/app/build.gradle` file.

```
```groovy
minSdkVersion 21
```

Expand Down
4 changes: 2 additions & 2 deletions packages/espresso/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ void main() {

The following command line command runs the test locally:

```
```sh
./gradlew app:connectedAndroidTest -Ptarget=`pwd`/../test_driver/example.dart
```

Espresso tests can also be run on [Firebase Test Lab](https://firebase.google.com/docs/test-lab):

```
```sh
./gradlew app:assembleAndroidTest
./gradlew app:assembleDebug -Ptarget=<path_to_test>.dart
gcloud auth activate-service-account --key-file=<PATH_TO_KEY_FILE>
Expand Down
4 changes: 2 additions & 2 deletions packages/file_selector/file_selector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ To use this plugin, add `file_selector` as a [dependency in your pubspec.yaml fi
### macOS

You will need to [add an entitlement][entitlement] for either read-only access:
```
```xml
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
```
or read/write access:
```
```xml
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
```
Expand Down
4 changes: 2 additions & 2 deletions packages/file_selector/file_selector_macos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ APIs directly.
### Entitlements

You will need to [add an entitlement][4] for either read-only access:
```
```xml
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
```
or read/write access:
```
```xml
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
```
Expand Down
2 changes: 1 addition & 1 deletion packages/google_sign_in/google_sign_in_web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Normally `flutter run` starts in a random port. In the case where you need to de

You can tell `flutter run` to listen for requests in a specific host and port with the following:

```
```sh
flutter run -d chrome --web-hostname localhost --web-port 7357
```

Expand Down
2 changes: 1 addition & 1 deletion packages/url_launcher/url_launcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ See the example app for more complex examples.
Add any URL schemes passed to `canLaunchUrl` as `LSApplicationQueriesSchemes` entries in your Info.plist file.

Example:
```
```xml
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
Expand Down
27 changes: 27 additions & 0 deletions script/configs/temp_exclude_excerpt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Packages that have not yet adopted code-excerpt.
#
# This only exists to allow incrementally adopting the new requirement.
# Packages shoud never be added to this list.

# TODO(ecosystem): Remove everything from this list. See
# https://github.com/flutter/flutter/issues/102679
- camera_web
- espresso
- file_selector/file_selector
- google_maps_flutter/google_maps_flutter
- google_sign_in/google_sign_in
- google_sign_in_web
- image_picker/image_picker
- image_picker_for_web
- in_app_purchase/in_app_purchase
- ios_platform_images
- local_auth/local_auth
- path_provider/path_provider
- plugin_platform_interface
- quick_actions/quick_actions
- shared_preferences/shared_preferences
- url_launcher/url_launcher
- video_player/video_player
- webview_flutter/webview_flutter
- webview_flutter_android
- webview_flutter_web
7 changes: 7 additions & 0 deletions script/tool/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.8.4

- `readme-check` now validates that there's a info tag on code blocks to
identify (and for supported languages, syntax highlight) the language.
- `readme-check` now has a `--require-excerpts` flag to require that any Dart
code blocks be managed by `code_excerpter`.

## 0.8.3

- Adds a new `update-excerpts` command to maintain README files using the
Expand Down
93 changes: 84 additions & 9 deletions script/tool/lib/src/readme_check_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
processRunner: processRunner,
platform: platform,
gitDir: gitDir,
);
) {
argParser.addFlag(_requireExcerptsArg,
help: 'Require that Dart code blocks be managed by code-excerpt.');
}

static const String _requireExcerptsArg = 'require-excerpts';

// Standardized capitalizations for platforms that a plugin can support.
static const Map<String, String> _standardPlatformNames = <String, String>{
Expand Down Expand Up @@ -61,8 +66,15 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
final Pubspec pubspec = package.parsePubspec();
final bool isPlugin = pubspec.flutter?['plugin'] != null;

final List<String> readmeLines = package.readmeFile.readAsLinesSync();

final String? blockValidationError = _validateCodeBlocks(readmeLines);
if (blockValidationError != null) {
errors.add(blockValidationError);
}

if (isPlugin && (!package.isFederated || package.isAppFacing)) {
final String? error = _validateSupportedPlatforms(package, pubspec);
final String? error = _validateSupportedPlatforms(readmeLines, pubspec);
if (error != null) {
errors.add(error);
}
Expand All @@ -73,23 +85,86 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
: PackageResult.fail(errors);
}

/// Validates that code blocks (``` ... ```) follow repository standards.
String? _validateCodeBlocks(List<String> readmeLines) {
final RegExp codeBlockDelimiterPattern = RegExp(r'^\s*```\s*([^ ]*)\s*');
final List<int> missingLanguageLines = <int>[];
final List<int> missingExcerptLines = <int>[];
bool inBlock = false;
for (int i = 0; i < readmeLines.length; ++i) {
final RegExpMatch? match =
codeBlockDelimiterPattern.firstMatch(readmeLines[i]);
if (match == null) {
continue;
}
if (inBlock) {
inBlock = false;
continue;
}
inBlock = true;

final int humanReadableLineNumber = i + 1;

// Ensure that there's a language tag.
final String infoString = match[1] ?? '';
if (infoString.isEmpty) {
missingLanguageLines.add(humanReadableLineNumber);
continue;
}

// Check for code-excerpt usage if requested.
if (getBoolArg(_requireExcerptsArg) && infoString == 'dart') {
const String excerptTagStart = '<?code-excerpt ';
if (i == 0 || !readmeLines[i - 1].trim().startsWith(excerptTagStart)) {
missingExcerptLines.add(humanReadableLineNumber);
}
}
}

String? errorSummary;

if (missingLanguageLines.isNotEmpty) {
for (final int lineNumber in missingLanguageLines) {
printError('${indentation}Code block at line $lineNumber is missing '
'a language identifier.');
}
printError(
'\n${indentation}For each block listed above, add a language tag to '
'the opening block. For instance, for Dart code, use:\n'
'${indentation * 2}```dart\n');
errorSummary = 'Missing language identifier for code block';
}

if (missingExcerptLines.isNotEmpty) {
for (final int lineNumber in missingExcerptLines) {
printError('${indentation}Dart code block at line $lineNumber is not '
'managed by code-excerpt.');
}
printError(
'\n${indentation}For each block listed above, add <?code-excerpt ...> '
'tag on the previous line, and ensure that a build.excerpt.yaml is '
'configured for the source example.\n');
errorSummary ??= 'Missing code-excerpt management for code block';
}

return errorSummary;
}

/// Validates that the plugin has a supported platforms table following the
/// expected format, returning an error string if any issues are found.
String? _validateSupportedPlatforms(
RepositoryPackage package, Pubspec pubspec) {
final List<String> contents = package.readmeFile.readAsLinesSync();

List<String> readmeLines, Pubspec pubspec) {
// Example table following expected format:
// | | Android | iOS | Web |
// |----------------|---------|----------|------------------------|
// | **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
final int detailsLineNumber =
contents.indexWhere((String line) => line.startsWith('| **Support**'));
final int detailsLineNumber = readmeLines
.indexWhere((String line) => line.startsWith('| **Support**'));
if (detailsLineNumber == -1) {
return 'No OS support table found';
}
final int osLineNumber = detailsLineNumber - 2;
if (osLineNumber < 0 || !contents[osLineNumber].startsWith('|')) {
if (osLineNumber < 0 || !readmeLines[osLineNumber].startsWith('|')) {
return 'OS support table does not have the expected header format';
}

Expand All @@ -111,7 +186,7 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
final YamlMap platformSupportMaps = platformsEntry as YamlMap;
final Set<String> actuallySupportedPlatform =
platformSupportMaps.keys.toSet().cast<String>();
final Iterable<String> documentedPlatforms = contents[osLineNumber]
final Iterable<String> documentedPlatforms = readmeLines[osLineNumber]
.split('|')
.map((String entry) => entry.trim())
.where((String entry) => entry.isNotEmpty);
Expand Down
2 changes: 1 addition & 1 deletion script/tool/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: flutter_plugin_tools
description: Productivity utils for flutter/plugins and flutter/packages
repository: https://github.com/flutter/plugins/tree/main/script/tool
version: 0.8.3
version: 0.8.4

dependencies:
args: ^2.1.0
Expand Down
Loading