From 3165380fd71f0d2dd25c570c4034e4e0b99c7fd7 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 30 Jun 2026 19:16:44 +0300 Subject: [PATCH 1/6] refactor: migrate avoid_using_api --- lib/main.dart | 5 +- .../avoid_using_api_linter.dart | 386 ---------------- .../avoid_using_api/avoid_using_api_rule.dart | 201 +++------ .../avoid_using_api_utils.dart | 21 - .../avoid_using_api_entry_parameters.dart | 4 +- .../models/avoid_using_api_parameters.dart | 3 + .../visitors/avoid_using_api_visitor.dart | 417 ++++++++++++++++++ lib/src/utils/node_utils.dart | 34 ++ lib/src/utils/path_utils.dart | 39 +- .../avoid_using_api/analysis_options.yaml | 7 - .../class_source_ban/analysis_options.yaml | 15 - .../lib/class_source_ban_test.dart | 37 -- .../class_source_ban/pubspec.yaml | 17 - .../external_source/lib/banned_library.dart | 1 - .../external_source/lib/external_source.dart | 30 -- .../external_source/pubspec.yaml | 13 - .../analysis_options.yaml | 40 -- .../lib/identifier_class_source_ban_test.dart | 38 -- .../identifier_class_source_ban/pubspec.yaml | 17 - .../analysis_options.yaml | 18 - .../identifier_extension_source_ban_test.dart | 11 - .../pubspec.yaml | 17 - .../analysis_options.yaml | 12 - .../lib/identifier_source_ban_test.dart | 9 - .../identifier_source_ban/pubspec.yaml | 17 - .../named_parameter_ban/analysis_options.yaml | 34 -- .../lib/named_parameter_ban.dart | 16 - .../lib/named_parameter_ban_test.dart | 26 -- .../named_parameter_ban/pubspec.yaml | 17 - .../source_ban/analysis_options.yaml | 18 - .../source_ban/lib/source_ban_test.dart | 6 - .../lib/src/domain/excludes_test.ex.dart | 6 - .../lib/src/domain/includes_test.dart | 9 - .../avoid_using_api/source_ban/pubspec.yaml | 17 - .../source_excludes_ban/analysis_options.yaml | 18 - .../lib/source_excludes_ban_test.dart | 6 - .../lib/src/domain/excludes_test.ex.dart | 6 - .../lib/src/domain/includes_test.dart | 9 - .../source_excludes_ban/pubspec.yaml | 17 - .../source_includes_ban/analysis_options.yaml | 22 - .../lib/source_includes_ban_test.dart | 5 - .../lib/src/domain/excludes_test.ex.dart | 9 - .../lib/src/domain/includes_test.dart | 14 - .../source_includes_ban/pubspec.yaml | 17 - test/lints/auto_lint_data.dart | 19 + test/lints/auto_test_lint_offsets.dart | 41 +- .../avoid_using_api_rule_test.dart | 359 +++++++++++++++ 47 files changed, 947 insertions(+), 1153 deletions(-) delete mode 100644 lib/src/lints/avoid_using_api/avoid_using_api_linter.dart delete mode 100644 lib/src/lints/avoid_using_api/avoid_using_api_utils.dart create mode 100644 lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart delete mode 100644 lint_test/avoid_using_api/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/class_source_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/class_source_ban/lib/class_source_ban_test.dart delete mode 100644 lint_test/avoid_using_api/class_source_ban/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/external_source/lib/banned_library.dart delete mode 100644 lint_test/avoid_using_api/external_source/lib/external_source.dart delete mode 100644 lint_test/avoid_using_api/external_source/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/identifier_class_source_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/identifier_class_source_ban/lib/identifier_class_source_ban_test.dart delete mode 100644 lint_test/avoid_using_api/identifier_class_source_ban/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/identifier_extension_source_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/identifier_extension_source_ban/lib/identifier_extension_source_ban_test.dart delete mode 100644 lint_test/avoid_using_api/identifier_extension_source_ban/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/identifier_source_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/identifier_source_ban/lib/identifier_source_ban_test.dart delete mode 100644 lint_test/avoid_using_api/identifier_source_ban/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/named_parameter_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban.dart delete mode 100644 lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban_test.dart delete mode 100644 lint_test/avoid_using_api/named_parameter_ban/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/source_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/source_ban/lib/source_ban_test.dart delete mode 100644 lint_test/avoid_using_api/source_ban/lib/src/domain/excludes_test.ex.dart delete mode 100644 lint_test/avoid_using_api/source_ban/lib/src/domain/includes_test.dart delete mode 100644 lint_test/avoid_using_api/source_ban/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/source_excludes_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/source_excludes_ban/lib/source_excludes_ban_test.dart delete mode 100644 lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/excludes_test.ex.dart delete mode 100644 lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/includes_test.dart delete mode 100644 lint_test/avoid_using_api/source_excludes_ban/pubspec.yaml delete mode 100644 lint_test/avoid_using_api/source_includes_ban/analysis_options.yaml delete mode 100644 lint_test/avoid_using_api/source_includes_ban/lib/source_includes_ban_test.dart delete mode 100644 lint_test/avoid_using_api/source_includes_ban/lib/src/domain/excludes_test.ex.dart delete mode 100644 lint_test/avoid_using_api/source_includes_ban/lib/src/domain/includes_test.dart delete mode 100644 lint_test/avoid_using_api/source_includes_ban/pubspec.yaml create mode 100644 test/lints/auto_lint_data.dart create mode 100644 test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart diff --git a/lib/main.dart b/lib/main.dart index 586c5d13..dabda7dc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_wi import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart'; import 'package:solid_lints/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart'; +import 'package:solid_lints/src/lints/avoid_using_api/avoid_using_api_rule.dart'; import 'package:solid_lints/src/lints/cyclomatic_complexity/cyclomatic_complexity_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart'; @@ -69,11 +70,9 @@ class SolidLintsPlugin extends Plugin { NoEmptyBlockRule(analysisOptionsLoader: analysisLoader), UseNearestContextRule(), NoMagicNumberRule(analysisOptionsLoader: analysisLoader), + AvoidUsingApiRule(analysisOptionsLoader: analysisLoader), preferFirstRule, preferLastRule, - // TODO: Add more lint rules and use analysisLoader - // for rules that need parameters - // For example: `CyclomaticComplexityRule(analysisLoader)` ]; for (final lintRule in lintRules) { 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 deleted file mode 100644 index 2f369081..00000000 --- a/lib/src/lints/avoid_using_api/avoid_using_api_linter.dart +++ /dev/null @@ -1,386 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/error/listener.dart'; -import 'package:collection/collection.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:path/path.dart' as p; -import 'package:solid_lints/src/lints/avoid_using_api/avoid_using_api_rule.dart'; -import 'package:solid_lints/src/lints/avoid_using_api/avoid_using_api_utils.dart'; -import 'package:solid_lints/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; - -/// Linter for [AvoidUsingApiRule] -class AvoidUsingApiLinter { - /// Linter for [AvoidUsingApiRule] - const AvoidUsingApiLinter({ - required this.resolver, - required this.reporter, - required this.context, - required this.config, - }); - - /// The identifier for the default constructor - static const String _defaultConstructorIdentifier = '()'; - - /// Access to the resolver for this lint context - final CustomLintResolver resolver; - - /// The error reporter to create lints - final DiagnosticReporter reporter; - - /// The current lint context - final CustomLintContext context; - - /// The configuration parameters for the lint - final RuleConfig config; - - bool _matchesSource(String parentSourcePath, String sourceToMatch) { - final Uri parentUri = p.toUri(parentSourcePath); - final Uri sourceUri = p.toUri(sourceToMatch); - - final libraryName = parentUri.pathSegments.first; - final sourceLibraryName = sourceUri.pathSegments.first; - - final matchesLibraryName = libraryName == sourceLibraryName; - final shouldLintFileFromSource = sourceUri.pathSegments.length > 1; - if (shouldLintFileFromSource) { - final sourceFile = sourceUri.pathSegments.sublist(1).join('/'); - final libraryFile = parentUri.pathSegments.sublist(1).join('/'); - - final matchesFile = p.equals(sourceFile, libraryFile); - return matchesLibraryName && matchesFile; - } - - return matchesLibraryName; - } - - /// Lints usages of a given source - void banSource(LintCode entryCode, String source) { - context.registry.addSimpleIdentifier((node) { - final sourcePath = node.sourceUrl; - if (sourcePath == null || !_matchesSource(sourcePath, source)) { - return; - } - - if (node.parent is ConstructorDeclaration) { - return; - } - - reporter.atNode(node, entryCode); - }); - - context.registry.addNamedType((node) { - final sourceName = node.sourceUrl; - if (sourceName == null || !_matchesSource(sourceName, source)) { - return; - } - - reporter.atNode(node, entryCode); - }); - } - - /// Lints usages of a global variable or function from a given source - void banIdFromSource(LintCode entryCode, String identifier, String source) { - // only matches globals - context.registry.addSimpleIdentifier((node) { - final name = node.name; - if (name != identifier) { - return; - } - - final sourcePath = node.sourceUrl; - if (sourcePath == null || !_matchesSource(sourcePath, source)) { - return; - } - - if (node.parent is ConstructorDeclaration) { - return; - } - - switch (node.element) { - case LocalFunctionElement() || - TopLevelFunctionElement() || - PropertyAccessorElement(): - reporter.atNode(node, entryCode); - } - }); - } - - /// Lints usages of a class and its members from a given source - void banClassFromSource( - LintCode entryCode, - String className, - String source, - ) { - context.registry.addVariableDeclaration((node) { - final typeName = node.declaredFragment?.element.type.element?.name; - if (typeName != className) { - return; - } - - final sourcePath = - node.declaredFragment?.element.type.element?.library?.uri.toString(); - if (sourcePath == null || !_matchesSource(sourcePath, source)) { - return; - } - reporter.atNode(node, entryCode); - }); - - context.registry.addSimpleIdentifier((node) { - final parent = node.parent; - if (parent == null) { - return; - } - - if (parent is ConstructorDeclaration) { - return; - } - - final entityBeforeNode = parent.childEntities.firstOrNull; - switch (entityBeforeNode) { - case InstanceCreationExpression(:final staticType?): - case SimpleIdentifier(:final staticType?): - final parentSourcePath = - staticType.element?.library?.uri.toString() ?? node.sourceUrl; - if (parentSourcePath == null || - !_matchesSource(parentSourcePath, source)) { - return; - } - - final parentTypeName = staticType.element?.name; - if (parentTypeName != className) { - return; - } - case SimpleIdentifier(:final element?): - final parentSourcePath = - element.library?.uri.toString() ?? node.sourceUrl; - if (parentSourcePath == null || - !_matchesSource(parentSourcePath, source)) { - return; - } - final parentElementName = element.name; - if (parentElementName != className) { - return; - } - default: - return; - } - - reporter.atNode(parent, entryCode); - }); - - context.registry.addNamedType((node) { - final nodeTypeName = node.name.lexeme; - if (nodeTypeName != className) { - return; - } - - final sourcePath = node.sourceUrl; - if (sourcePath == null || !_matchesSource(sourcePath, source)) { - return; - } - - reporter.atNode( - node.parent?.parent ?? node.parent ?? node, - entryCode, - ); - }); - } - - /// Lints usages of a class member from a given source - void banIdFromClassFromSource( - LintCode entryCode, - String identifier, - String className, - String source, - ) { - if (identifier == _defaultConstructorIdentifier) { - _banDefaultConstructor(className, source, entryCode); - return; - } - - context.registry.addSimpleIdentifier((node) { - final name = node.name; - if (name != identifier) { - return; - } - - final sourcePath = node.sourceUrl; - if (sourcePath == null || !_matchesSource(sourcePath, source)) { - return; - } - - final parent = node.parent; - if (parent == null) { - return; - } - - if (parent is ConstructorDeclaration) { - return; - } - - final entityBeforeNode = parent.childEntities.firstOrNull; - switch (entityBeforeNode) { - case InstanceCreationExpression(:final staticType?): - case SimpleIdentifier(:final staticType?): - final parentTypeName = staticType.element?.name; - if (parentTypeName != className) { - return; - } - - reporter.atNode( - node.parent ?? node, - entryCode, - ); - case SimpleIdentifier(:final element?): - final parentElementName = element.name; - if (parentElementName != className) { - return; - } - - reporter.atNode( - node.parent ?? node, - entryCode, - ); - case NamedType(:final element?): - final parentTypeName = element.name; - if (parentTypeName != className) { - return; - } - - reporter.atNode( - node.parent?.parent ?? node.parent ?? node, - entryCode, - ); - } - }); - - context.registry.addMethodInvocation((node) { - final methodName = node.methodName.name; - if (methodName != identifier) return; - - final enclosingElement = node.methodName.element?.enclosingElement; - if (enclosingElement is! ExtensionElement || - enclosingElement.name != className) { - return; - } - - final sourcePath = enclosingElement.library.uri.toString(); - if (!_matchesSource(sourcePath, source)) { - return; - } - - reporter.atNode(node.methodName, entryCode); - }); - - context.registry.addPrefixedIdentifier((node) { - final propertyName = node.identifier.name; - if (propertyName != identifier) return; - - final enclosingElement = node.identifier.element?.enclosingElement; - if (enclosingElement is! ExtensionElement || - enclosingElement.name != className) { - return; - } - - final sourcePath = enclosingElement.library.uri.toString(); - if (!_matchesSource(sourcePath, source)) return; - - reporter.atNode(node.identifier, entryCode); - }); - } - - void _banDefaultConstructor( - String className, - String source, - LintCode entryCode, - ) { - context.registry.addInstanceCreationExpression((node) { - final constructorName = node.constructorName.type.name.lexeme; - if (constructorName != className || node.constructorName.name != null) { - return; - } - - final sourcePath = - node.constructorName.type.element?.library?.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?.enclosingElement; - if (enclosingElement == null || enclosingElement.name != className) { - return; - } - - if (!_containsNamedParameter(node.argumentList, namedParameter)) { - return; - } - - final libSource = enclosingElement.library; - 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.name.lexeme; - if (actualClassName != className) { - return; - } - - if (node.constructorName.name?.name != expectedConstructorName) { - return; - } - - if (!_containsNamedParameter(node.argumentList, namedParameter)) { - return; - } - - final sourcePath = - node.constructorName.type.element?.library?.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 1cab45ce..8c459acb 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 @@ -1,164 +1,77 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:solid_lints/src/lints/avoid_using_api/avoid_using_api_linter.dart'; -import 'package:solid_lints/src/lints/avoid_using_api/models/avoid_using_api_entry_parameters.dart'; +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; +import 'package:analyzer/error/error.dart'; import 'package:solid_lints/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; +import 'package:solid_lints/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; -import 'package:solid_lints/src/utils/parameter_utils.dart'; -import 'package:solid_lints/src/utils/path_utils.dart'; -/// A `avoid_using_api` rule which -/// warns about usage of avoided APIs +/// A `avoid_using_api` rule which warns about usage of avoided APIs. /// +/// You can configure specific classes, methods, or packages that should be +/// avoided in your codebase, along with custom warning messages. /// -/// ### Usage -/// -/// External code can be "deprecated" when there is a better option available: -/// -/// ```yaml -/// custom_lint: -/// rules: -/// - avoid_using_api: -/// severity: info -/// entries: -/// - class_name: Future -/// identifier: wait -/// source: dart:async -/// reason: "Future.wait should be avoided because it loses type -/// safety for the results. Use a Record's `wait` method -/// instead." -/// severity: warning -/// ``` -/// -/// Result: -/// -/// ```dart -/// void main() async { -/// await Future.wait([...]); // LINT -/// await (...).wait; // OK -/// } -/// ``` -/// -/// ### Advanced Usage -/// -/// Each entry also has `includes` and `excludes` parameters. -/// These paths utilize [Glob](https://pub.dev/packages/glob) -/// patterns to determine if a lint entry should be applied to a file. -/// -/// For example, a lint to prevent usage of a domain-only package outside of the -/// domain folder: +/// ### Example config: /// /// ```yaml -/// custom_lint: -/// rules: -/// - avoid_using_api: -/// severity: info -/// entries: -/// - source: package:domain_models -/// excludes: -/// - "**/domain/**.dart" -/// reason: "domain_models is only intended to be used in the domain -/// layer." +/// plugins: +/// solid_lints: +/// diagnostics: +/// avoid_using_api: +/// entries: +/// - class_name: LegacyClient +/// source: package:legacy_api/legacy_api.dart +/// reason: 'Use ModernClient instead.' +/// - identifier: print +/// source: dart:core +/// reason: 'Use logging framework instead.' /// ``` -/// -/// Contributed by getBoolean (https://github.com/getBoolean). class AvoidUsingApiRule extends SolidLintRule { - /// This lint rule represents - /// the error whether we use bad formatted double literals. + /// This lint name. static const String lintName = 'avoid_using_api'; - AvoidUsingApiRule._(super.config); + /// The default lint message when no reason is provided. + static const String defaultMessage = + 'Usage of this code has been discouraged by your project.'; - /// Creates a new instance of [AvoidUsingApiRule] - /// based on the lint configuration. - factory AvoidUsingApiRule.createRule( - CustomLintConfigs configs, - ) { - final RuleConfig rule = RuleConfig( - configs: configs, - name: lintName, - paramsParser: AvoidUsingApiParameters.fromJson, - problemMessage: (_) => - 'Usage of this code has been discouraged by your project.', - ); - - return AvoidUsingApiRule._(rule); - } + static const _code = LintCode( + lintName, + defaultMessage, + ); @override - Future run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, - ) async { - final rootPath = (await resolver.getResolvedUnitResult()) - .session - .analysisContext - .contextRoot - .root - .path; - final linter = AvoidUsingApiLinter( - resolver: resolver, - reporter: reporter, - context: context, - config: config, - ); + DiagnosticCode get diagnosticCode => _code; - final parameters = config.parameters; - for (final entry in parameters.entries) { - if (shouldSkipFile( - includeGlobs: entry.includes, - excludeGlobs: entry.excludes, - path: resolver.path, - rootPath: rootPath, - )) { - continue; - } + /// Creates a new instance of [AvoidUsingApiRule]. + AvoidUsingApiRule({ + required super.analysisOptionsLoader, + }) : super.withParameters( + name: lintName, + description: 'Avoid using specific APIs.', + parametersParser: AvoidUsingApiParameters.fromJson, + ); - final entryCode = super.code.copyWith( - errorSeverity: entry.severity ?? - parameters.severity ?? - super.code.errorSeverity, - problemMessage: entry.reason, - ); + @override + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, + ) { + super.registerNodeProcessors(registry, context); - 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?, - :final source? - ): - linter.banIdFromClassFromSource( - entryCode, - identifier, - className, - source, - ); - case AvoidUsingApiEntryParameters(:final className?, :final source?): - linter.banClassFromSource(entryCode, className, source); - case AvoidUsingApiEntryParameters(:final identifier?, :final source?): - linter.banIdFromSource(entryCode, identifier, source); - case AvoidUsingApiEntryParameters(:final source?): - linter.banSource(entryCode, source); - case AvoidUsingApiEntryParameters(): - break; - } + final parameters = + getParametersForContext(context) ?? AvoidUsingApiParameters.empty(); + if (parameters.entries.isEmpty) { + return; } + + final visitor = AvoidUsingApiVisitor( + parameters: parameters, + context: context, + ); + + registry.addSimpleIdentifier(this, visitor); + registry.addNamedType(this, visitor); + registry.addVariableDeclaration(this, visitor); + registry.addInstanceCreationExpression(this, visitor); + registry.addMethodInvocation(this, visitor); } } diff --git a/lib/src/lints/avoid_using_api/avoid_using_api_utils.dart b/lib/src/lints/avoid_using_api/avoid_using_api_utils.dart deleted file mode 100644 index ef744380..00000000 --- a/lib/src/lints/avoid_using_api/avoid_using_api_utils.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; - -/// Extension to get library of the node -extension SimpleIdentifierParentSourceExtension on SimpleIdentifier { - /// Returns library of the node - String? get sourceUrl { - final parentSource = element?.library; - final parentSourceName = parentSource?.uri.toString(); - return parentSourceName; - } -} - -/// Extension to get library of the node -extension NamedTypeParentSourceExtension on NamedType { - /// Returns library of the node - String? get sourceUrl { - final parentSource = element?.library; - final parentSourceName = parentSource?.uri.toString(); - return parentSourceName; - } -} 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 145116da..7093cf5a 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 @@ -51,10 +51,10 @@ class AvoidUsingApiEntryParameters { /// Explain why the code is banned final String? reason; - /// Regex patterns for files to include + /// Glob patterns for files to include final List includes; - /// Regex patterns for files to exclude + /// Glob patterns for files to exclude final List excludes; /// Constructor for [AvoidUsingApiEntryParameters] model diff --git a/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart b/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart index b7e603a3..1ce252fb 100644 --- a/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart +++ b/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart @@ -37,6 +37,9 @@ class AvoidUsingApiParameters { this.severity, }); + /// Empty [AvoidUsingApiParameters] model. + factory AvoidUsingApiParameters.empty() => const AvoidUsingApiParameters(); + /// Method for creating from json data factory AvoidUsingApiParameters.fromJson( Map json, diff --git a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart new file mode 100644 index 00000000..d22fe2f2 --- /dev/null +++ b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart @@ -0,0 +1,417 @@ +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:solid_lints/src/lints/avoid_using_api/avoid_using_api_rule.dart'; +import 'package:solid_lints/src/lints/avoid_using_api/models/avoid_using_api_entry_parameters.dart'; +import 'package:solid_lints/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart'; +import 'package:solid_lints/src/utils/node_utils.dart'; +import 'package:solid_lints/src/utils/path_utils.dart'; + +/// The AST visitor that checks for avoided API usages. +class AvoidUsingApiVisitor extends SimpleAstVisitor { + /// The default constructor identifier representation. + static const String _defaultConstructorIdentifier = '()'; + + /// The parameters configuration. + final AvoidUsingApiParameters parameters; + + /// The rule context. + final RuleContext context; + + /// Creates a new instance of [AvoidUsingApiVisitor]. + AvoidUsingApiVisitor({ + required this.parameters, + required this.context, + }); + + List _getActiveEntries( + String filePath, + String rootPath, + ) { + return parameters.entries.where((entry) { + return !shouldSkipFile( + includeGlobs: entry.includes, + excludeGlobs: entry.excludes, + path: filePath, + rootPath: rootPath, + ); + }).toList(); + } + + List? _cachedActiveEntries; + + ({ + List activeEntries, + DiagnosticReporter reporter, + })? + _resolveContext() { + final currentUnit = context.currentUnit; + if (currentUnit == null) return null; + + final activeEntries = _cachedActiveEntries ??= _getActiveEntries( + currentUnit.file.path, + context.package?.root.path ?? '', + ); + + return ( + activeEntries: activeEntries, + reporter: currentUnit.diagnosticReporter, + ); + } + + LintCode _getLintCode(AvoidUsingApiEntryParameters entry) => LintCode( + AvoidUsingApiRule.lintName, + entry.reason ?? AvoidUsingApiRule.defaultMessage, + severity: entry.severity ?? parameters.severity ?? DiagnosticSeverity.INFO, + ); + + @override + void visitSimpleIdentifier(SimpleIdentifier node) { + super.visitSimpleIdentifier(node); + + final resolved = _resolveContext(); + if (resolved == null) return; + final (:activeEntries, :reporter) = resolved; + + for (final entry in activeEntries) { + final source = entry.source; + if (source == null) continue; + + switch ((entry.className, entry.identifier, entry.namedParameter)) { + case (String _, String _, String _): + // Handled in visitMethodInvocation and + // visitInstanceCreationExpression + continue; + case (String _, String _, null): + _checkIdFromClassFromSource(node, entry, reporter); + case (String _, null, null): + _checkClassFromSource(node, entry, reporter); + case (null, String _, null): + _checkIdFromSource(node, entry, reporter); + case (null, null, null): + _checkSource(node, entry, reporter); + default: + break; + } + } + } + + @override + void visitNamedType(NamedType node) { + super.visitNamedType(node); + + final resolved = _resolveContext(); + if (resolved == null) return; + final (:activeEntries, :reporter) = resolved; + + for (final entry in activeEntries) { + _checkNamedType(node, entry, reporter); + } + } + + @override + void visitVariableDeclaration(VariableDeclaration node) { + super.visitVariableDeclaration(node); + + final resolved = _resolveContext(); + if (resolved == null) return; + final (:activeEntries, :reporter) = resolved; + + for (final entry in activeEntries) { + _checkVariableDeclaration(node, entry, reporter); + } + } + + @override + void visitInstanceCreationExpression(InstanceCreationExpression node) { + super.visitInstanceCreationExpression(node); + + final resolved = _resolveContext(); + if (resolved == null) return; + final (:activeEntries, :reporter) = resolved; + + for (final entry in activeEntries) { + _checkInstanceCreation(node, entry, reporter); + } + } + + @override + void visitMethodInvocation(MethodInvocation node) { + super.visitMethodInvocation(node); + + final resolved = _resolveContext(); + if (resolved == null) return; + final (:activeEntries, :reporter) = resolved; + + for (final entry in activeEntries) { + _checkMethodInvocation(node, entry, reporter); + } + } + + void _checkClassFromSource( + SimpleIdentifier node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final className = entry.className; + final source = entry.source; + if (className == null || source == null) { + return; + } + + final parent = node.parent; + if (parent == null || parent is ConstructorDeclaration) { + return; + } + + if (_isMemberOrClass(node.element, className, source)) { + reporter.atNode(node, _getLintCode(entry)); + } + } + + void _checkIdFromClassFromSource( + SimpleIdentifier node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final identifier = entry.identifier; + final className = entry.className; + final source = entry.source; + if (identifier == null || className == null || source == null) { + return; + } + + if (identifier == _defaultConstructorIdentifier || + node.name != identifier) { + return; + } + + final parent = node.parent; + if (parent == null || parent is ConstructorDeclaration) { + return; + } + + if (_isMemberOrClass(node.element, className, source)) { + reporter.atNode(node, _getLintCode(entry)); + } + } + + void _checkSource( + SimpleIdentifier node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final source = entry.source; + if (source == null || !matchesSource(node.sourceUrl, source)) { + return; + } + + if (node.parent is ConstructorDeclaration) { + return; + } + + reporter.atNode(node, _getLintCode(entry)); + } + + void _checkIdFromSource( + SimpleIdentifier node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final identifier = entry.identifier; + final source = entry.source; + if (identifier == null || source == null) { + return; + } + + if (node.name != identifier) { + return; + } + + if (!matchesSource(node.sourceUrl, source)) { + return; + } + + if (node.parent is ConstructorDeclaration) { + return; + } + + final element = node.element; + if (element is LocalFunctionElement || + element is TopLevelFunctionElement || + element is PropertyAccessorElement) { + reporter.atNode(node, _getLintCode(entry)); + } + } + + void _checkNamedType( + NamedType node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final source = entry.source; + if (source == null) return; + + final className = entry.className; + final identifier = entry.identifier; + + switch ((className, identifier)) { + case (null, null): + // banSource + if (matchesSource(node.sourceUrl, source)) { + reporter.atNode(node, _getLintCode(entry)); + } + case (final String className, null): + // banClassFromSource + if (node.name.lexeme == className && + matchesSource(node.sourceUrl, source)) { + reporter.atNode(node, _getLintCode(entry)); + } + default: + break; + } + } + + void _checkVariableDeclaration( + VariableDeclaration node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final source = entry.source; + if (source == null) return; + + final className = entry.className; + + switch ((className, entry.identifier)) { + case (final String className, null): + // banClassFromSource + final typeElement = node.declaredType?.element; + if (typeElement?.name == className && + matchesSource(typeElement?.libraryUri, source)) { + reporter.atOffset( + offset: node.name.offset, + length: node.name.length, + diagnosticCode: _getLintCode(entry), + ); + } + default: + break; + } + } + + void _checkInstanceCreation( + InstanceCreationExpression node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final source = entry.source; + if (source == null) return; + + final className = entry.className; + final identifier = entry.identifier; + final namedParameter = entry.namedParameter; + + switch ((className, identifier, namedParameter)) { + case ( + final String className, + final String identifier, + final String namedParameter, + ): + // banUsageWithSpecificNamedParameter + String? expectedConstructorName; + if (identifier != _defaultConstructorIdentifier) { + expectedConstructorName = identifier; + } + + final actualClassName = node.constructorName.type.name.lexeme; + if (actualClassName != className) { + return; + } + + if (node.constructorName.name?.name != expectedConstructorName) { + return; + } + + if (!node.argumentList.containsNamed(namedParameter)) { + return; + } + + if (matchesSource( + node.constructorName.type.element?.libraryUri, + source, + )) { + reporter.atNode(node.constructorName.type, _getLintCode(entry)); + } + + case (final String className, _defaultConstructorIdentifier, null): + // banIdFromClassFromSource for default constructor + final constructorName = node.constructorName.type.name.lexeme; + if (constructorName != className || node.constructorName.name != null) { + return; + } + + if (matchesSource( + node.constructorName.type.element?.libraryUri, + source, + )) { + reporter.atNode(node.constructorName.type, _getLintCode(entry)); + } + default: + break; + } + } + + void _checkMethodInvocation( + MethodInvocation node, + AvoidUsingApiEntryParameters entry, + DiagnosticReporter reporter, + ) { + final source = entry.source; + if (source == null) return; + + final className = entry.className; + final identifier = entry.identifier; + final namedParameter = entry.namedParameter; + + final methodName = node.methodName.name; + if (methodName != identifier) return; + + switch ((className, identifier, namedParameter)) { + case (final String className, String _, final String namedParameter): + // banUsageWithSpecificNamedParameter + final enclosingElement = node.methodName.element?.enclosingElement; + if (enclosingElement == null || enclosingElement.name != className) { + return; + } + + if (!node.argumentList.containsNamed(namedParameter)) { + return; + } + + if (matchesSource(enclosingElement.libraryUri, source)) { + reporter.atNode(node.methodName, _getLintCode(entry)); + } + default: + break; + } + } + + bool _isMemberOrClass(Element? element, String className, String source) { + if (element == null) return false; + + final target = element is InterfaceElement + ? element + : element.enclosingElement; + if (target == null) return false; + + if (target is InterfaceElement || target is ExtensionElement) { + return target.name == className && + matchesSource(target.libraryUri, source); + } + + return false; + } +} diff --git a/lib/src/utils/node_utils.dart b/lib/src/utils/node_utils.dart index 7afb28a1..de3fc9dd 100644 --- a/lib/src/utils/node_utils.dart +++ b/lib/src/utils/node_utils.dart @@ -1,6 +1,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; /// Check node is override method from its metadata bool isOverride(List metadata) => metadata.any( @@ -58,6 +59,9 @@ extension SimpleIdentifierExtension on SimpleIdentifier { if (declOffset == null) return false; return declOffset >= body.offset && declOffset < body.end; } + + /// Returns the library URI string of the element, or null. + String? get sourceUrl => element?.libraryUri; } /// Extension on [AstNode] to provide generic context/traversal checks. @@ -103,3 +107,33 @@ extension AstNodeExtension on AstNode { return p is IndexExpression; } } + +/// Extension on [NamedType] to provide source URL utility. +extension NamedTypeExtension on NamedType { + /// Returns the library URI string of the element, or null. + String? get sourceUrl => element?.libraryUri; +} + +/// Extension on [ArgumentList] to check for parameter names. +extension ArgumentListExtension on ArgumentList { + /// Returns `true` if this argument list contains a named parameter argument + /// with the given [name]. + bool containsNamed(String name) => arguments.any( + (arg) => arg is NamedExpression && arg.name.label.name == name, + ); +} + +/// Extension on [Element] to provide library URI utility. +extension ElementLibraryExtension on Element { + /// Returns the library URI string of this element, or null. + String? get libraryUri => library?.uri.toString(); +} + +/// Extension on [VariableDeclaration] to check declared type. +extension VariableDeclarationExtension on VariableDeclaration { + /// Returns the type of the declared variable, or null. + DartType? get declaredType { + final element = declaredFragment?.element; + return element is VariableElement ? element.type : null; + } +} diff --git a/lib/src/utils/path_utils.dart b/lib/src/utils/path_utils.dart index 1973c57d..e6ba3a67 100644 --- a/lib/src/utils/path_utils.dart +++ b/lib/src/utils/path_utils.dart @@ -18,30 +18,37 @@ bool shouldSkipFile({ required String path, String? rootPath, }) { + final includes = includeGlobs.map(Glob.new).toList(); + final excludes = excludeGlobs.map(Glob.new).toList(); + final relative = relativePath(path, rootPath); - final shouldAnalyzeFile = - (includeGlobs.isEmpty || _matchesAnyGlob(includeGlobs, relative)) && - (excludeGlobs.isEmpty || _doesNotMatchGlobs(excludeGlobs, relative)); - return !shouldAnalyzeFile; -} -bool _matchesAnyGlob(List globsList, String path) { - final hasMatch = - globsList.map(Glob.new).toList().any((glob) => glob.matches(path)); - return hasMatch; -} + final matchesInclude = includes.isEmpty || + _matchesAny(includes, relative) || + _matchesAny(includes, path); -bool _doesNotMatchGlobs(List globList, String path) { - return !_matchesAnyGlob(globList, path); + final matchesExclude = excludes.isNotEmpty && + (_matchesAny(excludes, relative) || _matchesAny(excludes, path)); + + return !matchesInclude || matchesExclude; } +bool _matchesAny(List globs, String path) => + globs.any((glob) => glob.matches(path)); + /// Converts path to relative using posix style and /// replaces backslashes with forward slashes String relativePath(String path, [String? root]) { - final uriNormlizedPath = p.toUri(path).normalizePath().path; - final uriNormlizedRoot = + final uriNormalizedPath = p.toUri(path).normalizePath().path; + final uriNormalizedRoot = root != null ? p.toUri(root).normalizePath().path : null; - final relative = p.posix.relative(uriNormlizedPath, from: uriNormlizedRoot); - return relative; + return p.posix.relative(uriNormalizedPath, from: uriNormalizedRoot); } + +/// Checks if the given [libraryUri] matches the [targetSource] string +/// (either exact match or as a directory/package prefix). +/// Returns `false` if [libraryUri] is null. +bool matchesSource(String? libraryUri, String targetSource) => + libraryUri != null && + (libraryUri == targetSource || libraryUri.startsWith('$targetSource/')); diff --git a/lint_test/avoid_using_api/analysis_options.yaml b/lint_test/avoid_using_api/analysis_options.yaml deleted file mode 100644 index 18050911..00000000 --- a/lint_test/avoid_using_api/analysis_options.yaml +++ /dev/null @@ -1,7 +0,0 @@ -analyzer: - plugins: - - ../custom_lint - -custom_lint: - rules: - - avoid_using_api: diff --git a/lint_test/avoid_using_api/class_source_ban/analysis_options.yaml b/lint_test/avoid_using_api/class_source_ban/analysis_options.yaml deleted file mode 100644 index 7eae7de0..00000000 --- a/lint_test/avoid_using_api/class_source_ban/analysis_options.yaml +++ /dev/null @@ -1,15 +0,0 @@ -analyzer: - plugins: - - ../../custom_lint - -custom_lint: - rules: - - avoid_using_api: - severity: warning - entries: - - class_name: BannedCodeUsage - source: package:external_source - reason: "BannedCodeUsage from package:external_source is not allowed" - - class_name: UnmodifiableListView - source: dart:collection - reason: "UnmodifiableListView from dart:collection is not allowed" diff --git a/lint_test/avoid_using_api/class_source_ban/lib/class_source_ban_test.dart b/lint_test/avoid_using_api/class_source_ban/lib/class_source_ban_test.dart deleted file mode 100644 index 261172fb..00000000 --- a/lint_test/avoid_using_api/class_source_ban/lib/class_source_ban_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -// ignore_for_file: unused_local_variable - -import 'dart:collection'; - -import 'package:external_source/external_source.dart'; - -void testingBannedCodeLint() { - // expect_lint: avoid_using_api - final bannedCodeUsage = BannedCodeUsage(); - // expect_lint: avoid_using_api - BannedCodeUsage.test2(); - // expect_lint: avoid_using_api - final res = BannedCodeUsage.test2(); - // expect_lint: avoid_using_api - bannedCodeUsage.test(); - - // expect_lint: avoid_using_api - final bannedCodeUsage2 = BannedCodeUsage.test3(); - // expect_lint: avoid_using_api - BannedCodeUsage.test3().test(); - // expect_lint: avoid_using_api - bannedCodeUsage2.test(); - test2; - // expect_lint: avoid_using_api - bannedCodeUsage2.test4; - - // expect_lint: avoid_using_api - final typed = bannedCodeUsage; - - // expect_lint: avoid_using_api - final unmodifiable = UnmodifiableListView([1, 2, 3]); - - // expect_lint: avoid_using_api - final first = unmodifiable.first; -} - -const test2 = 'Hello World'; diff --git a/lint_test/avoid_using_api/class_source_ban/pubspec.yaml b/lint_test/avoid_using_api/class_source_ban/pubspec.yaml deleted file mode 100644 index e8e30186..00000000 --- a/lint_test/avoid_using_api/class_source_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: class_source_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: ../../../ diff --git a/lint_test/avoid_using_api/external_source/lib/banned_library.dart b/lint_test/avoid_using_api/external_source/lib/banned_library.dart deleted file mode 100644 index d36ffcde..00000000 --- a/lint_test/avoid_using_api/external_source/lib/banned_library.dart +++ /dev/null @@ -1 +0,0 @@ -int banned = 5; diff --git a/lint_test/avoid_using_api/external_source/lib/external_source.dart b/lint_test/avoid_using_api/external_source/lib/external_source.dart deleted file mode 100644 index 144c6bd1..00000000 --- a/lint_test/avoid_using_api/external_source/lib/external_source.dart +++ /dev/null @@ -1,30 +0,0 @@ -// ignore_for_file: prefer_match_file_name -// ignore_for_file: no_empty_block - -class BannedCodeUsage { - final String test4 = 'Hello World'; - - BannedCodeUsage(); - - factory BannedCodeUsage.test3() { - return BannedCodeUsage(); - } - - void test() {} - - static String test2() { - return 'Hello World'; - } -} - -const test2 = 'Hello World'; - -void test() {} - -int banned = 5; - -extension BannedExtension on int { - int banned() => this + 10; - - int get bannedGetter => 10; -} diff --git a/lint_test/avoid_using_api/external_source/pubspec.yaml b/lint_test/avoid_using_api/external_source/pubspec.yaml deleted file mode 100644 index e727de12..00000000 --- a/lint_test/avoid_using_api/external_source/pubspec.yaml +++ /dev/null @@ -1,13 +0,0 @@ -name: external_source -description: A starting point for Dart libraries or applications. -version: 1.0.0 -publish_to: none - -environment: - sdk: ^3.1.3 - -dev_dependencies: - lints: ^3.0.0 - test: ^1.21.0 - solid_lints: - path: ../../../ 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 deleted file mode 100644 index c4a4dcf2..00000000 --- a/lint_test/avoid_using_api/identifier_class_source_ban/analysis_options.yaml +++ /dev/null @@ -1,40 +0,0 @@ -analyzer: - plugins: - - ../../custom_lint - -custom_lint: - rules: - - 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 - reason: "BannedCodeUsage.test4 from package:external_source is not allowed" - - identifier: test3 - class_name: BannedCodeUsage - source: package:external_source - reason: "BannedCodeUsage.test3 from package:external_source is not allowed" - - identifier: test2 - class_name: BannedCodeUsage - source: package:external_source - reason: "BannedCodeUsage.test2 from package:external_source is not allowed" - - identifier: test - class_name: BannedCodeUsage - source: package:external_source - reason: "BannedCodeUsage.test from package:external_source is not allowed" - - class_name: UnmodifiableListView - source: dart:collection - reason: "UnmodifiableListView from dart:collection is not allowed" - - class_name: UnmodifiableListView - identifier: first - source: dart:collection - reason: "UnmodifiableListView from dart:collection is not allowed" - - identifier: wait - class_name: Future - source: dart:async - reason: "Future.wait from dart:async is not allowed" 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 deleted file mode 100644 index 7e8570e6..00000000 --- a/lint_test/avoid_using_api/identifier_class_source_ban/lib/identifier_class_source_ban_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -// ignore_for_file: unused_local_variable - -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(); - // expect_lint: avoid_using_api - final res = BannedCodeUsage.test2(); - // expect_lint: avoid_using_api - bannedCodeUsage.test(); - - // expect_lint: avoid_using_api - final bannedCodeUsage2 = BannedCodeUsage.test3(); - // expect_lint: avoid_using_api - BannedCodeUsage.test3().test(); - // expect_lint: avoid_using_api - bannedCodeUsage2.test(); - test2; - // expect_lint: avoid_using_api - bannedCodeUsage2.test4; - test(); - - // expect_lint: avoid_using_api - final unmodifiable = UnmodifiableListView([1, 2, 3]); - - // expect_lint: avoid_using_api - final first = unmodifiable.first; - - // expect_lint: avoid_using_api - Future.wait([Future.value(1), Future.value(2)]); - - await (Future.value(1), Future.value(2)).wait; -} diff --git a/lint_test/avoid_using_api/identifier_class_source_ban/pubspec.yaml b/lint_test/avoid_using_api/identifier_class_source_ban/pubspec.yaml deleted file mode 100644 index 83756002..00000000 --- a/lint_test/avoid_using_api/identifier_class_source_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: identifier_class_source_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: ../../../ diff --git a/lint_test/avoid_using_api/identifier_extension_source_ban/analysis_options.yaml b/lint_test/avoid_using_api/identifier_extension_source_ban/analysis_options.yaml deleted file mode 100644 index 3a2b7c69..00000000 --- a/lint_test/avoid_using_api/identifier_extension_source_ban/analysis_options.yaml +++ /dev/null @@ -1,18 +0,0 @@ -analyzer: - plugins: - - ../../custom_lint - -custom_lint: - rules: - - avoid_using_api: - severity: warning - entries: - - class_name: BannedExtension - identifier: banned - source: package:external_source - reason: "Banned identifier from BannedExtension from package:external_source is not allowed" - - class_name: BannedExtension - identifier: bannedGetter - source: package:external_source - reason: "bannedGetter identifier from BannedExtension from package:external_source is not allowed" - diff --git a/lint_test/avoid_using_api/identifier_extension_source_ban/lib/identifier_extension_source_ban_test.dart b/lint_test/avoid_using_api/identifier_extension_source_ban/lib/identifier_extension_source_ban_test.dart deleted file mode 100644 index a3c0a8d0..00000000 --- a/lint_test/avoid_using_api/identifier_extension_source_ban/lib/identifier_extension_source_ban_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:external_source/external_source.dart'; - -void extensionIdentifierBanTesting() { - const int a = 10; - - // expect_lint: avoid_using_api - a.banned(); - - // expect_lint: avoid_using_api - a.bannedGetter; -} diff --git a/lint_test/avoid_using_api/identifier_extension_source_ban/pubspec.yaml b/lint_test/avoid_using_api/identifier_extension_source_ban/pubspec.yaml deleted file mode 100644 index 012fa6ca..00000000 --- a/lint_test/avoid_using_api/identifier_extension_source_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: identifier_extension_source_ban -description: A sample command-line application. -version: 1.0.0 -publish_to: none - -environment: - sdk: ^3.7.2 - -dependencies: - external_source: - path: ../external_source - -dev_dependencies: - lints: ^3.0.0 - test: ^1.21.0 - solid_lints: - path: ../../../ diff --git a/lint_test/avoid_using_api/identifier_source_ban/analysis_options.yaml b/lint_test/avoid_using_api/identifier_source_ban/analysis_options.yaml deleted file mode 100644 index 34bd5331..00000000 --- a/lint_test/avoid_using_api/identifier_source_ban/analysis_options.yaml +++ /dev/null @@ -1,12 +0,0 @@ -analyzer: - plugins: - - ../../custom_lint - -custom_lint: - rules: - - avoid_using_api: - severity: warning - entries: - - identifier: test - source: package:external_source - reason: "test methods from package:external_source is not allowed" diff --git a/lint_test/avoid_using_api/identifier_source_ban/lib/identifier_source_ban_test.dart b/lint_test/avoid_using_api/identifier_source_ban/lib/identifier_source_ban_test.dart deleted file mode 100644 index f5e8890b..00000000 --- a/lint_test/avoid_using_api/identifier_source_ban/lib/identifier_source_ban_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:external_source/external_source.dart'; - -void testingBannedCodeLint() { - final bannedCodeUsage = BannedCodeUsage(); - bannedCodeUsage.test(); - - // expect_lint: avoid_using_api - test(); -} diff --git a/lint_test/avoid_using_api/identifier_source_ban/pubspec.yaml b/lint_test/avoid_using_api/identifier_source_ban/pubspec.yaml deleted file mode 100644 index 8cd612da..00000000 --- a/lint_test/avoid_using_api/identifier_source_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: identifier_source_ban -description: A starting point for Dart libraries or applications. -version: 1.0.0 -publish_to: none - -environment: - sdk: ^3.1.4 - -dependencies: - external_source: - path: ../external_source - -dev_dependencies: - lints: ^3.0.0 - test: ^1.21.0 - solid_lints: - path: ../../../ 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 deleted file mode 100644 index 557c6a83..00000000 --- a/lint_test/avoid_using_api/named_parameter_ban/analysis_options.yaml +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index c81889b3..00000000 --- a/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban.dart +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 1a583c72..00000000 --- a/lint_test/avoid_using_api/named_parameter_ban/lib/named_parameter_ban_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index db054efe..00000000 --- a/lint_test/avoid_using_api/named_parameter_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -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: ../../../ diff --git a/lint_test/avoid_using_api/source_ban/analysis_options.yaml b/lint_test/avoid_using_api/source_ban/analysis_options.yaml deleted file mode 100644 index 8cdb4a71..00000000 --- a/lint_test/avoid_using_api/source_ban/analysis_options.yaml +++ /dev/null @@ -1,18 +0,0 @@ -analyzer: - plugins: - - ../../custom_lint - -custom_lint: - rules: - - avoid_using_api: - severity: warning - entries: - - source: dart:collection - reason: "dart:collection should not be used" - # Uses Glob notation, test using https://www.digitalocean.com/community/tools/glob - includes: - - "**/domain/**.dart" - excludes: - - "**/domain/**.ex.dart" - - source: package:external_source/banned_library.dart - reason: "package:external_source/banned_library.dart should not be used anymore" diff --git a/lint_test/avoid_using_api/source_ban/lib/source_ban_test.dart b/lint_test/avoid_using_api/source_ban/lib/source_ban_test.dart deleted file mode 100644 index b05d50c3..00000000 --- a/lint_test/avoid_using_api/source_ban/lib/source_ban_test.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:external_source/banned_library.dart'; - -void testingBannedCodeLint() { - // expect_lint: avoid_using_api - banned; -} diff --git a/lint_test/avoid_using_api/source_ban/lib/src/domain/excludes_test.ex.dart b/lint_test/avoid_using_api/source_ban/lib/src/domain/excludes_test.ex.dart deleted file mode 100644 index 845085d7..00000000 --- a/lint_test/avoid_using_api/source_ban/lib/src/domain/excludes_test.ex.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'dart:collection'; - -void testingBannedCodeLint() { - final unmodifiable = UnmodifiableListView([1, 2, 3]); - unmodifiable.first; -} diff --git a/lint_test/avoid_using_api/source_ban/lib/src/domain/includes_test.dart b/lint_test/avoid_using_api/source_ban/lib/src/domain/includes_test.dart deleted file mode 100644 index 91a0932d..00000000 --- a/lint_test/avoid_using_api/source_ban/lib/src/domain/includes_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'dart:collection'; - -void testingBannedCodeLint() { - // expect_lint: avoid_using_api - final unmodifiable = UnmodifiableListView([1, 2, 3]); - - // expect_lint: avoid_using_api - unmodifiable.first; -} diff --git a/lint_test/avoid_using_api/source_ban/pubspec.yaml b/lint_test/avoid_using_api/source_ban/pubspec.yaml deleted file mode 100644 index a1e1c52c..00000000 --- a/lint_test/avoid_using_api/source_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: source_ban -description: A starting point for Dart libraries or applications. -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: ../../../ diff --git a/lint_test/avoid_using_api/source_excludes_ban/analysis_options.yaml b/lint_test/avoid_using_api/source_excludes_ban/analysis_options.yaml deleted file mode 100644 index 19acd133..00000000 --- a/lint_test/avoid_using_api/source_excludes_ban/analysis_options.yaml +++ /dev/null @@ -1,18 +0,0 @@ -analyzer: - plugins: - - ../../custom_lint - -custom_lint: - rules: - - avoid_using_api: - severity: warning - entries: - - source: dart:collection - reason: "dart:collection should not be used" - # Uses Glob notation, test using https://www.digitalocean.com/community/tools/glob - excludes: - - "**.ex.dart" - - source: package:external_source/banned_library.dart - reason: "package:external_source/banned_library.dart should not be used anymore" - excludes: - - "**.ex.dart" diff --git a/lint_test/avoid_using_api/source_excludes_ban/lib/source_excludes_ban_test.dart b/lint_test/avoid_using_api/source_excludes_ban/lib/source_excludes_ban_test.dart deleted file mode 100644 index b05d50c3..00000000 --- a/lint_test/avoid_using_api/source_excludes_ban/lib/source_excludes_ban_test.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:external_source/banned_library.dart'; - -void testingBannedCodeLint() { - // expect_lint: avoid_using_api - banned; -} diff --git a/lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/excludes_test.ex.dart b/lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/excludes_test.ex.dart deleted file mode 100644 index 845085d7..00000000 --- a/lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/excludes_test.ex.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'dart:collection'; - -void testingBannedCodeLint() { - final unmodifiable = UnmodifiableListView([1, 2, 3]); - unmodifiable.first; -} diff --git a/lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/includes_test.dart b/lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/includes_test.dart deleted file mode 100644 index 91a0932d..00000000 --- a/lint_test/avoid_using_api/source_excludes_ban/lib/src/domain/includes_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'dart:collection'; - -void testingBannedCodeLint() { - // expect_lint: avoid_using_api - final unmodifiable = UnmodifiableListView([1, 2, 3]); - - // expect_lint: avoid_using_api - unmodifiable.first; -} diff --git a/lint_test/avoid_using_api/source_excludes_ban/pubspec.yaml b/lint_test/avoid_using_api/source_excludes_ban/pubspec.yaml deleted file mode 100644 index 8ad20af5..00000000 --- a/lint_test/avoid_using_api/source_excludes_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: source_excludes_ban -description: A starting point for Dart libraries or applications. -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: ../../../ diff --git a/lint_test/avoid_using_api/source_includes_ban/analysis_options.yaml b/lint_test/avoid_using_api/source_includes_ban/analysis_options.yaml deleted file mode 100644 index 1c4cb4de..00000000 --- a/lint_test/avoid_using_api/source_includes_ban/analysis_options.yaml +++ /dev/null @@ -1,22 +0,0 @@ -analyzer: - plugins: - - ../../custom_lint - -custom_lint: - rules: - - avoid_using_api: - severity: warning - entries: - - source: dart:collection - reason: "dart:collection should not be used" - # Uses Glob notation, test using https://www.digitalocean.com/community/tools/glob - includes: - - "**/domain/**.dart" - excludes: - - "**.ex.dart" - - source: package:external_source/banned_library.dart - reason: "package:external_source/banned_library.dart should not be used anymore" - includes: - - "**/domain/**.dart" - excludes: - - "**.ex.dart" diff --git a/lint_test/avoid_using_api/source_includes_ban/lib/source_includes_ban_test.dart b/lint_test/avoid_using_api/source_includes_ban/lib/source_includes_ban_test.dart deleted file mode 100644 index e2031dff..00000000 --- a/lint_test/avoid_using_api/source_includes_ban/lib/source_includes_ban_test.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:external_source/banned_library.dart'; - -void testingBannedCodeLint() { - banned; -} diff --git a/lint_test/avoid_using_api/source_includes_ban/lib/src/domain/excludes_test.ex.dart b/lint_test/avoid_using_api/source_includes_ban/lib/src/domain/excludes_test.ex.dart deleted file mode 100644 index bd2b4a82..00000000 --- a/lint_test/avoid_using_api/source_includes_ban/lib/src/domain/excludes_test.ex.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'dart:collection'; - -import 'package:external_source/banned_library.dart'; - -void testingBannedCodeLint() { - final unmodifiable = UnmodifiableListView([1, 2, 3]); - unmodifiable.first; - banned; -} diff --git a/lint_test/avoid_using_api/source_includes_ban/lib/src/domain/includes_test.dart b/lint_test/avoid_using_api/source_includes_ban/lib/src/domain/includes_test.dart deleted file mode 100644 index 0308e417..00000000 --- a/lint_test/avoid_using_api/source_includes_ban/lib/src/domain/includes_test.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:collection'; - -import 'package:external_source/banned_library.dart'; - -void testingBannedCodeLint() { - // expect_lint: avoid_using_api - final unmodifiable = UnmodifiableListView([1, 2, 3]); - - // expect_lint: avoid_using_api - unmodifiable.first; - - // expect_lint: avoid_using_api - banned; -} diff --git a/lint_test/avoid_using_api/source_includes_ban/pubspec.yaml b/lint_test/avoid_using_api/source_includes_ban/pubspec.yaml deleted file mode 100644 index a531643c..00000000 --- a/lint_test/avoid_using_api/source_includes_ban/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: source_includes_ban -description: A starting point for Dart libraries or applications. -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: ../../../ diff --git a/test/lints/auto_lint_data.dart b/test/lints/auto_lint_data.dart new file mode 100644 index 00000000..61b900dc --- /dev/null +++ b/test/lints/auto_lint_data.dart @@ -0,0 +1,19 @@ +import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart'; + +/// Holds data about a lint placeholder, including the original code +/// and optional diagnostic assertions. +class AutoLintData { + final String code; + final Pattern? correctionContains; + final List messageContainsAll; + final String? name; + final List? contextMessages; + + const AutoLintData({ + required this.code, + this.correctionContains, + this.messageContainsAll = const [], + this.name, + this.contextMessages, + }); +} diff --git a/test/lints/auto_test_lint_offsets.dart b/test/lints/auto_test_lint_offsets.dart index a9b4b298..e026f382 100644 --- a/test/lints/auto_test_lint_offsets.dart +++ b/test/lints/auto_test_lint_offsets.dart @@ -2,17 +2,19 @@ import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart'; import 'package:collection/collection.dart'; +import 'auto_lint_data.dart'; + mixin AutoTestLintOffsets on AnalysisRuleTest { int _nextPlaceholderId = 0; - final Map _placeholderToCode = {}; + final Map _placeholderToData = {}; Future assertAutoDiagnostics(String source) async { try { - final placeholders = _placeholderToCode.entries + final placeholders = _placeholderToData.entries .map( (entry) => ( placeholder: entry.key, - code: entry.value, + data: entry.value, index: source.indexOf(entry.key), ), ) @@ -29,27 +31,46 @@ mixin AutoTestLintOffsets on AnalysisRuleTest { } expectedDiagnostics.add( - lint(match.index + replacedPlaceholdersDelta, match.code.length), + lint( + match.index + replacedPlaceholdersDelta, + match.data.code.length, + correctionContains: match.data.correctionContains, + messageContainsAll: match.data.messageContainsAll, + name: match.data.name, + contextMessages: match.data.contextMessages, + ), ); replacedPlaceholdersDelta += - match.code.length - match.placeholder.length; + match.data.code.length - match.placeholder.length; } - final resolvedSource = _placeholderToCode.entries.fold( + final resolvedSource = _placeholderToData.entries.fold( source, - (resolved, entry) => resolved.replaceFirst(entry.key, entry.value), + (resolved, entry) => resolved.replaceFirst(entry.key, entry.value.code), ); await assertDiagnostics(resolvedSource, expectedDiagnostics); } finally { _nextPlaceholderId = 0; - _placeholderToCode.clear(); + _placeholderToData.clear(); } } - String expectLint(String code) { + String expectLint( + String code, { + Pattern? correctionContains, + List messageContainsAll = const [], + String? name, + List? contextMessages, + }) { final placeholder = '__AUTO_TEST_LINT_${_nextPlaceholderId++}__'; - _placeholderToCode[placeholder] = code; + _placeholderToData[placeholder] = AutoLintData( + code: code, + correctionContains: correctionContains, + messageContainsAll: messageContainsAll, + name: name, + contextMessages: contextMessages, + ); return placeholder; } } diff --git a/test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart b/test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart new file mode 100644 index 00000000..5efb076c --- /dev/null +++ b/test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart @@ -0,0 +1,359 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:analyzer_testing/utilities/utilities.dart'; +import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; +import 'package:solid_lints/src/lints/avoid_using_api/avoid_using_api_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../lints/auto_test_lint_offsets.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidUsingApiRuleTest); + }); +} + +@reflectiveTest +class AvoidUsingApiRuleTest extends AnalysisRuleTest with AutoTestLintOffsets { + static const _mockMyDepContent = ''' +class BadClass { + const BadClass({int? badParam}); + const BadClass.named({int? badParam}); + void badMethod({int? badParam}) {} +} +class Holder { + const Holder(); + BadClass get badClass => const BadClass(); +} +int get badGlobal => 42; +void badFunction() {} +'''; + + @override + void setUp() { + rule = AvoidUsingApiRule( + analysisOptionsLoader: AnalysisOptionsLoader( + resourceProvider: resourceProvider, + ), + ); + newPackage('my_dep') + ..addFile('lib/my_dep.dart', _mockMyDepContent); + newPackage('other_dep') + ..addFile('lib/other_dep.dart', 'class BadClass {}'); + super.setUp(); + } + + void _configureRule(String entriesYaml) { + newAnalysisOptionsYamlFile(testPackageRootPath, ''' +${analysisOptionsContent(rules: [rule.name])} +plugins: + solid_lints: + diagnostics: + avoid_using_api: + entries: +$entriesYaml'''); + } + + Future test_reports_ban_source() { + _configureRule(''' + - source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final a = ${expectLint('BadClass')}(); + final b = ${expectLint('badGlobal')}; + ${expectLint('badFunction')}(); +} +'''); + } + + Future test_reports_ban_class_from_source() { + _configureRule(''' + - class_name: BadClass + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + ${expectLint('BadClass')} ${expectLint('a')} = ${expectLint('BadClass')}(); + final b = badGlobal; +} +'''); + } + + Future test_reports_ban_id_from_source() { + _configureRule(''' + - identifier: badGlobal + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final a = BadClass(); + final b = ${expectLint('badGlobal')}; + badFunction(); +} +'''); + } + + Future test_reports_ban_id_from_class_from_source() { + _configureRule(''' + - class_name: BadClass + identifier: badMethod + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final a = BadClass(); + a.${expectLint('badMethod')}(); +} +'''); + } + + Future test_reports_ban_default_constructor() { + _configureRule(''' + - class_name: BadClass + identifier: "()" + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final a = ${expectLint('BadClass')}(); + final b = BadClass.named(); +} +'''); + } + + Future test_reports_ban_usage_with_specific_named_parameter() { + _configureRule(''' + - class_name: BadClass + identifier: badMethod + named_parameter: badParam + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final a = BadClass(); + a.${expectLint('badMethod')}(badParam: 123); + a.badMethod(); // OK +} +'''); + } + + Future test_respects_exclude_patterns() { + _configureRule(''' + - class_name: BadClass + source: package:my_dep/my_dep.dart + excludes: + - "lib/src/**" +'''); + + // Create a file in a folder matching the exclude pattern + final excludedFilePath = '$testPackageRootPath/lib/src/excluded_test.dart'; + newFile(excludedFilePath, ''' +import 'package:my_dep/my_dep.dart'; +void fn() { + BadClass a = BadClass(); +} +'''); + + // Verify no diagnostics in the excluded file + return assertNoDiagnosticsInFile(excludedFilePath); + } + + Future test_reports_ban_id_from_class_from_source_chained() { + _configureRule(''' + - class_name: BadClass + identifier: badMethod + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final h = const Holder(); + h.badClass.${expectLint('badMethod')}(); +} +'''); + } + + Future test_reports_ban_class_from_source_chained() { + _configureRule(''' + - class_name: BadClass + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final h = const Holder(); + h.badClass.${expectLint('badMethod')}(); +} +'''); + } + + Future test_reports_ban_source_with_custom_reason() { + _configureRule(''' + - source: package:my_dep/my_dep.dart + reason: "Custom warning message" +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final a = ${expectLint('BadClass', messageContainsAll: ["Custom warning message"])}(); +} +'''); + } + + Future test_does_not_report_same_names_from_other_source() { + _configureRule(''' + - class_name: BadClass + source: package:my_dep/my_dep.dart +'''); + + return assertNoDiagnostics(''' +import 'package:other_dep/other_dep.dart'; + +void fn() { + final a = BadClass(); // OK +} +'''); + } + + Future test_reports_ban_extension_method() { + newPackage('my_dep') + ..addFile('lib/my_dep.dart', ''' +extension BadExtension on String { + void badExtMethod() {} +} +'''); + + _configureRule(''' + - class_name: BadExtension + identifier: badExtMethod + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + "".${expectLint('badExtMethod')}(); +} +'''); + } + + Future test_reports_ban_static_method() { + newPackage('my_dep') + ..addFile('lib/my_dep.dart', ''' +class BadClass { + static void staticBadMethod() {} +} +'''); + + _configureRule(''' + - class_name: BadClass + identifier: staticBadMethod + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + BadClass.${expectLint('staticBadMethod')}(); +} +'''); + } + + Future test_respects_include_patterns() { + _configureRule(''' + - class_name: BadClass + source: package:my_dep/my_dep.dart + includes: + - "**/test.dart" +'''); + + // Create a file not matching the include pattern + final otherFilePath = '$testPackageRootPath/lib/other.dart'; + newFile(otherFilePath, ''' +import 'package:my_dep/my_dep.dart'; +void fn() { + final a = BadClass(); +} +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; +void fn() { + final ${expectLint('a')} = ${expectLint('BadClass')}(); +} +''').then((_) => assertNoDiagnosticsInFile(otherFilePath)); + } + + Future test_reports_ban_usage_with_specific_named_parameter_in_constructors() { + _configureRule(''' + - class_name: BadClass + identifier: "()" + named_parameter: badParam + source: package:my_dep/my_dep.dart + - class_name: BadClass + identifier: named + named_parameter: badParam + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final a = ${expectLint('BadClass')}(badParam: 123); + final b = BadClass(); // OK + final c = ${expectLint('BadClass')}.named(badParam: 123); + final d = BadClass.named(); // OK +} +'''); + } + + Future test_reports_ban_extension_getter() { + newPackage('my_dep') + ..addFile('lib/my_dep.dart', ''' +extension BadExtension on String { + String get badExtGetter => ''; +} +'''); + + _configureRule(''' + - class_name: BadExtension + identifier: badExtGetter + source: package:my_dep/my_dep.dart +'''); + + return assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final val = "".${expectLint('badExtGetter')}; +} +'''); + } +} From 82960ebe444214dfc76d3131cc4ca0a9f1bce34c Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 30 Jun 2026 19:26:08 +0300 Subject: [PATCH 2/6] refactor: update rootPath parameter to nullable in avoid_using_api_visitor --- .../avoid_using_api/visitors/avoid_using_api_visitor.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart index d22fe2f2..01581e2c 100644 --- a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart +++ b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart @@ -29,7 +29,7 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { List _getActiveEntries( String filePath, - String rootPath, + String? rootPath, ) { return parameters.entries.where((entry) { return !shouldSkipFile( @@ -53,7 +53,7 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { final activeEntries = _cachedActiveEntries ??= _getActiveEntries( currentUnit.file.path, - context.package?.root.path ?? '', + context.package?.root.path, ); return ( From 2dccbebfb67689d2edbc4c2f15a81102a0462290 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Wed, 1 Jul 2026 14:36:23 +0300 Subject: [PATCH 3/6] fix: upgrade avoid_using_api to support per-entry severity levels and multiple lint codes --- .../avoid_using_api/avoid_using_api_rule.dart | 60 +++++++++++++++--- .../avoid_using_api_entry_parameters.dart | 29 +++++---- .../models/avoid_using_api_parameters.dart | 51 ++++++++------- .../visitors/avoid_using_api_visitor.dart | 17 +++-- .../avoid_using_api_rule_test.dart | 62 +++++++++++++++---- 5 files changed, 156 insertions(+), 63 deletions(-) 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 8c459acb..2a03131e 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 @@ -3,7 +3,7 @@ import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; import 'package:analyzer/error/error.dart'; import 'package:solid_lints/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart'; import 'package:solid_lints/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; +import 'package:solid_lints/src/models/solid_multi_lint_rule.dart'; /// A `avoid_using_api` rule which warns about usage of avoided APIs. /// @@ -17,15 +17,18 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// solid_lints: /// diagnostics: /// avoid_using_api: +/// avoid_using_api: true +/// severity: warning /// entries: /// - class_name: LegacyClient /// source: package:legacy_api/legacy_api.dart /// reason: 'Use ModernClient instead.' +/// severity: error /// - identifier: print /// source: dart:core /// reason: 'Use logging framework instead.' /// ``` -class AvoidUsingApiRule extends SolidLintRule { +class AvoidUsingApiRule extends SolidMultiLintRule { /// This lint name. static const String lintName = 'avoid_using_api'; @@ -33,22 +36,61 @@ class AvoidUsingApiRule extends SolidLintRule { static const String defaultMessage = 'Usage of this code has been discouraged by your project.'; - static const _code = LintCode( + /// Unique name for info level diagnostic codes. + static const infoUniqueName = 'info'; + + /// Unique name for warning level diagnostic codes. + static const warningUniqueName = 'warning'; + + /// Unique name for error level diagnostic codes. + static const errorUniqueName = 'error'; + + /// Returns the unique name corresponding to the [severity]. + static String getUniqueName(DiagnosticSeverity severity) => + switch (severity) { + DiagnosticSeverity.WARNING => warningUniqueName, + DiagnosticSeverity.ERROR => errorUniqueName, + _ => infoUniqueName, + }; + + /// The lint code for info level diagnostics. + static const infoCode = LintCode( + lintName, + defaultMessage, + uniqueName: infoUniqueName, + ); + + /// The lint code for warning level diagnostics. + static const warningCode = LintCode( + lintName, + defaultMessage, + severity: DiagnosticSeverity.WARNING, + uniqueName: warningUniqueName, + ); + + /// The lint code for error level diagnostics. + static const errorCode = LintCode( lintName, defaultMessage, + severity: DiagnosticSeverity.ERROR, + uniqueName: errorUniqueName, ); @override - DiagnosticCode get diagnosticCode => _code; + List get diagnosticCodes => [ + infoCode, + warningCode, + errorCode, + ]; /// Creates a new instance of [AvoidUsingApiRule]. AvoidUsingApiRule({ required super.analysisOptionsLoader, - }) : super.withParameters( - name: lintName, - description: 'Avoid using specific APIs.', - parametersParser: AvoidUsingApiParameters.fromJson, - ); + }) : super( + parametersParser: AvoidUsingApiParameters.fromJson, + name: lintName, + description: 'Avoid using specific APIs.', + ); @override void registerNodeProcessors( 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 7093cf5a..03337134 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 @@ -72,19 +72,18 @@ class AvoidUsingApiEntryParameters { /// Method for creating from json data factory AvoidUsingApiEntryParameters.fromJson( Map json, - ) => - 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?), - reason: json['reason'] as String?, - includes: (json['includes'] as List? ?? []) - .whereType() - .toList(), - excludes: (json['excludes'] as List? ?? []) - .whereType() - .toList(), - ); + ) => 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?), + reason: json['reason'] as String?, + includes: (json['includes'] as List? ?? []) + .whereType() + .toList(), + excludes: (json['excludes'] as List? ?? []) + .whereType() + .toList(), + ); } diff --git a/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart b/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart index 1ce252fb..6494fcaf 100644 --- a/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart +++ b/lib/src/lints/avoid_using_api/models/avoid_using_api_parameters.dart @@ -9,20 +9,20 @@ import 'package:yaml/yaml.dart'; /// /// Parameters: /// * entries: A list of BannedCodeOption parameters. -/// * severity: The default severity of the lint for each entry. /// /// Example: /// ```yaml -/// custom_lint: -/// rules: -/// - avoid_using_api: -/// severity: error -/// entries: -/// - identifier: wait -/// class_name: Future -/// source: dart:async -/// reason: "Future.wait from dart:async isnt allowed" -/// severity: warning +/// plugins: +/// solid_lints: +/// diagnostics: +/// avoid_using_api: +/// avoid_using_api: error +/// entries: +/// - identifier: wait +/// class_name: Future +/// source: dart:async +/// reason: "Future.wait from dart:async isnt allowed" +/// severity: warning /// ``` class AvoidUsingApiParameters { /// A list of BannedCodeOption parameters. @@ -43,16 +43,21 @@ class AvoidUsingApiParameters { /// Method for creating from json data factory AvoidUsingApiParameters.fromJson( Map json, - ) => - AvoidUsingApiParameters( - entries: List.from( - (json['entries'] as Iterable?)?.map( - (e) => AvoidUsingApiEntryParameters.fromJson( - (e as YamlMap).toMap(), - ), - ) ?? - [], - ), - severity: decodeErrorSeverity(json['severity'] as String?), - ); + ) { + final avoidUsingApi = json['avoid_using_api']; + return AvoidUsingApiParameters( + entries: List.from( + (json['entries'] as Iterable?)?.map( + (e) => AvoidUsingApiEntryParameters.fromJson( + (e as YamlMap).toMap(), + ), + ) ?? + [], + ), + severity: decodeErrorSeverity( + json['severity'] as String? ?? + (avoidUsingApi is String ? avoidUsingApi : null), + ), + ); + } } diff --git a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart index 01581e2c..cd0b4579 100644 --- a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart +++ b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart @@ -62,11 +62,18 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { ); } - LintCode _getLintCode(AvoidUsingApiEntryParameters entry) => LintCode( - AvoidUsingApiRule.lintName, - entry.reason ?? AvoidUsingApiRule.defaultMessage, - severity: entry.severity ?? parameters.severity ?? DiagnosticSeverity.INFO, - ); + LintCode _getLintCode(AvoidUsingApiEntryParameters entry) { + final severity = + entry.severity ?? parameters.severity ?? DiagnosticSeverity.INFO; + final message = entry.reason ?? AvoidUsingApiRule.defaultMessage; + + return LintCode( + AvoidUsingApiRule.lintName, + message, + severity: severity, + uniqueName: AvoidUsingApiRule.getUniqueName(severity), + ); + } @override void visitSimpleIdentifier(SimpleIdentifier node) { diff --git a/test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart b/test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart index 5efb076c..09b070b3 100644 --- a/test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart +++ b/test/src/lints/avoid_using_api/avoid_using_api_rule_test.dart @@ -2,6 +2,7 @@ import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:analyzer_testing/utilities/utilities.dart'; import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; import 'package:solid_lints/src/lints/avoid_using_api/avoid_using_api_rule.dart'; +import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import '../../../lints/auto_test_lint_offsets.dart'; @@ -35,10 +36,8 @@ void badFunction() {} resourceProvider: resourceProvider, ), ); - newPackage('my_dep') - ..addFile('lib/my_dep.dart', _mockMyDepContent); - newPackage('other_dep') - ..addFile('lib/other_dep.dart', 'class BadClass {}'); + newPackage('my_dep')..addFile('lib/my_dep.dart', _mockMyDepContent); + newPackage('other_dep')..addFile('lib/other_dep.dart', 'class BadClass {}'); super.setUp(); } @@ -240,8 +239,7 @@ void fn() { } Future test_reports_ban_extension_method() { - newPackage('my_dep') - ..addFile('lib/my_dep.dart', ''' + newPackage('my_dep')..addFile('lib/my_dep.dart', ''' extension BadExtension on String { void badExtMethod() {} } @@ -263,8 +261,7 @@ void fn() { } Future test_reports_ban_static_method() { - newPackage('my_dep') - ..addFile('lib/my_dep.dart', ''' + newPackage('my_dep')..addFile('lib/my_dep.dart', ''' class BadClass { static void staticBadMethod() {} } @@ -310,7 +307,8 @@ void fn() { ''').then((_) => assertNoDiagnosticsInFile(otherFilePath)); } - Future test_reports_ban_usage_with_specific_named_parameter_in_constructors() { + Future + test_reports_ban_usage_with_specific_named_parameter_in_constructors() { _configureRule(''' - class_name: BadClass identifier: "()" @@ -335,8 +333,7 @@ void fn() { } Future test_reports_ban_extension_getter() { - newPackage('my_dep') - ..addFile('lib/my_dep.dart', ''' + newPackage('my_dep')..addFile('lib/my_dep.dart', ''' extension BadExtension on String { String get badExtGetter => ''; } @@ -356,4 +353,47 @@ void fn() { } '''); } + + Future test_reports_ban_with_custom_severities() async { + newAnalysisOptionsYamlFile(testPackageRootPath, ''' +${analysisOptionsContent(rules: [rule.name])} +plugins: + solid_lints: + diagnostics: + avoid_using_api: + avoid_using_api: true + severity: warning + entries: + - class_name: BadClass + source: package:my_dep/my_dep.dart + severity: error + - identifier: badGlobal + source: package:my_dep/my_dep.dart + severity: info + - identifier: badFunction + source: package:my_dep/my_dep.dart +'''); + + await assertAutoDiagnostics(''' +import 'package:my_dep/my_dep.dart'; + +void fn() { + final ${expectLint('a')} = ${expectLint('BadClass')}(); + final b = ${expectLint('badGlobal')}; + ${expectLint('badFunction')}(); +} +'''); + + final diagnostics = result.diagnostics + .where((d) => d.diagnosticCode.lowerCaseName == 'avoid_using_api') + .toList(); + + diagnostics.sort((first, second) => first.offset.compareTo(second.offset)); + + expect(diagnostics, hasLength(4)); + expect(diagnostics[0].severity.name, 'error'); + expect(diagnostics[1].severity.name, 'error'); + expect(diagnostics[2].severity.name, 'info'); + expect(diagnostics[3].severity.name, 'warning'); + } } From 3f4e2fdc75c2cef2ff9a6093c50a9da292c06d24 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Wed, 1 Jul 2026 14:56:13 +0300 Subject: [PATCH 4/6] test: implement getRuleOptionsForFile in FakeAnalysisOptionsLoader --- test/utils/fake_analysis_options_loader.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/utils/fake_analysis_options_loader.dart b/test/utils/fake_analysis_options_loader.dart index c8683879..777abb87 100644 --- a/test/utils/fake_analysis_options_loader.dart +++ b/test/utils/fake_analysis_options_loader.dart @@ -10,6 +10,13 @@ class FakeAnalysisOptionsLoader implements AnalysisOptionsLoader { Map? getRuleOptions(RuleContext context, String ruleName) => ruleOptions; + @override + Map? getRuleOptionsForFile( + String filePath, + String ruleName, + ) => + ruleOptions; + @override void loadRulesOptionsFromContext(RuleContext context) {} } From f818fe5b2370e999821104aba6c2b9f35f28cedd Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Wed, 1 Jul 2026 17:07:30 +0300 Subject: [PATCH 5/6] refactor: simplify logic and reduce nesting in AvoidUsingApiVisitor methods --- .../visitors/avoid_using_api_visitor.dart | 243 +++++++----------- 1 file changed, 87 insertions(+), 156 deletions(-) diff --git a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart index cd0b4579..5cbd00f7 100644 --- a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart +++ b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart @@ -21,6 +21,8 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { /// The rule context. final RuleContext context; + List? _cachedActiveEntries; + /// Creates a new instance of [AvoidUsingApiVisitor]. AvoidUsingApiVisitor({ required this.parameters, @@ -41,8 +43,6 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { }).toList(); } - List? _cachedActiveEntries; - ({ List activeEntries, DiagnosticReporter reporter, @@ -165,18 +165,16 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { ) { final className = entry.className; final source = entry.source; - if (className == null || source == null) { - return; - } - final parent = node.parent; - if (parent == null || parent is ConstructorDeclaration) { + if (className == null || + source == null || + parent == null || + parent is ConstructorDeclaration || + !_isMemberOrClass(node.element, className, source)) { return; } - if (_isMemberOrClass(node.element, className, source)) { - reporter.atNode(node, _getLintCode(entry)); - } + reporter.atNode(node, _getLintCode(entry)); } void _checkIdFromClassFromSource( @@ -187,23 +185,19 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { final identifier = entry.identifier; final className = entry.className; final source = entry.source; - if (identifier == null || className == null || source == null) { - return; - } - - if (identifier == _defaultConstructorIdentifier || - node.name != identifier) { - return; - } - final parent = node.parent; - if (parent == null || parent is ConstructorDeclaration) { + if (identifier == null || + className == null || + source == null || + identifier == _defaultConstructorIdentifier || + node.name != identifier || + parent == null || + parent is ConstructorDeclaration || + !_isMemberOrClass(node.element, className, source)) { return; } - if (_isMemberOrClass(node.element, className, source)) { - reporter.atNode(node, _getLintCode(entry)); - } + reporter.atNode(node, _getLintCode(entry)); } void _checkSource( @@ -212,11 +206,9 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { DiagnosticReporter reporter, ) { final source = entry.source; - if (source == null || !matchesSource(node.sourceUrl, source)) { - return; - } - - if (node.parent is ConstructorDeclaration) { + if (source == null || + !matchesSource(node.sourceUrl, source) || + node.parent is ConstructorDeclaration) { return; } @@ -230,26 +222,18 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { ) { final identifier = entry.identifier; final source = entry.source; - if (identifier == null || source == null) { - return; - } - - if (node.name != identifier) { - return; - } - - if (!matchesSource(node.sourceUrl, source)) { - return; - } - - if (node.parent is ConstructorDeclaration) { + if (identifier == null || + source == null || + node.name != identifier || + !matchesSource(node.sourceUrl, source) || + node.parent is ConstructorDeclaration) { return; } - final element = node.element; - if (element is LocalFunctionElement || - element is TopLevelFunctionElement || - element is PropertyAccessorElement) { + if (node.element + case LocalFunctionElement() || + TopLevelFunctionElement() || + PropertyAccessorElement()) { reporter.atNode(node, _getLintCode(entry)); } } @@ -260,26 +244,14 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { DiagnosticReporter reporter, ) { final source = entry.source; - if (source == null) return; - - final className = entry.className; - final identifier = entry.identifier; - - switch ((className, identifier)) { - case (null, null): - // banSource - if (matchesSource(node.sourceUrl, source)) { - reporter.atNode(node, _getLintCode(entry)); - } - case (final String className, null): - // banClassFromSource - if (node.name.lexeme == className && - matchesSource(node.sourceUrl, source)) { - reporter.atNode(node, _getLintCode(entry)); - } - default: - break; + if (source == null || + entry.identifier != null || + !matchesSource(node.sourceUrl, source) || + (entry.className != null && node.name.lexeme != entry.className)) { + return; } + + reporter.atNode(node, _getLintCode(entry)); } void _checkVariableDeclaration( @@ -288,25 +260,21 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { DiagnosticReporter reporter, ) { final source = entry.source; - if (source == null) return; - final className = entry.className; - - switch ((className, entry.identifier)) { - case (final String className, null): - // banClassFromSource - final typeElement = node.declaredType?.element; - if (typeElement?.name == className && - matchesSource(typeElement?.libraryUri, source)) { - reporter.atOffset( - offset: node.name.offset, - length: node.name.length, - diagnosticCode: _getLintCode(entry), - ); - } - default: - break; + final typeElement = node.declaredType?.element; + if (source == null || + entry.identifier != null || + className == null || + typeElement?.name != className || + !matchesSource(typeElement?.libraryUri, source)) { + return; } + + reporter.atOffset( + offset: node.name.offset, + length: node.name.length, + diagnosticCode: _getLintCode(entry), + ); } void _checkInstanceCreation( @@ -315,59 +283,35 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { DiagnosticReporter reporter, ) { final source = entry.source; - if (source == null) return; - final className = entry.className; - final identifier = entry.identifier; - final namedParameter = entry.namedParameter; - - switch ((className, identifier, namedParameter)) { - case ( - final String className, - final String identifier, - final String namedParameter, - ): - // banUsageWithSpecificNamedParameter - String? expectedConstructorName; - if (identifier != _defaultConstructorIdentifier) { - expectedConstructorName = identifier; - } - - final actualClassName = node.constructorName.type.name.lexeme; - if (actualClassName != className) { - return; - } - - if (node.constructorName.name?.name != expectedConstructorName) { - return; - } - - if (!node.argumentList.containsNamed(namedParameter)) { - return; - } - - if (matchesSource( + if (source == null || + className == null || + node.constructorName.type.name.lexeme != className || + !matchesSource( node.constructorName.type.element?.libraryUri, source, )) { - reporter.atNode(node.constructorName.type, _getLintCode(entry)); - } + return; + } - case (final String className, _defaultConstructorIdentifier, null): - // banIdFromClassFromSource for default constructor - final constructorName = node.constructorName.type.name.lexeme; - if (constructorName != className || node.constructorName.name != null) { - return; - } + final identifier = entry.identifier; + final namedParameter = entry.namedParameter; - if (matchesSource( - node.constructorName.type.element?.libraryUri, - source, - )) { - reporter.atNode(node.constructorName.type, _getLintCode(entry)); - } - default: - break; + // Case 1: banUsageWithSpecificNamedParameter + if (identifier != null && namedParameter != null) { + final expectedConstructorName = + identifier != _defaultConstructorIdentifier ? identifier : null; + if (node.constructorName.name?.name == expectedConstructorName && + node.argumentList.containsNamed(namedParameter)) { + reporter.atNode(node.constructorName.type, _getLintCode(entry)); + } + } + // Case 2: banIdFromClassFromSource for default constructor + else if (identifier == _defaultConstructorIdentifier && + namedParameter == null) { + if (node.constructorName.name == null) { + reporter.atNode(node.constructorName.type, _getLintCode(entry)); + } } } @@ -377,44 +321,31 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { DiagnosticReporter reporter, ) { final source = entry.source; - if (source == null) return; - final className = entry.className; - final identifier = entry.identifier; final namedParameter = entry.namedParameter; + if (source == null || + className == null || + namedParameter == null || + node.methodName.name != entry.identifier) { + return; + } - final methodName = node.methodName.name; - if (methodName != identifier) return; - - switch ((className, identifier, namedParameter)) { - case (final String className, String _, final String namedParameter): - // banUsageWithSpecificNamedParameter - final enclosingElement = node.methodName.element?.enclosingElement; - if (enclosingElement == null || enclosingElement.name != className) { - return; - } - - if (!node.argumentList.containsNamed(namedParameter)) { - return; - } - - if (matchesSource(enclosingElement.libraryUri, source)) { - reporter.atNode(node.methodName, _getLintCode(entry)); - } - default: - break; + final enclosingElement = node.methodName.element?.enclosingElement; + if (enclosingElement != null && + enclosingElement.name == className && + node.argumentList.containsNamed(namedParameter) && + matchesSource(enclosingElement.libraryUri, source)) { + reporter.atNode(node.methodName, _getLintCode(entry)); } } bool _isMemberOrClass(Element? element, String className, String source) { - if (element == null) return false; - final target = element is InterfaceElement ? element - : element.enclosingElement; - if (target == null) return false; + : element?.enclosingElement; - if (target is InterfaceElement || target is ExtensionElement) { + if (target case InterfaceElement() || ExtensionElement() + when target != null) { return target.name == className && matchesSource(target.libraryUri, source); } From a1ea90802df68c52177d2e002c5e4ae787a70f95 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Wed, 1 Jul 2026 17:40:47 +0300 Subject: [PATCH 6/6] refactor: replace conditional logic with switch expression in AvoidUsingApiVisitor to improve readability and structure --- .../visitors/avoid_using_api_visitor.dart | 63 +++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart index 5cbd00f7..4959802f 100644 --- a/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart +++ b/lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart @@ -283,35 +283,48 @@ class AvoidUsingApiVisitor extends SimpleAstVisitor { DiagnosticReporter reporter, ) { final source = entry.source; - final className = entry.className; - if (source == null || - className == null || - node.constructorName.type.name.lexeme != className || - !matchesSource( - node.constructorName.type.element?.libraryUri, - source, - )) { - return; - } + if (source == null) return; + final className = entry.className; final identifier = entry.identifier; final namedParameter = entry.namedParameter; - // Case 1: banUsageWithSpecificNamedParameter - if (identifier != null && namedParameter != null) { - final expectedConstructorName = - identifier != _defaultConstructorIdentifier ? identifier : null; - if (node.constructorName.name?.name == expectedConstructorName && - node.argumentList.containsNamed(namedParameter)) { - reporter.atNode(node.constructorName.type, _getLintCode(entry)); - } - } - // Case 2: banIdFromClassFromSource for default constructor - else if (identifier == _defaultConstructorIdentifier && - namedParameter == null) { - if (node.constructorName.name == null) { - reporter.atNode(node.constructorName.type, _getLintCode(entry)); - } + switch ((className, identifier, namedParameter)) { + case ( + final String className, + final String identifier, + final String namedParameter, + ): + // banUsageWithSpecificNamedParameter + String? expectedConstructorName; + if (identifier != _defaultConstructorIdentifier) { + expectedConstructorName = identifier; + } + + final actualClassName = node.constructorName.type.name.lexeme; + if (actualClassName == className && + node.constructorName.name?.name == expectedConstructorName && + node.argumentList.containsNamed(namedParameter) && + matchesSource( + node.constructorName.type.element?.libraryUri, + source, + )) { + reporter.atNode(node.constructorName.type, _getLintCode(entry)); + } + + case (final String className, _defaultConstructorIdentifier, null): + // banIdFromClassFromSource for default constructor + final constructorName = node.constructorName.type.name.lexeme; + if (constructorName == className && + node.constructorName.name == null && + matchesSource( + node.constructorName.type.element?.libraryUri, + source, + )) { + reporter.atNode(node.constructorName.type, _getLintCode(entry)); + } + default: + break; } }