@@ -26,7 +26,12 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
2626 processRunner: processRunner,
2727 platform: platform,
2828 gitDir: gitDir,
29- );
29+ ) {
30+ argParser.addFlag (_requireExcerptsArg,
31+ help: 'Require that Dart code blocks be managed by code-excerpt.' );
32+ }
33+
34+ static const String _requireExcerptsArg = 'require-excerpts' ;
3035
3136 // Standardized capitalizations for platforms that a plugin can support.
3237 static const Map <String , String > _standardPlatformNames = < String , String > {
@@ -61,8 +66,15 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
6166 final Pubspec pubspec = package.parsePubspec ();
6267 final bool isPlugin = pubspec.flutter? ['plugin' ] != null ;
6368
69+ final List <String > readmeLines = package.readmeFile.readAsLinesSync ();
70+
71+ final String ? blockValidationError = _validateCodeBlocks (readmeLines);
72+ if (blockValidationError != null ) {
73+ errors.add (blockValidationError);
74+ }
75+
6476 if (isPlugin && (! package.isFederated || package.isAppFacing)) {
65- final String ? error = _validateSupportedPlatforms (package , pubspec);
77+ final String ? error = _validateSupportedPlatforms (readmeLines , pubspec);
6678 if (error != null ) {
6779 errors.add (error);
6880 }
@@ -73,23 +85,86 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
7385 : PackageResult .fail (errors);
7486 }
7587
88+ /// Validates that code blocks (``` ... ` ``) follow repository standards.
89+ String ? _validateCodeBlocks (List <String > readmeLines) {
90+ final RegExp codeBlockDelimiterPattern = RegExp (r'^\s*```\s*([^ ]*)\s*' );
91+ final List <int > missingLanguageLines = < int > [];
92+ final List <int > missingExcerptLines = < int > [];
93+ bool inBlock = false ;
94+ for (int i = 0 ; i < readmeLines.length; ++ i) {
95+ final RegExpMatch ? match =
96+ codeBlockDelimiterPattern.firstMatch (readmeLines[i]);
97+ if (match == null ) {
98+ continue ;
99+ }
100+ if (inBlock) {
101+ inBlock = false ;
102+ continue ;
103+ }
104+ inBlock = true ;
105+
106+ final int humanReadableLineNumber = i + 1 ;
107+
108+ // Ensure that there's a language tag.
109+ final String infoString = match[1 ] ?? '' ;
110+ if (infoString.isEmpty) {
111+ missingLanguageLines.add (humanReadableLineNumber);
112+ continue ;
113+ }
114+
115+ // Check for code-excerpt usage if requested.
116+ if (getBoolArg (_requireExcerptsArg) && infoString == 'dart' ) {
117+ const String excerptTagStart = '<?code-excerpt ' ;
118+ if (i == 0 || ! readmeLines[i - 1 ].trim ().startsWith (excerptTagStart)) {
119+ missingExcerptLines.add (humanReadableLineNumber);
120+ }
121+ }
122+ }
123+
124+ String ? errorSummary;
125+
126+ if (missingLanguageLines.isNotEmpty) {
127+ for (final int lineNumber in missingLanguageLines) {
128+ printError ('${indentation }Code block at line $lineNumber is missing '
129+ 'a language identifier.' );
130+ }
131+ printError (
132+ '\n ${indentation }For each block listed above, add a language tag to '
133+ 'the opening block. For instance, for Dart code, use:\n '
134+ '${indentation * 2 }```dart\n ' );
135+ errorSummary = 'Missing language identifier for code block' ;
136+ }
137+
138+ if (missingExcerptLines.isNotEmpty) {
139+ for (final int lineNumber in missingExcerptLines) {
140+ printError ('${indentation }Dart code block at line $lineNumber is not '
141+ 'managed by code-excerpt.' );
142+ }
143+ printError (
144+ '\n ${indentation }For each block listed above, add <?code-excerpt ...> '
145+ 'tag on the previous line, and ensure that a build.excerpt.yaml is '
146+ 'configured for the source example.\n ' );
147+ errorSummary ?? = 'Missing code-excerpt management for code block' ;
148+ }
149+
150+ return errorSummary;
151+ }
152+
76153 /// Validates that the plugin has a supported platforms table following the
77154 /// expected format, returning an error string if any issues are found.
78155 String ? _validateSupportedPlatforms (
79- RepositoryPackage package, Pubspec pubspec) {
80- final List <String > contents = package.readmeFile.readAsLinesSync ();
81-
156+ List <String > readmeLines, Pubspec pubspec) {
82157 // Example table following expected format:
83158 // | | Android | iOS | Web |
84159 // |----------------|---------|----------|------------------------|
85160 // | **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
86- final int detailsLineNumber =
87- contents .indexWhere ((String line) => line.startsWith ('| **Support**' ));
161+ final int detailsLineNumber = readmeLines
162+ .indexWhere ((String line) => line.startsWith ('| **Support**' ));
88163 if (detailsLineNumber == - 1 ) {
89164 return 'No OS support table found' ;
90165 }
91166 final int osLineNumber = detailsLineNumber - 2 ;
92- if (osLineNumber < 0 || ! contents [osLineNumber].startsWith ('|' )) {
167+ if (osLineNumber < 0 || ! readmeLines [osLineNumber].startsWith ('|' )) {
93168 return 'OS support table does not have the expected header format' ;
94169 }
95170
@@ -111,7 +186,7 @@ class ReadmeCheckCommand extends PackageLoopingCommand {
111186 final YamlMap platformSupportMaps = platformsEntry as YamlMap ;
112187 final Set <String > actuallySupportedPlatform =
113188 platformSupportMaps.keys.toSet ().cast <String >();
114- final Iterable <String > documentedPlatforms = contents [osLineNumber]
189+ final Iterable <String > documentedPlatforms = readmeLines [osLineNumber]
115190 .split ('|' )
116191 .map ((String entry) => entry.trim ())
117192 .where ((String entry) => entry.isNotEmpty);
0 commit comments