• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / linter / 6444183028

01 Sep 2023 04:26PM CUT coverage: 96.562% (+0.01%) from 96.551%
6444183028

push

github

web-flow
update labels (#4740)

9127 of 9452 relevant lines covered (96.56%)

1.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.07
/lib/src/rules/public_member_api_docs.dart
1
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4

5
import 'package:analyzer/dart/ast/ast.dart';
6
import 'package:analyzer/dart/ast/token.dart';
7
import 'package:analyzer/dart/ast/visitor.dart';
8

9
import '../analyzer.dart';
10
import '../ast.dart';
11
import '../extensions.dart';
12

13
const _desc = r'Document all public members.';
14

15
const _details = r'''
16
**DO** document all public members.
17

18
All non-overriding public members should be documented with `///` doc-style
19
comments.
20

21
**BAD:**
22
```dart
23
class Bad {
24
  void meh() { }
25
}
26
```
27

28
**GOOD:**
29
```dart
30
/// A good thing.
31
abstract class Good {
32
  /// Start doing your thing.
33
  void start() => _start();
34

35
  _start();
36
}
37
```
38

39
In case a public member overrides a member it is up to the declaring member
40
to provide documentation.  For example, in the following, `Sub` needn't
41
document `init` (though it certainly may, if there's need).
42

43
**GOOD:**
44
```dart
45
/// Base of all things.
46
abstract class Base {
47
  /// Initialize the base.
48
  void init();
49
}
50

51
/// A sub base.
52
class Sub extends Base {
53
  @override
54
  void init() { ... }
55
}
56
```
57

58
Note that consistent with `dart doc`, an exception to the rule is made when
59
documented getters have corresponding undocumented setters.  In this case the
60
setters inherit the docs from the getters.
61

62
''';
63

64
// TODO(devoncarew): Longer term, this lint could benefit from being more aware
65
// of the actual API surface area of a package - including that defined by
66
// exports - and linting against that.
67

68
class PublicMemberApiDocs extends LintRule {
69
  static const LintCode code = LintCode(
70
      'public_member_api_docs', 'Missing documentation for a public member.',
71
      correctionMessage: 'Try adding documentation for the member.');
72

73
  PublicMemberApiDocs()
1✔
74
      : super(
1✔
75
            name: 'public_member_api_docs',
76
            description: _desc,
77
            details: _details,
78
            group: Group.style);
79

80
  @override
1✔
81
  LintCode get lintCode => code;
82

83
  @override
1✔
84
  void registerNodeProcessors(
85
      NodeLintRegistry registry, LinterContext context) {
86
    if (!isInLibDir(context.currentUnit.unit, context.package)) {
4✔
87
      return;
88
    }
89

90
    var visitor = _Visitor(this, context);
1✔
91
    registry.addClassDeclaration(this, visitor);
1✔
92
    registry.addClassTypeAlias(this, visitor);
1✔
93
    registry.addCompilationUnit(this, visitor);
1✔
94
    registry.addConstructorDeclaration(this, visitor);
1✔
95
    registry.addEnumConstantDeclaration(this, visitor);
1✔
96
    registry.addEnumDeclaration(this, visitor);
1✔
97
    registry.addExtensionDeclaration(this, visitor);
1✔
98
    registry.addExtensionTypeDeclaration(this, visitor);
1✔
99
    registry.addFieldDeclaration(this, visitor);
1✔
100
    registry.addFunctionTypeAlias(this, visitor);
1✔
101
    registry.addGenericTypeAlias(this, visitor);
1✔
102
    registry.addMixinDeclaration(this, visitor);
1✔
103
    registry.addTopLevelVariableDeclaration(this, visitor);
1✔
104
  }
105
}
106

107
class _Visitor extends SimpleAstVisitor {
108
  final LintRule rule;
109
  final LinterContext context;
110

111
  _Visitor(this.rule, this.context);
1✔
112

113
  bool check(Declaration node) {
1✔
114
    if (node.documentationComment == null && !isOverridingMember(node)) {
2✔
115
      var errorNode = getNodeToAnnotate(node);
1✔
116
      rule.reportLintForOffset(errorNode.offset, errorNode.length);
4✔
117
      return true;
118
    }
119
    return false;
120
  }
121

122
  void checkMethods(List<ClassMember> members) {
1✔
123
    // Check methods
124

125
    var getters = <String, MethodDeclaration>{};
1✔
126
    var setters = <MethodDeclaration>[];
1✔
127

128
    // Non-getters/setters.
129
    var methods = <MethodDeclaration>[];
1✔
130

131
    // Identify getter/setter pairs.
132
    for (var member in members) {
2✔
133
      if (member is MethodDeclaration && !isPrivate(member.name)) {
3✔
134
        if (member.isGetter) {
1✔
135
          getters[member.name.lexeme] = member;
3✔
136
        } else if (member.isSetter) {
1✔
137
          setters.add(member);
×
138
        } else {
139
          methods.add(member);
1✔
140
        }
141
      }
142
    }
143

144
    // Check all getters, and collect offenders along the way.
145
    var missingDocs = <MethodDeclaration>{};
146
    for (var getter in getters.values) {
2✔
147
      if (check(getter)) {
1✔
148
        missingDocs.add(getter);
1✔
149
      }
150
    }
151

152
    // But only setters whose getter is missing a doc.
153
    for (var setter in setters) {
1✔
154
      var getter = getters[setter.name.lexeme];
×
155
      if (getter != null && missingDocs.contains(getter)) {
×
156
        check(setter);
×
157
      }
158
    }
159

160
    // Check remaining methods.
161
    methods.forEach(check);
2✔
162
  }
163

164
  /// Whether [node] overrides some other member.
165
  bool isOverridingMember(Declaration node) =>
1✔
166
      context.inheritanceManager.overriddenMember(node.declaredElement) != null;
4✔
167

168
  @override
1✔
169
  void visitClassDeclaration(ClassDeclaration node) {
170
    var element = node.declaredElement;
1✔
171
    if (element == null || element.hasInternal) return;
1✔
172
    _visitMembers(node, node.name, node.members);
3✔
173
  }
174

175
  @override
×
176
  void visitClassTypeAlias(ClassTypeAlias node) {
177
    if (!isPrivate(node.name)) {
×
178
      check(node);
×
179
    }
180
  }
181

182
  @override
1✔
183
  void visitCompilationUnit(CompilationUnit node) {
184
    var getters = <String, FunctionDeclaration>{};
1✔
185
    var setters = <FunctionDeclaration>[];
1✔
186

187
    // Check functions.
188

189
    // Non-getters/setters.
190
    var functions = <FunctionDeclaration>[];
1✔
191

192
    // Identify getter/setter pairs.
193
    for (var member in node.declarations) {
2✔
194
      if (member is FunctionDeclaration) {
1✔
195
        var name = member.name;
1✔
196
        if (!isPrivate(name) && name.lexeme != 'main') {
3✔
197
          if (member.isGetter) {
1✔
198
            getters[member.name.lexeme] = member;
3✔
199
          } else if (member.isSetter) {
×
200
            setters.add(member);
×
201
          } else {
202
            functions.add(member);
×
203
          }
204
        }
205
      }
206
    }
207

208
    // Check all getters, and collect offenders along the way.
209
    var missingDocs = <FunctionDeclaration>{};
210
    for (var getter in getters.values) {
2✔
211
      if (check(getter)) {
1✔
212
        missingDocs.add(getter);
1✔
213
      }
214
    }
215

216
    // But only setters whose getter is missing a doc.
217
    for (var setter in setters) {
1✔
218
      var getter = getters[setter.name.lexeme];
×
219
      if (getter != null && missingDocs.contains(getter)) {
×
220
        check(setter);
×
221
      }
222
    }
223

224
    // Check remaining functions.
225
    functions.forEach(check);
2✔
226

227
    super.visitCompilationUnit(node);
1✔
228
  }
229

230
  @override
1✔
231
  void visitConstructorDeclaration(ConstructorDeclaration node) {
232
    if (inPrivateMember(node) || isPrivate(node.name)) return;
3✔
233
    var parent = node.parent;
1✔
234
    if (parent is EnumDeclaration) return;
1✔
235
    if (parent != null && parent.isEffectivelyPrivate) return;
1✔
236

237
    check(node);
×
238
  }
239

240
  @override
1✔
241
  void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
242
    // todo(pq): update this to be called from the parent (like with visitMembers)
243
    if (node.isInternal) return;
1✔
244

245
    if (!inPrivateMember(node) && !isPrivate(node.name)) {
3✔
246
      check(node);
1✔
247
    }
248
  }
249

250
  @override
1✔
251
  void visitEnumDeclaration(EnumDeclaration node) {
252
    if (isPrivate(node.name)) return;
2✔
253
    if (node.isInternal) return;
1✔
254

255
    check(node);
1✔
256
    checkMethods(node.members);
2✔
257
  }
258

259
  @override
1✔
260
  void visitExtensionDeclaration(ExtensionDeclaration node) {
261
    if (node.name == null || isPrivate(node.name)) return;
3✔
262
    if (node.isInternal) return;
1✔
263

264
    check(node);
1✔
265
    checkMethods(node.members);
2✔
266
  }
267

268
  @override
1✔
269
  void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
270
    var element = node.declaredElement;
1✔
271
    if (element == null || element.hasInternal) return;
1✔
272
    _visitMembers(node, node.name, node.members);
3✔
273
  }
