Skip to content

Commit cc04427

Browse files
committed
Warn if the user did not specify a language on the fenced code block
1 parent cab6931 commit cc04427

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

lib/src/model/documentation_comment.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,15 @@ mixin DocumentationComment
6161
String processCommentWithoutTools(String documentationComment) {
6262
var docs = stripComments(documentationComment);
6363
if (!docs.contains('{@')) {
64+
_analyzeCodeBlocks(docs);
6465
return docs;
6566
}
6667
docs = _injectExamples(docs);
6768
docs = _injectYouTube(docs);
6869
docs = _injectAnimations(docs);
70+
71+
_analyzeCodeBlocks(docs);
72+
6973
// TODO(srawlins): Processing templates here causes #2281. But leaving them
7074
// unprocessed causes #2272.
7175
docs = _stripHtmlAndAddToIndex(docs);
@@ -79,6 +83,7 @@ mixin DocumentationComment
7983
// Must evaluate tools first, in case they insert any other directives.
8084
docs = await _evaluateTools(docs);
8185
docs = processCommentDirectives(docs);
86+
_analyzeCodeBlocks(docs);
8287
return docs;
8388
}
8489

@@ -673,4 +678,27 @@ mixin DocumentationComment
673678
return '$option${match[0]}';
674679
});
675680
}
681+
682+
static final _codeBlockPattern =
683+
RegExp(r'^[ ]{0,3}(`{3,}|~{3,})(.*)$', multiLine: true);
684+
685+
/// Analyze fenced code blocks present in the documentation comment,
686+
/// warning if there is no language specified.
687+
void _analyzeCodeBlocks(String docs) {
688+
final results = _codeBlockPattern.allMatches(docs).toList(growable: false);
689+
final firstOfPair = <Match>[];
690+
for (var i = 0; i < results.length; i++) {
691+
if (i.isEven && i != results.length - 1) {
692+
firstOfPair.add(results[i]);
693+
}
694+
}
695+
firstOfPair.forEach((element) {
696+
final result = element.group(2).trim();
697+
if (result.isEmpty) {
698+
warn(PackageWarning.missingCodeBlockLanguage,
699+
message:
700+
'A fenced code block in Markdown should have a language specified.');
701+
}
702+
});
703+
}
676704
}

lib/src/warnings.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ enum PackageWarning {
306306
unresolvedExport,
307307
missingConstantConstructor,
308308
missingExampleFile,
309+
missingCodeBlockLanguage,
309310
}
310311

311312
/// Used to declare defaults for a particular package warning.

test/documentation_comment_test.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,67 @@ Text.
844844
'Supported YouTube URLs have the following format: '
845845
'https://www.youtube.com/watch?v=oHg5SJYRHA0.'));
846846
});
847+
848+
test('warns when fenced code block does not specify language', () async {
849+
await libraryModel.processComment('''
850+
/// ```
851+
/// void main() {}
852+
/// ```
853+
''');
854+
855+
expect(
856+
packageGraph.packageWarningCounter.hasWarning(
857+
libraryModel,
858+
PackageWarning.missingCodeBlockLanguage,
859+
'A fenced code block in Markdown should have a language specified.'),
860+
isTrue);
861+
});
862+
863+
test('warns when squiggly fenced code block does not specify language',
864+
() async {
865+
await libraryModel.processComment('''
866+
/// ~~~
867+
/// void main() {}
868+
/// ~~~
869+
''');
870+
871+
expect(
872+
packageGraph.packageWarningCounter.hasWarning(
873+
libraryModel,
874+
PackageWarning.missingCodeBlockLanguage,
875+
'A fenced code block in Markdown should have a language specified.'),
876+
isTrue);
877+
});
878+
879+
test('does not warn when fenced code block does specify language',
880+
() async {
881+
await libraryModel.processComment('''
882+
/// ```dart
883+
/// void main() {}
884+
/// ```
885+
''');
886+
887+
expect(
888+
packageGraph.packageWarningCounter.hasWarning(
889+
libraryModel,
890+
PackageWarning.missingCodeBlockLanguage,
891+
'A fenced code block in Markdown should have a language specified.'),
892+
isFalse);
893+
});
894+
895+
test('does not warn when fenced block is not closed', () async {
896+
await libraryModel.processComment('''
897+
/// ```
898+
/// A not closed fenced code block
899+
''');
900+
901+
expect(
902+
packageGraph.packageWarningCounter.hasWarning(
903+
libraryModel,
904+
PackageWarning.missingCodeBlockLanguage,
905+
'A fenced code block in Markdown should have a language specified.'),
906+
isFalse);
907+
});
847908
}, onPlatform: {
848909
'windows': Skip('These tests do not work on Windows (#2446)')
849910
});

0 commit comments

Comments
 (0)