Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 167 1 #186

Merged
merged 15 commits into from
Dec 6, 2024
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
/// Model class for AvoidReturningWidgetsExclude parameters
class AvoidReturningWidgetsExclude {
/// Model class for ExcludeRule parameters
class ExcludedIdentifierParameter {
/// The name of the method that should be excluded from the lint.
final String methodName;

/// The name of the class that should be excluded from the lint.
final String? className;

/// Constructor for [AvoidReturningWidgetsExclude] model
const AvoidReturningWidgetsExclude({
/// Constructor for [ExcludedIdentifierParameter] model
const ExcludedIdentifierParameter({
required this.methodName,
required this.className,
});

///
factory AvoidReturningWidgetsExclude.fromJson(
factory ExcludedIdentifierParameter.fromJson(
Map<dynamic, dynamic> json,
) {
return AvoidReturningWidgetsExclude(
return ExcludedIdentifierParameter(
methodName: json['method_name'] as String,
className: json['class_name'] as String?,
);
78 changes: 78 additions & 0 deletions lib/src/common/parameters/excluded_identifiers_list_parameter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:collection/collection.dart';
import 'package:solid_lints/src/common/parameters/excluded_identifier_parameter.dart';

/// A model representing "exclude" parameters for linting, defining
/// identifiers (classes, methods, functions) to be ignored during analysis.
class ExcludedIdentifiersListParameter {
/// A list of identifiers (classes, methods, functions) that should be
/// excluded from the lint.
final List<ExcludedIdentifierParameter> exclude;

/// A common parameter key for analysis_options.yaml
static const String excludeParameterName = 'exclude';

/// Constructor for [ExcludedIdentifiersListParameter] model
ExcludedIdentifiersListParameter({
required this.exclude,
});

/// Method for creating from json data
factory ExcludedIdentifiersListParameter.fromJson({
required Iterable<dynamic> excludeList,
}) {
final exclude = <ExcludedIdentifierParameter>[];

for (final item in excludeList) {
if (item is Map) {
exclude.add(ExcludedIdentifierParameter.fromJson(item));
}
}
return ExcludedIdentifiersListParameter(
exclude: exclude,
);
}

/// Method for creating from json data with default params
factory ExcludedIdentifiersListParameter.defaultFromJson(
Map<String, dynamic> json,
) {
final exclude = <ExcludedIdentifierParameter>[];

final excludeList =
json[ExcludedIdentifiersListParameter.excludeParameterName]
as Iterable? ??
[];

for (final item in excludeList) {
if (item is Map) {
exclude.add(ExcludedIdentifierParameter.fromJson(item));
}
}
return ExcludedIdentifiersListParameter(
exclude: exclude,
);
}

/// Returns whether the target node should be ignored during analysis.
bool shouldIgnore(Declaration node) {
final methodName = node.declaredElement?.name;

final excludedItem = exclude.firstWhereOrNull(
(e) => e.methodName == methodName || e.className == methodName,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes some confusion.

And also highlights that we may not be comparing what we think we should be comparing.

We should check whether the declaration is a class before running exclude.className == declarationName; same for method names.

Suggested change
(e) => e.methodName == methodName || e.className == methodName,
(e) => e.methodName == declarationName || e.className == declarationName,

);

if (excludedItem == null) return false;

final className = excludedItem.className;

if (className == null || node is! MethodDeclaration) {
return true;
} else {
final classDeclaration = node.thisOrAncestorOfType<ClassDeclaration>();

return classDeclaration != null &&
classDeclaration.name.toString() == className;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:collection/collection.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart';
import 'package:solid_lints/src/models/rule_config.dart';
@@ -96,7 +95,7 @@ class AvoidReturningWidgetsRule

final isWidgetReturned = hasWidgetType(returnType);

final isIgnored = _shouldIgnore(node);
final isIgnored = config.parameters.exclude.shouldIgnore(node);

final isOverriden = node.declaredElement?.hasOverride ?? false;

@@ -105,25 +104,4 @@ class AvoidReturningWidgetsRule
}
});
}

bool _shouldIgnore(Declaration node) {
final methodName = node.declaredElement?.name;

final excludedItem = config.parameters.exclude
.firstWhereOrNull((e) => e.methodName == methodName);

if (excludedItem == null) return false;

final className = excludedItem.className;

if (className == null || node is! MethodDeclaration) {
return true;
} else {
final classDeclaration = node.thisOrAncestorOfType<ClassDeclaration>();

if (classDeclaration == null) return false;

return classDeclaration.name.toString() == className;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_exclude.dart';
import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart';

/// A data model class that represents the "avoid returning widgets" input
/// parameters.
class AvoidReturningWidgetsParameters {
/// A list of methods that should be excluded from the lint.
final List<AvoidReturningWidgetsExclude> exclude;
final ExcludedIdentifiersListParameter exclude;

/// Constructor for [AvoidReturningWidgetsParameters] model
AvoidReturningWidgetsParameters({
@@ -13,16 +13,8 @@ class AvoidReturningWidgetsParameters {

/// Method for creating from json data
factory AvoidReturningWidgetsParameters.fromJson(Map<String, dynamic> json) {
final exclude = <AvoidReturningWidgetsExclude>[];

final excludeList = json['exclude'] as Iterable? ?? [];
for (final item in excludeList) {
if (item is Map) {
exclude.add(AvoidReturningWidgetsExclude.fromJson(item));
}
}
return AvoidReturningWidgetsParameters(
exclude: exclude,
exclude: ExcludedIdentifiersListParameter.defaultFromJson(json),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/avoid_unused_parameters/models/avoid_unused_parameters.dart';
import 'package:solid_lints/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';
@@ -64,7 +66,7 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// };
///
/// ```
class AvoidUnusedParametersRule extends SolidLintRule {
class AvoidUnusedParametersRule extends SolidLintRule<AvoidUnusedParameters> {
/// This lint rule represents
/// the error whether we use bad formatted double literals.
static const String lintName = 'avoid_unused_parameters';
@@ -79,6 +81,7 @@ class AvoidUnusedParametersRule extends SolidLintRule {
final rule = RuleConfig(
configs: configs,
name: lintName,
paramsParser: AvoidUnusedParameters.fromJson,
problemMessage: (_) => 'Parameter is unused.',
);

@@ -91,12 +94,16 @@ class AvoidUnusedParametersRule extends SolidLintRule {
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addCompilationUnit((node) {
final visitor = AvoidUnusedParametersVisitor();
node.accept(visitor);
context.registry.addDeclaration((node) {
final isIgnored = config.parameters.exclude.shouldIgnore(node);

if (!isIgnored) {
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
final visitor = AvoidUnusedParametersVisitor();
node.accept(visitor);

for (final element in visitor.unusedParameters) {
reporter.atNode(element, code);
for (final element in visitor.unusedParameters) {
reporter.atNode(element, code);
}
}
});
}
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart';

/// A data model class that represents the "avoid returning widgets" input
/// parameters.
class AvoidUnusedParameters {
/// A list of methods that should be excluded from the lint.
final ExcludedIdentifiersListParameter exclude;

/// Constructor for [AvoidUnusedParameters] model
AvoidUnusedParameters({
required this.exclude,
});

/// Method for creating from json data
factory AvoidUnusedParameters.fromJson(Map<String, dynamic> json) {
return AvoidUnusedParameters(
exclude: ExcludedIdentifiersListParameter.defaultFromJson(json),
);
}
}
Original file line number Diff line number Diff line change
@@ -52,13 +52,19 @@ class CyclomaticComplexityRule
CustomLintContext context,
) {
context.registry.addBlockFunctionBody((node) {
final visitor = CyclomaticComplexityFlowVisitor();
node.visitChildren(visitor);
context.registry.addDeclaration((declarationNode) {
final isIgnored =
config.parameters.exclude.shouldIgnore(declarationNode);
if (!isIgnored) {
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
final visitor = CyclomaticComplexityFlowVisitor();
node.visitChildren(visitor);

if (visitor.complexityEntities.length + 1 >
config.parameters.maxComplexity) {
reporter.atNode(node, code);
}
if (visitor.complexityEntities.length + 1 >
config.parameters.maxComplexity) {
reporter.atNode(node, code);
}
}
});
});
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart';

/// Cyclomatic complexity metric limits configuration.
class CyclomaticComplexityParameters {
/// Threshold cyclomatic complexity level, exceeding it triggers a warning.
final int maxComplexity;

/// A list of methods that should be excluded from the lint.
final ExcludedIdentifiersListParameter exclude;

/// Reference: NIST 500-235 item 2.5
static const _defaultMaxComplexity = 10;

/// Constructor for [CyclomaticComplexityParameters] model
const CyclomaticComplexityParameters({
required this.maxComplexity,
required this.exclude,
});

/// Method for creating from json data
factory CyclomaticComplexityParameters.fromJson(Map<String, Object?> json) =>
CyclomaticComplexityParameters(
maxComplexity: json['max_complexity'] as int? ?? _defaultMaxComplexity,
exclude: ExcludedIdentifiersListParameter.defaultFromJson(json),
);
}
Original file line number Diff line number Diff line change
@@ -50,22 +50,22 @@ class FunctionLinesOfCodeRule
) {
void checkNode(AstNode node) => _checkNode(resolver, reporter, node);

context.registry.addMethodDeclaration(checkNode);
context.registry.addFunctionDeclaration(checkNode);
context.registry.addFunctionExpression(checkNode);
context.registry.addDeclaration((declarationNode) {
final isIgnored = config.parameters.exclude.shouldIgnore(declarationNode);

if (!isIgnored) {
context.registry.addMethodDeclaration(checkNode);
context.registry.addFunctionDeclaration(checkNode);
context.registry.addFunctionExpression(checkNode);
}
});
}

void _checkNode(
CustomLintResolver resolver,
ErrorReporter reporter,
AstNode node,
) {
final functionName = _getFunctionName(node);
if (functionName != null &&
config.parameters.excludeNames.contains(functionName)) {
return;
}

final visitor = FunctionLinesOfCodeVisitor(resolver.lineInfo);
node.visitChildren(visitor);

@@ -84,16 +84,4 @@ class FunctionLinesOfCodeRule
);
}
}

String? _getFunctionName(AstNode node) {
if (node is FunctionDeclaration) {
return node.name.lexeme;
} else if (node is MethodDeclaration) {
return node.name.lexeme;
} else if (node is FunctionExpression) {
return node.declaredElement?.name;
} else {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart';

/// A data model class that represents the "function lines of code" input
/// parameters.
class FunctionLinesOfCodeParameters {
/// Maximum allowed number of lines of code (LoC) per function,
/// exceeding this limit triggers a warning.
final int maxLines;

/// Function names to be excluded from the rule check
final List<String> excludeNames;
/// A list of methods that should be excluded from the lint.
final ExcludedIdentifiersListParameter exclude;

static const _defaultMaxLines = 200;

/// Constructor for [FunctionLinesOfCodeParameters] model
const FunctionLinesOfCodeParameters({
required this.maxLines,
required this.excludeNames,
required this.exclude,
});

/// Method for creating from json data
factory FunctionLinesOfCodeParameters.fromJson(Map<String, Object?> json) =>
FunctionLinesOfCodeParameters(
maxLines: json['max_lines'] as int? ?? _defaultMaxLines,
excludeNames:
List<String>.from(json['excludeNames'] as Iterable? ?? []),
exclude: ExcludedIdentifiersListParameter.defaultFromJson(json),
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
);
}
Loading
Loading