Skip to content

Commit

Permalink
Merge branch 'release-v0.1.0' into feature/prefer-condtional-expressions
Browse files Browse the repository at this point in the history
# Conflicts:
#	lib/solid_lints.dart
#	lint_test/analysis_options.yaml
#	lint_test/no_equal_then_else_test.dart
  • Loading branch information
Denis Bogatirov committed Sep 15, 2023
2 parents 7b237f3 + 553e67f commit 18cb664
Show file tree
Hide file tree
Showing 15 changed files with 623 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/lints/avoid_unused_parameters/avoid_unused_parameters_visitor.dart';
import 'package:solid_lints/models/rule_config.dart';
import 'package:solid_lints/models/solid_lint_rule.dart';

/// A `avoid-unused-parameters` rule which
/// warns about unused parameters
class AvoidUnusedParametersRule extends SolidLintRule {
/// The [LintCode] of this lint rule that represents
/// the error whether we use bad formatted double literals.
static const String lintName = 'avoid-unused-parameters';

AvoidUnusedParametersRule._(super.config);

/// Creates a new instance of [AvoidUnusedParametersRule]
/// based on the lint configuration.
factory AvoidUnusedParametersRule.createRule(
CustomLintConfigs configs,
) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => 'Parameter is unused.',
);

return AvoidUnusedParametersRule._(rule);
}

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addCompilationUnit((node) {
final visitor = AvoidUnusedParametersVisitor();
node.accept(visitor);

for (final element in visitor.unusedParameters) {
reporter.reportErrorForNode(code, element);
}
});
}
}
190 changes: 190 additions & 0 deletions lib/lints/avoid_unused_parameters/avoid_unused_parameters_visitor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// MIT License
//
// Copyright (c) 2020-2021 Dart Code Checker team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:collection/collection.dart';
import 'package:solid_lints/utils/node_utils.dart';
import 'package:solid_lints/utils/parameter_utils.dart';

/// AST Visitor which finds all is expressions and checks if they are
/// unrelated (result always false)
class AvoidUnusedParametersVisitor extends RecursiveAstVisitor<void> {
final _unusedParameters = <FormalParameter>[];

/// List of unused parameters
Iterable<FormalParameter> get unusedParameters => _unusedParameters;

@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
super.visitConstructorDeclaration(node);

final parent = node.parent;
final parameters = node.parameters;

if (parent is ClassDeclaration && parent.abstractKeyword != null ||
node.externalKeyword != null ||
parameters.parameters.isEmpty) {
return;
}

_unusedParameters.addAll(
_getUnusedParameters(
node.body,
parameters.parameters,
).whereNot(nameConsistsOfUnderscoresOnly),
);
}

@override
void visitMethodDeclaration(MethodDeclaration node) {
super.visitMethodDeclaration(node);

final parent = node.parent;
final parameters = node.parameters;

if (parent is ClassDeclaration && parent.abstractKeyword != null ||
node.isAbstract ||
node.externalKeyword != null ||
(parameters == null || parameters.parameters.isEmpty)) {
return;
}

final isTearOff = _usedAsTearOff(node);

if (!isOverride(node.metadata) && !isTearOff) {
_unusedParameters.addAll(
_getUnusedParameters(
node.body,
parameters.parameters,
).whereNot(nameConsistsOfUnderscoresOnly),
);
}
}

@override
void visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);

final parameters = node.functionExpression.parameters;

if (node.externalKeyword != null ||
(parameters == null || parameters.parameters.isEmpty)) {
return;
}

_unusedParameters.addAll(
_getUnusedParameters(
node.functionExpression.body,
parameters.parameters,
).whereNot(nameConsistsOfUnderscoresOnly),
);
}

Set<FormalParameter> _getUnusedParameters(
AstNode body,
Iterable<FormalParameter> parameters,
) {
final result = <FormalParameter>{};
final visitor = _IdentifiersVisitor();
body.visitChildren(visitor);

final allIdentifierElements = visitor.elements;

for (final parameter in parameters) {
final name = parameter.name;
final isPresentInAll = allIdentifierElements.contains(
parameter.declaredElement,
);

/// Variables declared and initialized as 'Foo(this.param)'
bool isFieldFormalParameter = parameter is FieldFormalParameter;

/// Variables declared and initialized as 'Foo(super.param)'
bool isSuperFormalParameter = parameter is SuperFormalParameter;

if (parameter is DefaultFormalParameter) {
/// Variables as 'Foo({super.param})' or 'Foo({this.param})'
/// is being reported as [DefaultFormalParameter] instead
/// of [SuperFormalParameter] it seems to be an issue in DartSDK
isFieldFormalParameter = parameter.toSource().contains('this.');
isSuperFormalParameter = parameter.toSource().contains('super.');
}

if (name != null &&
!isPresentInAll &&
!isFieldFormalParameter &&
!isSuperFormalParameter) {
result.add(parameter);
}
}

return result;
}

