diff --git a/README.md b/README.md index 02d62c85..a26b963d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ Add an `analysis_options.yaml` file under the `test/` directory, and include the ```yaml include: package:solid_lints/analysis_options_test.yaml + +analyzer: + plugins: + - custom_lint ``` Then you can see suggestions in your IDE or you can run checks manually: diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index 78d35b72..5bd79756 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -288,3 +288,6 @@ linter: - use_test_throws_matchers - valid_regexps - void_checks + +formatter: + trailing_commas: preserve diff --git a/lib/analysis_options_test.yaml b/lib/analysis_options_test.yaml index 0acf73b8..b98534ae 100644 --- a/lib/analysis_options_test.yaml +++ b/lib/analysis_options_test.yaml @@ -1,5 +1,9 @@ include: package:solid_lints/analysis_options.yaml +analyzer: + plugins: + - custom_lint + custom_lint: rules: # Tests usually organized in one large main() function making this rule not applicable. diff --git a/lib/src/lints/avoid_using_api/avoid_using_api_linter.dart b/lib/src/lints/avoid_using_api/avoid_using_api_linter.dart index 70e74e61..12fe3a88 100644 --- a/lib/src/lints/avoid_using_api/avoid_using_api_linter.dart +++ b/lib/src/lints/avoid_using_api/avoid_using_api_linter.dart @@ -19,6 +19,9 @@ class AvoidUsingApiLinter { required this.config, }); + /// The identifier for the default constructor + static const String _defaultConstructorIdentifier = '()'; + /// Access to the resolver for this lint context final CustomLintResolver resolver; @@ -191,6 +194,11 @@ class AvoidUsingApiLinter { String className, String source, ) { + if (identifier == _defaultConstructorIdentifier) { + _banDefaultConstructor(className, source, entryCode); + return; + } + context.registry.addSimpleIdentifier((node) { final name = node.name; if (name != identifier) { @@ -281,4 +289,98 @@ class AvoidUsingApiLinter { reporter.atNode(node.identifier, entryCode); }); } + + void _banDefaultConstructor( + String className, + String source, + LintCode entryCode, + ) { + context.registry.addInstanceCreationExpression((node) { + final constructorName = node.constructorName.type.name2.lexeme; + if (constructorName != className || node.constructorName.name != null) { + return; + } + + final sourcePath = + node.constructorName.type.element2?.library2?.uri.toString(); + if (sourcePath == null || !_matchesSource(sourcePath, source)) { + return; + } + + reporter.atNode(node, entryCode); + }); + } + + /// Lints usages of a named parameter from a given source + void banUsageWithSpecificNamedParameter( + LintCode entryCode, + String identifier, + String namedParameter, + String className, + String source, + ) { + context.registry.addMethodInvocation((node) { + final methodName = node.methodName.name; + if (methodName != identifier) return; + + final enclosingElement = node.methodName.element?.enclosingElement2; + if (enclosingElement == null || enclosingElement.name3 != className) { + return; + } + + if (!_containsNamedParameter(node.argumentList, namedParameter)) { + return; + } + + final libSource = enclosingElement.library2; + if (libSource == null) { + return; + } + + final sourcePath = libSource.uri.toString(); + if (!_matchesSource(sourcePath, source)) { + return; + } + + reporter.atNode(node.methodName, entryCode); + }); + + context.registry.addInstanceCreationExpression((node) { + String? expectedConstructorName; + + if (identifier != _defaultConstructorIdentifier) { + expectedConstructorName = identifier; + } + + final actualClassName = node.constructorName.type.name2.lexeme; + if (actualClassName != className) { + return; + } + + if (node.constructorName.name?.name != expectedConstructorName) { + return; + } + + if (!_containsNamedParameter(node.argumentList, namedParameter)) { + return; + } + + final sourcePath = + node.constructorName.type.element2?.library2?.uri.toString(); + if (sourcePath == null || !_matchesSource(sourcePath, source)) { + return; + } + + reporter.atNode(node, entryCode); + }); + } + + bool _containsNamedParameter( + ArgumentList argumentList, + String namedParameter, + ) => + argumentList.arguments.any( + (arg) => + arg is NamedExpression && arg.name.label.name == namedParameter, + ); } diff --git a/lib/src/lints/avoid_using_api/avoid_using_api_rule.dart b/lib/src/lints/avoid_using_api/avoid_using_api_rule.dart index a60bcfdd..2e96d221 100644 --- a/lib/src/lints/avoid_using_api/avoid_using_api_rule.dart +++ b/lib/src/lints/avoid_using_api/avoid_using_api_rule.dart @@ -126,6 +126,19 @@ class AvoidUsingApiRule extends SolidLintRule { switch (entry) { case AvoidUsingApiEntryParameters(:final source) when source == null: break; + case AvoidUsingApiEntryParameters( + :final identifier?, + :final namedParameter?, + :final className?, + :final source? + ): + linter.banUsageWithSpecificNamedParameter( + entryCode, + identifier, + namedParameter, + className, + source, + ); case AvoidUsingApiEntryParameters( :final identifier?, :final className?, diff --git a/lib/src/lints/avoid_using_api/models/avoid_using_api_entry_parameters.dart b/lib/src/lints/avoid_using_api/models/avoid_using_api_entry_parameters.dart index 23cec1cd..337a5ca7 100644 --- a/lib/src/lints/avoid_using_api/models/avoid_using_api_entry_parameters.dart +++ b/lib/src/lints/avoid_using_api/models/avoid_using_api_entry_parameters.dart @@ -7,6 +7,7 @@ import 'package:solid_lints/src/utils/parameter_utils.dart'; /// /// Parameters: /// * identifier: Variable/method name +/// * named_parameter: Named parameter of the constructor/method /// * class_name: Name of the class containing the variable/method /// * source: Package (e.g., dart:async or package:example) /// * severity: The default severity of the lint for each entry. @@ -35,6 +36,9 @@ class AvoidUsingApiEntryParameters { /// Variable/method name final String? identifier; + /// Named parameter of the constrcutor/method + final String? namedParameter; + /// Name of the class containing the variable/method final String? className; @@ -56,6 +60,7 @@ class AvoidUsingApiEntryParameters { /// Constructor for [AvoidUsingApiEntryParameters] model const AvoidUsingApiEntryParameters({ this.identifier, + this.namedParameter, this.className, this.source, this.severity, @@ -70,6 +75,7 @@ class AvoidUsingApiEntryParameters { ) => AvoidUsingApiEntryParameters( identifier: json['identifier'] as String?, + namedParameter: json['named_parameter'] as String?, className: json['class_name'] as String?, source: json['source'] as String?, severity: decodeErrorSeverity(json['severity'] as String?), diff --git a/lint_test/avoid_using_api/identifier_class_source_ban/analysis_options.yaml b/lint_test/avoid_using_api/identifier_class_source_ban/analysis_options.yaml index 654a08ad..c4a4dcf2 100644 --- a/lint_test/avoid_using_api/identifier_class_source_ban/analysis_options.yaml +++ b/lint_test/avoid_using_api/identifier_class_source_ban/analysis_options.yaml @@ -7,6 +7,10 @@ custom_lint: - avoid_using_api: severity: warning entries: + - identifier: () + class_name: BannedCodeUsage + source: package:external_source + reason: "BannedCodeUsage() from package:external_source is not allowed" - identifier: test4 class_name: BannedCodeUsage source: package:external_source diff --git a/lint_test/avoid_using_api/identifier_class_source_ban/lib/identifier_class_source_ban_test.dart b/lint_test/avoid_using_api/identifier_class_source_ban/lib/identifier_class_source_ban_test.dart index 4e65729f..7e8570e6 100644 --- a/lint_test/avoid_using_api/identifier_class_source_ban/lib/identifier_class_source_ban_test.dart +++ b/lint_test/avoid_using_api/identifier_class_source_ban/lib/identifier_class_source_ban_test.dart @@ -5,6 +5,7 @@ import 'dart:collection'; import 'package:external_source/external_source.dart'; void testingBannedCodeLint() async { + // expect_lint: avoid_using_api final bannedCodeUsage = BannedCodeUsage(); // expect_lint: avoid_using_api BannedCodeUsage.test2(); diff --git a/lint_test/avoid_using_api/named_parameter_ban/analysis_options.yaml b/lint_test/avoid_using_api/named_parameter_ban/analysis_options.yaml new file mode 100644 index 00000000..557c6a83 --- /dev/null +++ b/lint_test/avoid_using_api/named_parameter_ban/analysis_options.yaml @@ -0,0 +1,34 @@ +analyzer: + plugins: + - ../../custom_lint + +custom_lint: + rules: + - avoid_using_api: + severity: warning + entries: + - identifier: () + named_parameter: badParameter + class_name: NamedParameterBan + source: package:named_parameter_ban + reason: "Use goodParameter instead" + - identifier: namedConstructor + named_parameter: badParameter + class_name: NamedParameterBan + source: package:named_parameter_ban + reason: "Use goodParameter instead" + - identifier: staticMethod + named_parameter: badParameter + class_name: NamedParameterBan + source: package:named_parameter_ban + reason: "Use goodParameter instead" + - identifier: method + named_parameter: badParameter + class_name: NamedParameterBan + source: package:named_parameter_ban + reason: "Use goodParameter instead" + - identifier: extensionMethod + named_parameter: badParameter + class_name: NamedParameterBanExtension + source: package:named_parameter_ban + reason: "Use goodParameter instead" diff --git a/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban.dart b/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban.dart new file mode 100644 index 00000000..c81889b3 --- /dev/null +++ b/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban.dart @@ -0,0 +1,16 @@ +class NamedParameterBan { + NamedParameterBan({this.badParameter, this.goodParameter}); + + String? badParameter; + String? goodParameter; + + NamedParameterBan.namedConstructor( + {String? badParameter, String? goodParameter}) {} + + void method({String? badParameter, String? goodParameter}) {} + static void staticMethod({String? badParameter, String? goodParameter}) {} +} + +extension NamedParameterBanExtension on int { + String extensionMethod({String? badParameter, String? goodParameter}) => ''; +} diff --git a/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban_test.dart b/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban_test.dart new file mode 100644 index 00000000..1a583c72 --- /dev/null +++ b/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban_test.dart @@ -0,0 +1,26 @@ +import 'package:named_parameter_ban/named_parameter_ban.dart'; + +void testingBannedCodeLint() async { + // expect_lint: avoid_using_api + NamedParameterBan(badParameter: ''); + NamedParameterBan(goodParameter: ''); + + // expect_lint: avoid_using_api + NamedParameterBan.namedConstructor(badParameter: ''); + NamedParameterBan.namedConstructor(goodParameter: ''); + + // expect_lint: avoid_using_api + NamedParameterBan.staticMethod( + badParameter: 'test', + ); + NamedParameterBan.staticMethod(goodParameter: ''); + + final obj = NamedParameterBan(); + // expect_lint: avoid_using_api + obj.method(badParameter: ''); + obj.method(goodParameter: ''); + + // expect_lint: avoid_using_api + 0.extensionMethod(badParameter: ''); + 0.extensionMethod(goodParameter: ''); +} diff --git a/lint_test/avoid_using_api/named_parameter_ban/pubspec.yaml b/lint_test/avoid_using_api/named_parameter_ban/pubspec.yaml new file mode 100644 index 00000000..db054efe --- /dev/null +++ b/lint_test/avoid_using_api/named_parameter_ban/pubspec.yaml @@ -0,0 +1,17 @@ +name: named_parameter_ban +description: A sample command-line application. +version: 1.0.0 +publish_to: none + +environment: + sdk: ^3.1.3 + +dependencies: + external_source: + path: ../external_source + +dev_dependencies: + lints: ^3.0.0 + test: ^1.21.0 + solid_lints: + path: ../../../