3
3
// BSD-style license that can be found in the LICENSE file.
4
4
5
5
import 'dart:async' ;
6
+ import 'dart:convert' as convert;
6
7
import 'dart:io' ;
7
8
8
9
import 'package:logging/logging.dart' ;
@@ -27,6 +28,7 @@ import '../shared/versions.dart' as versions;
27
28
import 'backend.dart' ;
28
29
import 'customization.dart' ;
29
30
import 'models.dart' ;
31
+ import 'pub_dartdoc_data.dart' ;
30
32
31
33
final Logger _logger = new Logger ('pub.dartdoc.runner' );
32
34
final Uuid _uuid = new Uuid ();
@@ -35,9 +37,14 @@ const statusFilePath = 'status.json';
35
37
const _archiveFilePath = 'package.tar.gz' ;
36
38
const _buildLogFilePath = 'log.txt' ;
37
39
const _packageTimeout = const Duration (minutes: 10 );
40
+ const _pubDataFileName = 'pub-data.json' ;
38
41
const _sdkTimeout = const Duration (minutes: 20 );
39
42
final Duration _twoYears = const Duration (days: 2 * 365 );
40
43
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
+
41
48
final _pkgPubDartdocDir =
42
49
Platform .script.resolve ('../../pkg/pub_dartdoc' ).toFilePath ();
43
50
@@ -74,7 +81,7 @@ class DartdocJobProcessor extends JobProcessor {
74
81
timeout: _sdkTimeout,
75
82
);
76
83
77
- final pubDataFile = new File (p.join (outputDir, 'pub-data.json' ));
84
+ final pubDataFile = new File (p.join (outputDir, _pubDataFileName ));
78
85
final hasPubData = await pubDataFile.exists ();
79
86
final isOk = pr.exitCode == 0 && hasPubData;
80
87
if (! isOk) {
@@ -130,7 +137,8 @@ class DartdocJobProcessor extends JobProcessor {
130
137
bool hasContent = false ;
131
138
132
139
String reportStatus = ReportStatus .failed;
133
- final suggestions = < Suggestion > [];
140
+ final healthSuggestions = < Suggestion > [];
141
+ final maintenanceSuggestions = < Suggestion > [];
134
142
try {
135
143
final pkgDir = await downloadPackage (job.packageName, job.packageVersion);
136
144
if (pkgDir == null ) {
@@ -203,17 +211,52 @@ class DartdocJobProcessor extends JobProcessor {
203
211
await toolEnvRef.release ();
204
212
}
205
213
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 ());
208
249
}
209
- // TODO: calculate coverage score
210
250
await scoreCardBackend.updateReport (
211
251
job.packageName,
212
252
job.packageVersion,
213
253
new DartdocReport (
214
254
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,
217
260
));
218
261
await scoreCardBackend.updateScoreCard (job.packageName, job.packageVersion);
219
262
@@ -391,6 +434,21 @@ class DartdocJobProcessor extends JobProcessor {
391
434
await tmpTar.rename (p.join (outputDir, _archiveFilePath));
392
435
_appendLog (logFileOutput, pr);
393
436
}
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
+ }
394
452
}
395
453
396
454
bool _isKnownFailurePattern (String output) {
0 commit comments