Skip to content

Commit 0df46be

Browse files
authored
Calculate dartdoc coverage score and fix scorecard-model fields. (#1711)
* Separate health and maintenance suggestions in dartdoc report. * _pubDataFileName const * Calculate dartdoc coverage score. * Emit suggestion and penalty only when coverage is below 0.1
1 parent ff30ef8 commit 0df46be

File tree

3 files changed

+113
-19
lines changed

3 files changed

+113
-19
lines changed

app/lib/dartdoc/dartdoc_runner.dart

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:convert' as convert;
67
import 'dart:io';
78

89
import 'package:logging/logging.dart';
@@ -27,6 +28,7 @@ import '../shared/versions.dart' as versions;
2728
import 'backend.dart';
2829
import 'customization.dart';
2930
import 'models.dart';
31+
import 'pub_dartdoc_data.dart';
3032

3133
final Logger _logger = new Logger('pub.dartdoc.runner');
3234
final Uuid _uuid = new Uuid();
@@ -35,9 +37,14 @@ const statusFilePath = 'status.json';
3537
const _archiveFilePath = 'package.tar.gz';
3638
const _buildLogFilePath = 'log.txt';
3739
const _packageTimeout = const Duration(minutes: 10);
40+
const _pubDataFileName = 'pub-data.json';
3841
const _sdkTimeout = const Duration(minutes: 20);
3942
final Duration _twoYears = const Duration(days: 2 * 365);
4043

44+
// We'll emit a suggestion and apply score penalty only if the coverage is below
45+
// this value.
46+
const _coverageEmitThreshold = 0.1;
47+
4148
final _pkgPubDartdocDir =
4249
Platform.script.resolve('../../pkg/pub_dartdoc').toFilePath();
4350

@@ -74,7 +81,7 @@ class DartdocJobProcessor extends JobProcessor {
7481
timeout: _sdkTimeout,
7582
);
7683

77-
final pubDataFile = new File(p.join(outputDir, 'pub-data.json'));
84+
final pubDataFile = new File(p.join(outputDir, _pubDataFileName));
7885
final hasPubData = await pubDataFile.exists();
7986
final isOk = pr.exitCode == 0 && hasPubData;
8087
if (!isOk) {
@@ -130,7 +137,8 @@ class DartdocJobProcessor extends JobProcessor {
130137
bool hasContent = false;
131138

132139
String reportStatus = ReportStatus.failed;
133-
final suggestions = <Suggestion>[];
140+
final healthSuggestions = <Suggestion>[];
141+
final maintenanceSuggestions = <Suggestion>[];
134142
try {
135143
final pkgDir = await downloadPackage(job.packageName, job.packageVersion);
136144
if (pkgDir == null) {
@@ -203,17 +211,52 @@ class DartdocJobProcessor extends JobProcessor {
203211
await toolEnvRef.release();
204212
}
205213

206-
if (!hasContent) {
207-
suggestions.add(getDartdocRunFailedSuggestion());
214+
double coverageScore = 0.0;
215+
final dartdocData = await _loadPubDartdocData(outputDir);
216+
if (hasContent && dartdocData != null) {
217+
final total = dartdocData.apiElements.length;
218+
final documented = dartdocData.apiElements
219+
.where((elem) =>
220+
elem.documentation != null &&
221+
elem.documentation.isNotEmpty &&
222+
elem.documentation.trim().length >= 5)
223+
.length;
224+
if (total == documented) {
225+
// this also handles total == 0
226+
coverageScore = 1.0;
227+
} else {
228+
coverageScore = documented / total;
229+
}
230+
231+
if (coverageScore < _coverageEmitThreshold) {
232+
final level = coverageScore < 0.2
233+
? SuggestionLevel.warning
234+
: SuggestionLevel.hint;
235+
final undocumented = total - documented;
236+
healthSuggestions.add(
237+
new Suggestion(
238+
'dartdoc.coverage', // TODO: extract as const in pana
239+
level,
240+
'Document public APIs',
241+
'$undocumented out of $total API elements (library, class, field '
242+
'or method) have no adequate dartdoc content. Good documentation '
243+
'improves code readability and discoverability through search.',
244+
score: (1.0 - coverageScore) * 10.0),
245+
);
246+
}
247+
} else {
248+
maintenanceSuggestions.add(getDartdocRunFailedSuggestion());
208249
}
209-
// TODO: calculate coverage score
210250
await scoreCardBackend.updateReport(
211251
job.packageName,
212252
job.packageVersion,
213253
new DartdocReport(
214254
reportStatus: reportStatus,
215-
coverageScore: hasContent ? 1.0 : 0.0,
216-
suggestions: suggestions.isEmpty ? null : suggestions,
255+
coverageScore: coverageScore,
256+
healthSuggestions:
257+
healthSuggestions.isEmpty ? null : healthSuggestions,
258+
maintenanceSuggestions:
259+
maintenanceSuggestions.isEmpty ? null : maintenanceSuggestions,
217260
));
218261
await scoreCardBackend.updateScoreCard(job.packageName, job.packageVersion);
219262

@@ -391,6 +434,21 @@ class DartdocJobProcessor extends JobProcessor {
391434
await tmpTar.rename(p.join(outputDir, _archiveFilePath));
392435
_appendLog(logFileOutput, pr);
393436
}
437+
438+
Future<PubDartdocData> _loadPubDartdocData(String outputDir) async {
439+
final file = new File(p.join(outputDir, _pubDataFileName));
440+
if (!file.existsSync()) {
441+
return null;
442+
}
443+
try {
444+
final content = await file.readAsString();
445+
return new PubDartdocData.fromJson(
446+
convert.json.decode(content) as Map<String, dynamic>);
447+
} catch (e, st) {
448+
_logger.warning('Unable to parse $_pubDataFileName.', e, st);
449+
return null;
450+
}
451+
}
394452
}
395453

396454
bool _isKnownFailurePattern(String output) {

app/lib/scorecard/models.dart

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:convert';
66
import 'dart:io';
7+
import 'dart:math' as math;
78

89
import 'package:gcloud/db.dart' as db;
910
import 'package:json_annotation/json_annotation.dart';
@@ -150,9 +151,14 @@ class ScoreCard extends db.ExpandoModel {
150151
PanaReport panaReport,
151152
DartdocReport dartdocReport,
152153
}) {
153-
healthScore = (panaReport?.healthScore ?? 0.0) *
154-
(0.9 + ((dartdocReport?.coverageScore ?? 1.0) * 0.1));
155-
maintenanceScore = panaReport?.maintenanceScore ?? 0.0;
154+
healthScore = _applySuggestions(
155+
panaReport?.healthScore ?? 0.0,
156+
dartdocReport?.healthSuggestions,
157+
);
158+
maintenanceScore = _applySuggestions(
159+
panaReport?.maintenanceScore ?? 0.0,
160+
dartdocReport?.maintenanceSuggestions,
161+
);
156162
platformTags = panaReport?.platformTags ?? <String>[];
157163
reportTypes = [
158164
panaReport == null ? null : ReportType.pana,
@@ -162,6 +168,15 @@ class ScoreCard extends db.ExpandoModel {
162168
..sort();
163169
panaReport?.flags?.forEach(addFlag);
164170
}
171+
172+
double _applySuggestions(double score, List<Suggestion> suggestions) {
173+
suggestions?.forEach((s) {
174+
if (s.score != null) {
175+
score -= s.score / 100.0;
176+
}
177+
});
178+
return math.max(score, 0.0);
179+
}
165180
}
166181

167182
/// Detail of a specific report for a given PackageVersion.
@@ -385,12 +400,19 @@ class DartdocReport implements ReportData {
385400

386401
final double coverageScore;
387402

388-
final List<Suggestion> suggestions;
403+
/// Suggestions related to the package health score.
404+
@JsonKey(includeIfNull: false)
405+
final List<Suggestion> healthSuggestions;
406+
407+
/// Suggestions related to the package maintenance score.
408+
@JsonKey(includeIfNull: false)
409+
final List<Suggestion> maintenanceSuggestions;
389410

390411
DartdocReport({
391412
@required this.reportStatus,
392413
@required this.coverageScore,
393-
@required this.suggestions,
414+
@required this.healthSuggestions,
415+
@required this.maintenanceSuggestions,
394416
});
395417

396418
factory DartdocReport.fromJson(Map<String, dynamic> json) =>

app/lib/scorecard/models.g.dart

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)