Skip to content

Commit 5eb8c9a

Browse files
authored
Warn if the user did not specify a language on the fenced code block (#2559)
* Warn if the user did not specify a language on the fenced code block * Account for new warning in switch * Give code block pseudo language so it passes tests * Add new warning to package warning definitions
1 parent 48b0149 commit 5eb8c9a

File tree

4 files changed

+105
-2
lines changed

4 files changed

+105
-2
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/model/package_graph.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,9 @@ class PackageGraph {
452452
case PackageWarning.missingExampleFile:
453453
warningMessage = 'example file not found: $message';
454454
break;
455+
case PackageWarning.missingCodeBlockLanguage:
456+
warningMessage = 'missing code block language: $message';
457+
break;
455458
}
456459

457460
var messageParts = <String>[warningMessage];

lib/src/warnings.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,16 @@ const Map<PackageWarning, PackageWarningDefinition> packageWarningDefinitions =
256256
// Defaults to ignore as this doesn't impact the docs severely but is
257257
// useful for debugging package structure.
258258
defaultWarningMode: PackageWarningMode.ignore),
259+
PackageWarning.missingCodeBlockLanguage: PackageWarningDefinition(
260+
PackageWarning.missingCodeBlockLanguage,
261+
'missing-code-block-language',
262+
'A fenced code block is missing a specified language.',
263+
longHelp: [
264+
'To enable proper syntax highlighting of Markdown code blocks,',
265+
'Dartdoc requires code blocks to specify the language used after',
266+
'the initial declaration. As an example, to specify Dart you would',
267+
'specify ```dart or ~~~dart.'
268+
]),
259269
};
260270

261271
/// Something that package warnings can be called on. Optionally associated
@@ -306,6 +316,7 @@ enum PackageWarning {
306316
unresolvedExport,
307317
missingConstantConstructor,
308318
missingExampleFile,
319+
missingCodeBlockLanguage,
309320
}
310321

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

test/documentation_comment_test.dart

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ End text.'''));
482482

483483
test('processes @example with file', () async {
484484
projectRoot.getChildAssumingFile('abc.md').writeAsStringSync('''
485-
```
485+
```plaintext
486486
Code snippet
487487
```
488488
''');
@@ -498,7 +498,7 @@ Code snippet
498498
expect(doc, equals('''
499499
Text.
500500
501-
```
501+
```plaintext
502502
Code snippet
503503
```
504504
@@ -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)