Skip to content

Commit

Permalink
Version 3.6.0-112.0.dev
Browse files Browse the repository at this point in the history
Merge b39f386 into dev
  • Loading branch information
Dart CI committed Aug 3, 2024
2 parents ff56a84 + b39f386 commit c4c9a84
Show file tree
Hide file tree
Showing 14 changed files with 363 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2485,6 +2485,8 @@ LintCode.use_to_and_as_if_applicable:
status: noFix
notes: |-
This would require renaming the method, which is a refactoring.
LintCode.use_truncating_division:
status: hasFix
LintCode.valid_regexps:
status: noFix
LintCode.void_checks:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ import 'package:linter/src/rules/use_raw_strings.dart';
import 'package:linter/src/rules/use_rethrow_when_possible.dart';
import 'package:linter/src/rules/use_string_in_part_of_directives.dart';
import 'package:linter/src/rules/use_super_parameters.dart';
import 'package:linter/src/rules/use_truncating_division.dart';

final _builtInLintMultiProducers = {
CommentReferences.code: [
Expand Down Expand Up @@ -859,6 +860,9 @@ final _builtInLintProducers = <LintCode, List<ProducerGenerator>>{
UseSuperParameters.multipleParams: [
ConvertToSuperParameters.new,
],
UseTruncatingDivision.code: [
UseEffectiveIntegerDivision.new,
],
};

final _builtInNonLintMultiProducers = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../../../../client/completion_driver_test.dart';

void main() {
defineReflectiveSuite(() {
defineReflectiveTests(WildcardCatchClauseTest);
defineReflectiveTests(WildcardFieldTest);
defineReflectiveTests(WildcardForLoopTest);
defineReflectiveTests(WildcardImportPrefixTest);
Expand All @@ -17,15 +18,56 @@ void main() {
});
}

/// Fields are binding so not technically wildcards but look just like them.
@reflectiveTest
class WildcardFieldTest extends AbstractCompletionDriverTest {
class AbstractWildCardTest extends AbstractCompletionDriverTest {
@override
Set<String> allowedIdentifiers = {'_'};
Set<String> allowedIdentifiers = {'_', '__', '___'};

@override
bool get includeKeywords => false;
}

@reflectiveTest
class WildcardCatchClauseTest extends AbstractWildCardTest {
Future<void> test_argumentList() async {
await computeSuggestions('''
void p(Object o) {}
void f() {
try {
} catch(_, _) {
p(^);
}
}
''');
assertResponse(r'''
suggestions
''');
}

Future<void> test_argumentList_underscores() async {
await computeSuggestions('''
void p(Object o) {}
void f() {
try {
} catch(__, ___) {
p(^);
}
}
''');
assertResponse(r'''
suggestions
__
kind: localVariable
___
kind: localVariable
''');
}
}

/// Fields are binding so not technically wildcards but look just like them.
@reflectiveTest
class WildcardFieldTest extends AbstractWildCardTest {
Future<void> test_argumentList() async {
await computeSuggestions('''
void p(Object o) {}
Expand Down Expand Up @@ -63,13 +105,7 @@ suggestions
}

@reflectiveTest
class WildcardForLoopTest extends AbstractCompletionDriverTest {
@override
Set<String> allowedIdentifiers = {'_', '__'};

@override
bool get includeKeywords => false;

class WildcardForLoopTest extends AbstractWildCardTest {
Future<void> test_forEach_argumentList() async {
await computeSuggestions('''
void p(Object o) {}
Expand Down Expand Up @@ -136,12 +172,12 @@ suggestions
}

@reflectiveTest
class WildcardImportPrefixTest extends AbstractCompletionDriverTest {
class WildcardImportPrefixTest extends AbstractWildCardTest {
@override
Set<String> allowedIdentifiers = {'_', '__', 'isBlank'};

@override
bool get includeKeywords => false;
Future<void> setUp() async {
await super.setUp();
allowedIdentifiers.add('isBlank');
}

Future<void> test_argumentList() async {
newFile('$testPackageLibPath/ext.dart', '''
Expand Down Expand Up @@ -215,25 +251,19 @@ suggestions
}

@reflectiveTest
class WildcardLocalVariableTest extends AbstractCompletionDriverTest {
@override
Set<String> allowedIdentifiers = {'_', '__', 'b'};

@override
bool get includeKeywords => false;

class WildcardLocalVariableTest extends AbstractWildCardTest {
Future<void> test_argumentList() async {
await computeSuggestions('''
void p(Object o) {}
void f() {
var _, b = 0;
var _, b0 = 0;
p(^);
}
''');
assertResponse(r'''
suggestions
b
b0
kind: localVariable
''');
}
Expand Down Expand Up @@ -270,24 +300,18 @@ suggestions
}

@reflectiveTest
class WildcardParameterTest extends AbstractCompletionDriverTest {
@override
Set<String> allowedIdentifiers = {'_', '__', 'b'};

@override
bool get includeKeywords => false;

class WildcardParameterTest extends AbstractWildCardTest {
Future<void> test_argumentList() async {
await computeSuggestions('''
void p(Object o) {}
void f(int _, int b) {
void f(int _, int b0) {
p(^);
}
''');
assertResponse('''
suggestions
b
b0
kind: parameter
''');
}
Expand All @@ -311,13 +335,7 @@ suggestions
/// Top level variables are binding so not technically wildcards but look just
/// like them.
@reflectiveTest
class WildcardTopLevelVariableTest extends AbstractCompletionDriverTest {
@override
Set<String> allowedIdentifiers = {'_'};

@override
bool get includeKeywords => false;

class WildcardTopLevelVariableTest extends AbstractWildCardTest {
Future<void> test_argumentList() async {
await computeSuggestions('''
int _ = 0;
Expand Down
2 changes: 2 additions & 0 deletions pkg/analyzer/lib/src/error/best_practices_verifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
/// Checks the passed binary expression for [HintCode.DIVISION_OPTIMIZATION].
///
/// Returns whether a hint code is generated.
// TODO(srawlins): Remove this ASAP, as it is being replaced by the
// 'use_truncating_division' lint rule, to avoid double reporting.
bool _checkForDivisionOptimizationHint(BinaryExpression node) {
if (node.operator.type != TokenType.SLASH) return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3828,6 +3828,26 @@ class B extends A {
);
}

Future<void> test_method_wildcardParams() async {
await _assertWriteOverride(
content: '''
class A {
void m(int _, [int _]) { }
}
class B extends A {
}
''',
nameToOverride: 'm',
expected: '''
@override void m(int _, [int _]) {
// TODO: implement m
}
''',
displayText: 'm(int _, [int _]) { … }',
selection: SourceRange(122, 0),
);
}

Future<void> test_mixin_method_of_interface() async {
await _assertWriteOverride(
content: '''
Expand Down
9 changes: 9 additions & 0 deletions pkg/analyzer_plugin/test/support/abstract_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer_utilities/test/experiments/experiments.dart';
import 'package:analyzer_utilities/test/mock_packages/mock_packages.dart';
import 'package:linter/src/rules.dart';
import 'package:meta/meta.dart';

/// Finds an [Element] with the given [name].
Element? findChildElement(Element root, String name, [ElementKind? kind]) {
Expand Down Expand Up @@ -45,6 +47,10 @@ class AbstractContextTest with MockPackagesMixin, ResourceProviderMixin {

List<String> get collectionIncludedPaths => [workspaceRootPath];

/// Return a list of the experiments that are to be enabled for tests in this
/// class, an empty list if there are no experiments that should be enabled.
List<String> get experiments => experimentsForTests;

@override
String get packagesRootPath => '/packages';

Expand Down Expand Up @@ -112,6 +118,7 @@ class AbstractContextTest with MockPackagesMixin, ResourceProviderMixin {
return analysisContext.currentSession;
}

@mustCallSuper
void setUp() {
createMockSdk(
resourceProvider: resourceProvider,
Expand All @@ -120,8 +127,10 @@ class AbstractContextTest with MockPackagesMixin, ResourceProviderMixin {

newFolder(testPackageRootPath);
writeTestPackageConfig();
createAnalysisOptionsFile(experiments: experiments);
}

@mustCallSuper
void tearDown() {
AnalysisEngine.instance.clearCaches();
}
Expand Down
1 change: 1 addition & 0 deletions pkg/linter/example/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,6 @@ linter:
- use_super_parameters
- use_test_throws_matchers
- use_to_and_as_if_applicable
- use_truncating_division
- valid_regexps
- void_checks
2 changes: 2 additions & 0 deletions pkg/linter/lib/src/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ import 'rules/use_string_in_part_of_directives.dart';
import 'rules/use_super_parameters.dart';
import 'rules/use_test_throws_matchers.dart';
import 'rules/use_to_and_as_if_applicable.dart';
import 'rules/use_truncating_division.dart';
import 'rules/valid_regexps.dart';
import 'rules/void_checks.dart';

Expand Down Expand Up @@ -476,6 +477,7 @@ void registerLintRules() {
..register(UseSuperParameters())
..register(UseTestThrowsMatchers())
..register(UseToAndAsIfApplicable())
..register(UseTruncatingDivision())
..register(ValidRegexps())
..register(VoidChecks());
}
97 changes: 97 additions & 0 deletions pkg/linter/lib/src/rules/use_truncating_division.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';

import '../analyzer.dart';

const _desc = r'Use truncating division.';

const _details = r'''
**DO** use truncating division, '~/', instead of regular division ('/') followed
by 'toInt()'.
Dart features a "truncating division" operator which is the same operation as
division followed by truncation, but which is more concise and expressive, and
may be more performant on some platforms, for certain inputs.
**BAD:**
```dart
var x = (2 / 3).toInt();
```
**GOOD:**
```dart
var x = 2 ~/ 3;
```
''';

class UseTruncatingDivision extends LintRule {
static const LintCode code = LintCode(
'use_truncating_division',
'Use truncating division.',
correctionMessage:
"Try using truncating division, '~/', instead of regular division "
"('/') followed by 'toInt()'.",
);

UseTruncatingDivision()
: super(
name: 'use_truncating_division',
description: _desc,
details: _details,
categories: {LintRuleCategory.languageFeatureUsage});

@override
LintCode get lintCode => code;

@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this);
registry.addBinaryExpression(this, visitor);
}
}

class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;

_Visitor(this.rule);

@override
void visitBinaryExpression(BinaryExpression node) {
if (node.operator.type != TokenType.SLASH) return;

// Return if the two operands are not each `int`.
var leftType = node.leftOperand.staticType;
if (leftType == null || !leftType.isDartCoreInt) return;

var rightType = node.rightOperand.staticType;
if (rightType == null || !rightType.isDartCoreInt) return;

// Return if the '/' operator is not defined in core, or if we don't know
// its static type.
var methodElement = node.staticElement;
if (methodElement == null) return;

var libraryElement = methodElement.library;
if (!libraryElement.isDartCore) return;

var parent = node.parent;
if (parent is! ParenthesizedExpression) return;

var outermostParentheses = parent.thisOrAncestorMatching(
(e) => e.parent is! ParenthesizedExpression)!
as ParenthesizedExpression;
var grandParent = outermostParentheses.parent;
if (grandParent is MethodInvocation &&
grandParent.methodName.name == 'toInt' &&
grandParent.argumentList.arguments.isEmpty) {
rule.reportLint(grandParent);
}
}
}
Loading

0 comments on commit c4c9a84

Please sign in to comment.