Skip to content

Commit

Permalink
Warn if the user did not specify a language on the fenced code block (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
parlough committed Mar 9, 2021
1 parent 48b0149 commit 5eb8c9a
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 2 deletions.
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

0 comments on commit 5eb8c9a

Please sign in to comment.