Skip to content

Commit 4d2e8a9

Browse files
srawlinsCommit Queue
authored andcommitted
lint rules: Add strict_top_level_inference rule
Closes #59562 Change-Id: Ibf7ab6343d15ed328a366960b45cad4242528588 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400164 Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Phil Quitslund <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 1c79c3f commit 4d2e8a9

File tree

11 files changed

+1316
-27
lines changed

11 files changed

+1316
-27
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,18 @@ LintCode.specify_nonobvious_local_variable_types:
23672367
status: hasFix
23682368
LintCode.specify_nonobvious_property_types:
23692369
status: hasFix
2370+
LintCode.strict_top_level_inference_add_type:
2371+
status: noFix
2372+
notes: |-
2373+
There is no obvious way to pick a good candidate type.
2374+
LintCode.strict_top_level_inference_replace_keyword:
2375+
status: noFix
2376+
notes: |-
2377+
There is no obvious way to pick a good candidate type.
2378+
LintCode.strict_top_level_inference_split_to_types:
2379+
status: noFix
2380+
notes: |-
2381+
There is no obvious way to pick a good candidate type.
23702382
LintCode.test_types_in_equals:
23712383
status: noFix
23722384
LintCode.throw_in_finally:

pkg/linter/example/all.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ linter:
171171
- sort_unnamed_constructors_first
172172
- specify_nonobvious_local_variable_types
173173
- specify_nonobvious_property_types
174+
- strict_top_level_inference
174175
- test_types_in_equals
175176
- throw_in_finally
176177
- tighten_type_of_initializing_formals