274

275
  @override
1✔
276
  void visitFieldDeclaration(FieldDeclaration node) {
277
    // todo(pq): update this to be called from the parent (like with visitMembers)
278
    if (node.isInternal) return;
1✔
279
    if (inPrivateMember(node)) return;
1✔
280
    if (node.isInvalidExtensionTypeField) return;
1✔
281

282
    for (var field in node.fields.variables) {
3✔
283
      if (!isPrivate(field.name)) {
2✔
284
        check(field);
1✔
285
      }
286
    }
287
  }
288

289
  @override
×
290
  void visitFunctionTypeAlias(FunctionTypeAlias node) {
291
    if (!isPrivate(node.name)) {
×
292
      check(node);
×
293
    }
294
  }
295

296
  @override
1✔
297
  void visitGenericTypeAlias(GenericTypeAlias node) {
298
    if (!isPrivate(node.name)) {
2✔
299
      check(node);
1✔
300
    }
301
  }
302

303
  @override
1✔
304
  void visitMixinDeclaration(MixinDeclaration node) {
305
    if (node.isInternal) return;
1✔
306
    _visitMembers(node, node.name, node.members);
3✔
307
  }
308

309
  @override
1✔
310
  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
311
    for (var decl in node.variables.variables) {
3✔
312
      if (!isPrivate(decl.name)) {
2✔
313
        check(decl);
1✔
314
      }
315
    }
316
  }
317

318
  void _visitMembers(Declaration node, Token name, List<ClassMember> members) {
1✔
319
    if (isPrivate(name)) return;
1✔
320

321
    check(node);
1✔
322
    checkMethods(members);
1✔
323
  }
324
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc