Skip to content
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
28 changes: 28 additions & 0 deletions lib/src/model/documentation_comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,15 @@ mixin DocumentationComment
String processCommentWithoutTools(String documentationComment) {
var docs = stripComments(documentationComment);
if (!docs.contains('{@')) {
_analyzeCodeBlocks(docs);
return docs;
}
docs = _injectExamples(docs);
docs = _injectYouTube(docs);
docs = _injectAnimations(docs);

_analyzeCodeBlocks(docs);

// TODO(srawlins): Processing templates here causes #2281. But leaving them
// unprocessed causes #2272.
docs = _stripHtmlAndAddToIndex(docs);
Expand All @@ -79,6 +83,7 @@ mixin DocumentationComment
// Must evaluate tools first, in case they insert any other directives.
docs = await _evaluateTools(docs);
docs = processCommentDirectives(docs);
_analyzeCodeBlocks(docs);
return docs;
}

Expand Down Expand Up @@ -673,4 +678,27 @@ mixin DocumentationComment
return '$option${match[0]}';
});
}

static final _codeBlockPattern =
RegExp(r'^[ ]{0,3}(`{3,}|~{3,})(.*)$', multiLine: true);

/// Analyze fenced code blocks present in the documentation comment,
/// warning if there is no language specified.
void _analyzeCodeBlocks(String docs) {
final results = _codeBlockPattern.allMatches(docs).toList(growable: false);
final firstOfPair = <Match>[];
for (var i = 0; i < results.length; i++) {
if (i.isEven && i != results.length - 1) {
firstOfPair.add(results[i]);
}
}
firstOfPair.forEach((element) {
final result = element.group(2).trim();
if (result.isEmpty) {
warn(PackageWarning.missingCodeBlockLanguage,
message:
'A fenced code block in Markdown should have a language specified');
}
});
}
}
3 changes: 3 additions & 0 deletions lib/src/model/package_graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@ class PackageGraph {
case PackageWarning.missingExampleFile:
warningMessage = 'example file not found: $message';
break;
case PackageWarning.missingCodeBlockLanguage:
warningMessage = 'missing code block language: $message';
break;
}

var messageParts = <String>[warningMessage];
Expand Down
11 changes: 11 additions & 0 deletions lib/src/warnings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,16 @@ const Map<PackageWarning, PackageWarningDefinition> packageWarningDefinitions =
// Defaults to ignore as this doesn't impact the docs severely but is
// useful for debugging package structure.
defaultWarningMode: PackageWarningMode.ignore),
PackageWarning.missingCodeBlockLanguage: PackageWarningDefinition(
PackageWarning.missingCodeBlockLanguage,
'missing-code-block-language',
'A fenced code block is missing a specified language.',
longHelp: [
'To enable proper syntax highlighting of Markdown code blocks,',
'Dartdoc requires code blocks to specify the language used after',
'the initial declaration. As an example, to specify Dart you would',
'specify ```dart or ~~~dart.'
]),
};

/// Something that package warnings can be called on. Optionally associated
Expand Down Expand Up @@ -306,6 +316,7 @@ enum PackageWarning {
unresolvedExport,
missingConstantConstructor,
missingExampleFile,
missingCodeBlockLanguage,
}

/// Used to declare defaults for a particular package warning.
Expand Down
65 changes: 63 additions & 2 deletions test/documentation_comment_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ End text.'''));

test('processes @example with file', () async {
projectRoot.getChildAssumingFile('abc.md').writeAsStringSync('''
```
```plaintext
Code snippet
```
''');
Expand All @@ -498,7 +498,7 @@ Code snippet
expect(doc, equals('''
Text.

```
```plaintext
Code snippet
```

Expand Down Expand Up @@ -844,6 +844,67 @@ Text.
'Supported YouTube URLs have the following format: '
'https://www.youtube.com/watch?v=oHg5SJYRHA0.'));
});

test('warns when fenced code block does not specify language', () async {
await libraryModel.processComment('''
/// ```
/// void main() {}
/// ```
''');

expect(
packageGraph.packageWarningCounter.hasWarning(
libraryModel,
PackageWarning.missingCodeBlockLanguage,
'A fenced code block in Markdown should have a language specified'),
isTrue);
});

test('warns when squiggly fenced code block does not specify language',
() async {
await libraryModel.processComment('''
/// ~~~
/// void main() {}
/// ~~~
''');

expect(
packageGraph.packageWarningCounter.hasWarning(
libraryModel,
PackageWarning.missingCodeBlockLanguage,
'A fenced code block in Markdown should have a language specified'),
isTrue);
});

test('does not warn when fenced code block does specify language',
() async {
await libraryModel.processComment('''
/// ```dart
/// void main() {}
/// ```
''');

expect(
packageGraph.packageWarningCounter.hasWarning(
libraryModel,
PackageWarning.missingCodeBlockLanguage,
'A fenced code block in Markdown should have a language specified'),
isFalse);
});

test('does not warn when fenced block is not closed', () async {
await libraryModel.processComment('''
/// ```
/// A not closed fenced code block
''');

expect(
packageGraph.packageWarningCounter.hasWarning(
libraryModel,
PackageWarning.missingCodeBlockLanguage,
'A fenced code block in Markdown should have a language specified'),
isFalse);
});
}, onPlatform: {
'windows': Skip('These tests do not work on Windows (#2446)')
});
Expand Down