pkg/linter/lib/src/extensions.dart

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -463,29 +463,20 @@ extension InhertanceManager3Extension on InheritanceManager3 {
463463
/// Returns the class member that is overridden by [member], if there is one,
464464
/// as defined by [getInherited].
465465
ExecutableElement2? overriddenMember(Element2? member) {
466-
ExecutableElement2? executable;
467-
switch (member) {
468-
case FieldElement2():
469-
executable = member.getter2;
470-
case MethodElement2():
471-
executable = member;
472-
case PropertyAccessorElement2():
473-
executable = member;
474-
}
466+
var executable = switch (member) {
467+
FieldElement2() => member.getter2,
468+
MethodElement2() => member,
469+
PropertyAccessorElement2() => member,
470+
_ => null,
471+
};
475472

476-
if (executable == null) {
477-
return null;
478-
}
473+
if (executable == null) return null;
479474

480475
var interfaceElement = executable.enclosingElement2;
481-
if (interfaceElement is! InterfaceElement2) {
482-
return null;
483-
}
476+
if (interfaceElement is! InterfaceElement2) return null;
484477

485478
var nameObj = Name.forElement(executable);
486-
if (nameObj == null) {
487-
return null;
488-
}
479+
if (nameObj == null) return null;
489480

490481
return getInherited3(interfaceElement.thisType, nameObj);
491482
}

pkg/linter/lib/src/lint_codes.g.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,31 @@ class LinterLintCode extends LintCode {
14891489
correctionMessage: "Try adding a type annotation.",
14901490
);
14911491

1492+
static const LintCode strict_top_level_inference_add_type = LinterLintCode(
1493+
LintNames.strict_top_level_inference,
1494+
"Missing type annotation.",
1495+
correctionMessage: "Try adding a type annotation.",
1496+
uniqueName: 'strict_top_level_inference_add_type',
1497+
);
1498+
1499+
static const LintCode strict_top_level_inference_replace_keyword =
1500+
LinterLintCode(
1501+
LintNames.strict_top_level_inference,
1502+
"Missing type annotation.",
1503+
correctionMessage: "Try replacing '{0}' with a type annotation.",
1504+
uniqueName: 'strict_top_level_inference_replace_keyword',
1505+
);
1506+
1507+
static const LintCode strict_top_level_inference_split_to_types =
1508+
LinterLintCode(
1509+
LintNames.strict_top_level_inference,
1510+
"Missing type annotation.",
1511+
correctionMessage:
1512+
"Try splitting the declaration and specify the different type "
1513+
"annotations.",
1514+
uniqueName: 'strict_top_level_inference_split_to_types',
1515+
);
1516+
14921517
static const LintCode test_types_in_equals = LinterLintCode(
14931518
LintNames.test_types_in_equals,
14941519
"Missing type test for '{0}' in '=='.",

pkg/linter/lib/src/lint_names.g.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,8 @@ abstract final class LintNames {
457457
static const String specify_nonobvious_property_types =
458458
'specify_nonobvious_property_types';
459459

460+
static const String strict_top_level_inference = 'strict_top_level_inference';
461+
460462
static const String super_goes_last = 'super_goes_last';
461463

462464
static const String test_types_in_equals = 'test_types_in_equals';

pkg/linter/lib/src/rules.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ import 'rules/sort_constructors_first.dart';
188188
import 'rules/sort_unnamed_constructors_first.dart';
189189
import 'rules/specify_nonobvious_local_variable_types.dart';
190190
import 'rules/specify_nonobvious_property_types.dart';
191+
import 'rules/strict_top_level_inference.dart';
191192
import 'rules/super_goes_last.dart';
192193
import 'rules/test_types_in_equals.dart';
193194
import 'rules/throw_in_finally.dart';
@@ -435,6 +436,7 @@ void registerLintRules() {
435436
..registerLintRule(SuperGoesLast())
436437
..registerLintRule(SpecifyNonObviousLocalVariableTypes())
437438
..registerLintRule(SpecifyNonObviousPropertyTypes())
439+
..registerLintRule(StrictTopLevelInference())
438440
..registerLintRule(TestTypesInEquals())
439441
..registerLintRule(ThrowInFinally())
440442
..registerLintRule(TightenTypeOfInitializingFormals())
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
// Copyright (c) 2024, 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+
import 'package:analyzer/dart/element/element2.dart';
9+
// ignore: implementation_imports
10+
import 'package:analyzer/src/dart/element/element.dart';
11+
import 'package:collection/collection.dart';
12+
13+
import '../analyzer.dart';
14+
import '../extensions.dart';
15+
16+
const _desc = r'Specify type annotations.';
17+
18+
class StrictTopLevelInference extends LintRule {
19+
StrictTopLevelInference()
20+
: super(
21+
name: LintNames.strict_top_level_inference,
22+
description: _desc,
23+
);
24+
25+
@override
26+
List<LintCode> get lintCodes => [
27+
LinterLintCode.strict_top_level_inference_add_type,
28+
LinterLintCode.strict_top_level_inference_replace_keyword,
29+
LinterLintCode.strict_top_level_inference_split_to_types,
30+
];
31+
32+
@override
33+
void registerNodeProcessors(
34+
NodeLintRegistry registry, LinterContext context) {
35+
var visitor = _Visitor(this, context);
36+
registry.addConstructorDeclaration(this, visitor);
37+
registry.addFunctionDeclaration(this, visitor);
38+
registry.addMethodDeclaration(this, visitor);
39+
registry.addSimpleFormalParameter(this, visitor);
40+
registry.addVariableDeclarationList(this, visitor);
41+
}
42+
}
43+
44+
class _Visitor extends SimpleAstVisitor<void> {
45+
final LintRule rule;
46+
47+
final LinterContext context;
48+
49+
_Visitor(this.rule, this.context);
50+
51+
@override
52+
void visitConstructorDeclaration(ConstructorDeclaration node) {
53+
_checkFormalParameters(node.parameters.parameters);
54+
}
55+
56+
@override
57+
void visitFunctionDeclaration(FunctionDeclaration node) {
58+
if (node.parent is! CompilationUnit) return;
59+
if (node.returnType == null && !node.isSetter) {
60+
_report(node.name);
61+
}
62+
63+
if (node.functionExpression.parameters case var parameters?) {
64+
_checkFormalParameters(parameters.parameters);
65+
}
66+
}
67+
68+
@override
69+
void visitMethodDeclaration(MethodDeclaration node) {
70+
var fragment = node.declaredFragment;
71+
if (fragment is PropertyAccessorFragment) {
72+
var element = fragment.element;
73+
if (node.isGetter) {
74+
_checkGetter(node, element);
75+
} else {
76+
_checkSetter(node, element);
77+
}
78+
} else if (fragment is MethodElementImpl) {
79+
_checkMethod(node, fragment);
80+
}
81+
}
82+
83+
@override
84+
void visitVariableDeclarationList(VariableDeclarationList node) {
85+
if (node.type != null) return;
86+
if (node.parent is! TopLevelVariableDeclaration &&
87+
node.parent is! FieldDeclaration) {
88+
return;
89+
}
90+
var variablesMissingAnInitializer =
91+
node.variables.where((v) => v.initializer == null).toList();
92+
93+
if (variablesMissingAnInitializer.isEmpty) return;
94+
95+
// At this point, we know that a type was not specified for the declaration
96+
// list, and at least one of the variables declared does not have an
97+
// initializer.
98+
99+
if (node.variables.length == 1) {
100+
var variable = node.variables.single;
101+
var overriddenMember = context.inheritanceManager
102+
.overriddenMember(variable.declaredFragment?.element);
103+
if (overriddenMember == null) {
104+
_report(variable.name, keyword: node.keyword);
105+
}
106+
} else {
107+
// Handle the multiple-variable case separately so that we can instead
108+
// report `LinterLintCode.strict_top_level_inference_split_to_types`.
109+
for (var variable in variablesMissingAnInitializer) {
110+
var overriddenMember = context.inheritanceManager
111+
.overriddenMember(variable.declaredFragment?.element);
112+
if (overriddenMember == null) {
113+
rule.reportLintForToken(
114+
variable.name,
115+
errorCode: LinterLintCode.strict_top_level_inference_split_to_types,
116+
);
117+
}
118+
}
119+
}
120+
}
121+
122+
void _checkFormalParameters(List<FormalParameter> parameters,
123+
{ExecutableElement2? overriddenMember}) {
124+
for (var i = 0; i < parameters.length; i++) {
125+
var parameter = parameters[i];
126+
if (parameter is DefaultFormalParameter) {
127+
parameter = parameter.parameter;
128+
}
129+
if (parameter is! SimpleFormalParameter) {
130+
// Every type of parameter other than simple formal parameters get a type
131+
// one way or another:
132+
// * Field formal parameters have an explicit type or it is derived from
133+
// the field type.
134+
// * Super formal parameters have an explicit type or it is derived from
135+
// the super constructor's corresponding parameter's type.
136+
// * Function-typed formal parameters have a function type, possibly with
137+
// implicit return and parameter types.
138+
return;
139+
}
140+
141+
if (parameter.type != null) return;
142+
if (overriddenMember == null) {
143+
_report(parameter.name, keyword: parameter.keyword);
144+
} else {
145+
if (parameter.isPositional) {
146+
if (overriddenMember.formalParameters.length <= i ||
147+
overriddenMember.formalParameters[i].isNamed) {
148+
// The overridden member does not have a corresponding parameter.
149+
_report(parameter.name, keyword: parameter.keyword);
150+
}
151+
} else {
152+
var overriddenParameter = overriddenMember.formalParameters
153+
.firstWhereOrNull((p) => p.isNamed);
154+
if (overriddenParameter == null) {
155+
// The overridden member does not have a corresponding parameter.
156+
_report(parameter.name, keyword: parameter.keyword);
157+
}
158+
}
159+
}
160+
}
161+
}
162+
163+
void _checkGetter(MethodDeclaration node, PropertyAccessorElement2 element) {
164+
if (node.returnType != null) return;
165+
166+
if (!_isOverride(node, element)) {
167+
rule.reportLintForToken(
168+
node.name,
169+
errorCode: LinterLintCode.strict_top_level_inference_add_type,
170+
);
171+
}
172+
}
173+
174+
void _checkMethod(MethodDeclaration node, MethodElementImpl element) {
175+
if (element.typeInferenceError != null) {
176+
// Inferring the return type and/or one or more parameter types resulted
177+
// in a type inference error. Do not report lint in this case.
178+
return;
179+
}
180+
181+
var container = element.enclosingFragment!.element;
182+
var noOverride = node.isStatic ||
183+
container is ExtensionElement2 ||
184+
container is ExtensionTypeElement2;
185+
186+
if (noOverride) {
187+
if (node.returnType == null) {
188+
rule.reportLintForToken(node.name,
189+
errorCode: LinterLintCode.strict_top_level_inference_add_type);
190+
}
191+
if (node.parameters case var parameters?) {
192+
_checkFormalParameters(parameters.parameters);
193+
}
194+
} else {
195+
var overriddenMember = context.inheritanceManager
196+
.overriddenMember(node.declaredFragment?.element);
197+
if (overriddenMember == null && node.returnType == null) {
198+
_report(node.name);
199+
}
200+
if (node.parameters case var parameters?) {
201+
_checkFormalParameters(parameters.parameters,
202+
overriddenMember: overriddenMember);
203+
}
204+
}
205+
}
206+
207+
void _checkSetter(MethodDeclaration node, PropertyAccessorElement2 element) {
208+
var parameter = node.parameters?.parameters.firstOrNull;
209+
if (parameter == null) return;
210+
if (parameter is! SimpleFormalParameter) return;
211+
if (parameter.type != null) return;
212+
213+
if (!_isOverride(node, element)) {
214+
rule.reportLintForToken(
215+
node.name,
216+
errorCode: LinterLintCode.strict_top_level_inference_add_type,
217+
);
218+
}
219+
}
220+
221+
bool _isOverride(MethodDeclaration node, PropertyAccessorElement2 element) {
222+
var container = element.enclosingElement2;
223+
if (node.isStatic) return false;
224+
if (container is ExtensionElement2) return false;
225+
if (container is ExtensionTypeElement2) return false;
226+
var overriddenMember = context.inheritanceManager
227+
.overriddenMember(node.declaredFragment?.element);
228+
return overriddenMember != null;
229+
}
230+
231+
void _report(Token? errorToken, {Token? keyword}) {
232+
if (keyword == null || keyword.type == Keyword.FINAL) {
233+
rule.reportLintForToken(
234+
errorToken,
235+
errorCode: LinterLintCode.strict_top_level_inference_add_type,
236+
);
237+
} else if (keyword.type == Keyword.VAR) {
238+
rule.reportLintForToken(
239+
errorToken,
240+
arguments: [keyword.lexeme],
241+
errorCode: LinterLintCode.strict_top_level_inference_replace_keyword,
242+
);
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)