Skip to content

Commit ee3e27d

Browse files
authored
Lint default constructor and named parameter (issue #126) (#212)
* feat: lint default constructor only * feat: add named parameter * fix: rename test package * fix: add explicit null check * CR fixes * fix: make default constructor identifier field private
1 parent b6ce57a commit ee3e27d

File tree

9 files changed

+219
-0
lines changed

9 files changed

+219
-0
lines changed

lib/src/lints/avoid_using_api/avoid_using_api_linter.dart

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class AvoidUsingApiLinter {
1919
required this.config,
2020
});
2121

22+
/// The identifier for the default constructor
23+
static const String _defaultConstructorIdentifier = '()';
24+
2225
/// Access to the resolver for this lint context
2326
final CustomLintResolver resolver;
2427

@@ -191,6 +194,11 @@ class AvoidUsingApiLinter {
191194
String className,
192195
String source,
193196
) {
197+
if (identifier == _defaultConstructorIdentifier) {
198+
_banDefaultConstructor(className, source, entryCode);
199+
return;
200+
}
201+
194202
context.registry.addSimpleIdentifier((node) {
195203
final name = node.name;
196204
if (name != identifier) {
@@ -281,4 +289,98 @@ class AvoidUsingApiLinter {
281289
reporter.atNode(node.identifier, entryCode);
282290
});
283291
}
292+
293+
void _banDefaultConstructor(
294+
String className,
295+
String source,
296+
LintCode entryCode,
297+
) {
298+
context.registry.addInstanceCreationExpression((node) {
299+
final constructorName = node.constructorName.type.name2.lexeme;
300+
if (constructorName != className || node.constructorName.name != null) {
301+
return;
302+
}
303+
304+
final sourcePath =
305+
node.constructorName.type.element2?.library2?.uri.toString();
306+
if (sourcePath == null || !_matchesSource(sourcePath, source)) {
307+
return;
308+
}
309+
310+
reporter.atNode(node, entryCode);
311+
});
312+
}
313+
314+
/// Lints usages of a named parameter from a given source
315+
void banUsageWithSpecificNamedParameter(
316+
LintCode entryCode,
317+
String identifier,
318+
String namedParameter,
319+
String className,
320+
String source,
321+
) {
322+
context.registry.addMethodInvocation((node) {
323+
final methodName = node.methodName.name;
324+
if (methodName != identifier) return;
325+
326+
final enclosingElement = node.methodName.element?.enclosingElement2;
327+
if (enclosingElement == null || enclosingElement.name3 != className) {
328+
return;
329+
}
330+
331+
if (!_containsNamedParameter(node.argumentList, namedParameter)) {
332+
return;
333+
}
334+
335+
final libSource = enclosingElement.library2;
336+
if (libSource == null) {
337+
return;
338+
}
339+
340+
final sourcePath = libSource.uri.toString();
341+
if (!_matchesSource(sourcePath, source)) {
342+
return;
343+
}
344+
345+
reporter.atNode(node.methodName, entryCode);
346+
});
347+
348+
context.registry.addInstanceCreationExpression((node) {
349+
String? expectedConstructorName;
350+
351+
if (identifier != _defaultConstructorIdentifier) {
352+
expectedConstructorName = identifier;
353+
}
354+
355+
final actualClassName = node.constructorName.type.name2.lexeme;
356+
if (actualClassName != className) {
357+
return;
358+
}
359+
360+
if (node.constructorName.name?.name != expectedConstructorName) {
361+
return;
362+
}
363+
364+
if (!_containsNamedParameter(node.argumentList, namedParameter)) {
365+
return;
366+
}
367+
368+
final sourcePath =
369+
node.constructorName.type.element2?.library2?.uri.toString();
370+
if (sourcePath == null || !_matchesSource(sourcePath, source)) {
371+
return;
372+
}
373+
374+
reporter.atNode(node, entryCode);
375+
});
376+
}
377+
378+
bool _containsNamedParameter(
379+
ArgumentList argumentList,
380+
String namedParameter,
381+
) =>
382+
argumentList.arguments.any(
383+
(arg) =>
384+
arg is NamedExpression && arg.name.label.name == namedParameter,
385+
);
284386
}

lib/src/lints/avoid_using_api/avoid_using_api_rule.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,19 @@ class AvoidUsingApiRule extends SolidLintRule<AvoidUsingApiParameters> {
126126
switch (entry) {
127127
case AvoidUsingApiEntryParameters(:final source) when source == null:
128128
break;
129+
case AvoidUsingApiEntryParameters(
130+
:final identifier?,
131+
:final namedParameter?,
132+
:final className?,
133+
:final source?
134+
):
135+
linter.banUsageWithSpecificNamedParameter(
136+
entryCode,
137+
identifier,
138+
namedParameter,
139+
className,
140+
source,
141+
);
129142
case AvoidUsingApiEntryParameters(
130143
:final identifier?,
131144
:final className?,

lib/src/lints/avoid_using_api/models/avoid_using_api_entry_parameters.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:solid_lints/src/utils/parameter_utils.dart';
77
///
88
/// Parameters:
99
/// * identifier: Variable/method name
10+
/// * named_parameter: Named parameter of the constructor/method
1011
/// * class_name: Name of the class containing the variable/method
1112
/// * source: Package (e.g., dart:async or package:example)
1213
/// * severity: The default severity of the lint for each entry.
@@ -35,6 +36,9 @@ class AvoidUsingApiEntryParameters {
3536
/// Variable/method name
3637
final String? identifier;
3738

39+
/// Named parameter of the constrcutor/method
40+
final String? namedParameter;
41+
3842
/// Name of the class containing the variable/method
3943
final String? className;
4044

@@ -56,6 +60,7 @@ class AvoidUsingApiEntryParameters {
5660
/// Constructor for [AvoidUsingApiEntryParameters] model
5761
const AvoidUsingApiEntryParameters({
5862
this.identifier,
63+
this.namedParameter,
5964
this.className,
6065
this.source,
6166
this.severity,
@@ -70,6 +75,7 @@ class AvoidUsingApiEntryParameters {
7075
) =>
7176
AvoidUsingApiEntryParameters(
7277
identifier: json['identifier'] as String?,
78+
namedParameter: json['named_parameter'] as String?,
7379
className: json['class_name'] as String?,
7480
source: json['source'] as String?,
7581
severity: decodeErrorSeverity(json['severity'] as String?),

lint_test/avoid_using_api/identifier_class_source_ban/analysis_options.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ custom_lint:
77
- avoid_using_api:
88
severity: warning
99
entries:
10+
- identifier: ()
11+
class_name: BannedCodeUsage
12+
source: package:external_source
13+
reason: "BannedCodeUsage() from package:external_source is not allowed"
1014
- identifier: test4
1115
class_name: BannedCodeUsage
1216
source: package:external_source

lint_test/avoid_using_api/identifier_class_source_ban/lib/identifier_class_source_ban_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'dart:collection';
55
import 'package:external_source/external_source.dart';
66

77
void testingBannedCodeLint() async {
8+
// expect_lint: avoid_using_api
89
final bannedCodeUsage = BannedCodeUsage();
910
// expect_lint: avoid_using_api
1011
BannedCodeUsage.test2();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
analyzer:
2+
plugins:
3+
- ../../custom_lint
4+
5+
custom_lint:
6+
rules:
7+
- avoid_using_api:
8+
severity: warning
9+
entries:
10+
- identifier: ()
11+
named_parameter: badParameter
12+
class_name: NamedParameterBan
13+
source: package:named_parameter_ban
14+
reason: "Use goodParameter instead"
15+
- identifier: namedConstructor
16+
named_parameter: badParameter
17+
class_name: NamedParameterBan
18+
source: package:named_parameter_ban
19+
reason: "Use goodParameter instead"
20+
- identifier: staticMethod
21+
named_parameter: badParameter
22+
class_name: NamedParameterBan
23+
source: package:named_parameter_ban
24+
reason: "Use goodParameter instead"
25+
- identifier: method
26+
named_parameter: badParameter
27+
class_name: NamedParameterBan
28+
source: package:named_parameter_ban
29+
reason: "Use goodParameter instead"
30+
- identifier: extensionMethod
31+
named_parameter: badParameter
32+
class_name: NamedParameterBanExtension
33+
source: package:named_parameter_ban
34+
reason: "Use goodParameter instead"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class NamedParameterBan {
2+
NamedParameterBan({this.badParameter, this.goodParameter});
3+
4+
String? badParameter;
5+
String? goodParameter;
6+
7+
NamedParameterBan.namedConstructor(
8+
{String? badParameter, String? goodParameter}) {}
9+
10+
void method({String? badParameter, String? goodParameter}) {}
11+
static void staticMethod({String? badParameter, String? goodParameter}) {}
12+
}
13+
14+
extension NamedParameterBanExtension on int {
15+
String extensionMethod({String? badParameter, String? goodParameter}) => '';
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import 'package:named_parameter_ban/named_parameter_ban.dart';
2+
3+
void testingBannedCodeLint() async {
4+
// expect_lint: avoid_using_api
5+
NamedParameterBan(badParameter: '');
6+
NamedParameterBan(goodParameter: '');
7+
8+
// expect_lint: avoid_using_api
9+
NamedParameterBan.namedConstructor(badParameter: '');
10+
NamedParameterBan.namedConstructor(goodParameter: '');
11+
12+
// expect_lint: avoid_using_api
13+
NamedParameterBan.staticMethod(
14+
badParameter: 'test',
15+
);
16+
NamedParameterBan.staticMethod(goodParameter: '');
17+
18+
final obj = NamedParameterBan();
19+
// expect_lint: avoid_using_api
20+
obj.method(badParameter: '');
21+
obj.method(goodParameter: '');
22+
23+
// expect_lint: avoid_using_api
24+
0.extensionMethod(badParameter: '');
25+
0.extensionMethod(goodParameter: '');
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: named_parameter_ban
2+
description: A sample command-line application.
3+
version: 1.0.0
4+
publish_to: none
5+
6+
environment:
7+
sdk: ^3.1.3
8+
9+
dependencies:
10+
external_source:
11+
path: ../external_source
12+
13+
dev_dependencies:
14+
lints: ^3.0.0
15+
test: ^1.21.0
16+
solid_lints:
17+
path: ../../../

0 commit comments

Comments
 (0)