bool _usedAsTearOff(MethodDeclaration node) {
final name = node.name.lexeme;
if (!Identifier.isPrivateName(name)) {
return false;
}

final visitor = _InvocationsVisitor(name);
node.root.visitChildren(visitor);

return visitor.hasTearOffInvocations;
}
}

class _IdentifiersVisitor extends RecursiveAstVisitor<void> {
final elements = <Element>{};

@override
void visitSimpleIdentifier(SimpleIdentifier node) {
super.visitSimpleIdentifier(node);

final element = node.staticElement;
if (element != null) {
elements.add(element);
}
}
}

class _InvocationsVisitor extends RecursiveAstVisitor<void> {
final String methodName;

bool hasTearOffInvocations = false;

_InvocationsVisitor(this.methodName);

@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.name == methodName &&
node.staticElement is MethodElement &&
node.parent is ArgumentList) {
hasTearOffInvocations = true;
}

super.visitSimpleIdentifier(node);
}
}
21 changes: 21 additions & 0 deletions lib/lints/no_magic_number/models/no_magic_number_parameters.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// A data model class that represents the "no magic numbers" input
/// parameters.
class NoMagicNumberParameters {
static const _allowedConfigName = 'allowed';
static const _defaultMagicNumbers = [-1, 0, 1];

/// List of allowed numbers
final Iterable<num> allowedNumbers;

/// Constructor for [NoMagicNumberParameters] model
const NoMagicNumberParameters({
required this.allowedNumbers,
});

/// Method for creating from json data
factory NoMagicNumberParameters.fromJson(Map<String, Object?> json) =>
NoMagicNumberParameters(
allowedNumbers:
json[_allowedConfigName] as Iterable<num>? ?? _defaultMagicNumbers,
);
}
125 changes: 125 additions & 0 deletions lib/lints/no_magic_number/no_magic_number_rule.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// MIT License
//
// Copyright (c) 2020-2021 Dart Code Checker team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/lints/no_magic_number/models/no_magic_number_parameters.dart';
import 'package:solid_lints/lints/no_magic_number/no_magic_number_visitor.dart';
import 'package:solid_lints/models/rule_config.dart';
import 'package:solid_lints/models/solid_lint_rule.dart';

/// A `no-magic-number` rule which forbids having numbers without variable
class NoMagicNumberRule extends SolidLintRule<NoMagicNumberParameters> {
/// The [LintCode] of this lint rule that represents
/// the error when having magic number.
static const String lintName = 'no-magic-number';

NoMagicNumberRule._(super.config);

/// Creates a new instance of [NoMagicNumberRule]
/// based on the lint configuration.
factory NoMagicNumberRule.createRule(CustomLintConfigs configs) {
final config = RuleConfig<NoMagicNumberParameters>(
configs: configs,
name: lintName,
paramsParser: NoMagicNumberParameters.fromJson,
problemMessage: (_) => 'Avoid using magic numbers.'
'Extract them to named constants or variables.',
);

return NoMagicNumberRule._(config);
}

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addCompilationUnit((node) {
final visitor = NoMagicNumberVisitor();
node.accept(visitor);

final magicNumbers = visitor.literals
.where(_isMagicNumber)
.where(_isNotInsideVariable)
.where(_isNotInsideCollectionLiteral)
.where(_isNotInsideConstMap)
.where(_isNotInsideConstConstructor)
.where(_isNotInDateTime)
.where(_isNotInsideIndexExpression)
.where(_isNotInsideEnumConstantArguments);

for (final magicNumber in magicNumbers) {
reporter.reportErrorForNode(code, magicNumber);
}
});
}

bool _isMagicNumber(Literal l) =>
(l is DoubleLiteral &&
!config.parameters.allowedNumbers.contains(l.value)) ||
(l is IntegerLiteral &&
!config.parameters.allowedNumbers.contains(l.value));

bool _isNotInsideVariable(Literal l) =>
l.thisOrAncestorMatching(
(ancestor) => ancestor is VariableDeclaration,
) ==
null;

bool _isNotInDateTime(Literal l) =>
l.thisOrAncestorMatching(
(a) =>
a is InstanceCreationExpression &&
a.staticType?.getDisplayString(withNullability: false) ==
'DateTime',
) ==
null;

bool _isNotInsideEnumConstantArguments(Literal l) {
final node = l.thisOrAncestorMatching(
(ancestor) => ancestor is EnumConstantArguments,
);

return node == null;
}

bool _isNotInsideCollectionLiteral(Literal l) => l.parent is! TypedLiteral;

bool _isNotInsideConstMap(Literal l) {
final grandParent = l.parent?.parent;

return !(grandParent is SetOrMapLiteral && grandParent.isConst);
}

bool _isNotInsideConstConstructor(Literal l) =>
l.thisOrAncestorMatching(
(ancestor) =>
ancestor is InstanceCreationExpression && ancestor.isConst,
) ==
null;

bool _isNotInsideIndexExpression(Literal l) => l.parent is! IndexExpression;
}
Loading

0 comments on commit 18cb664

Please sign in to comment.