blob: 14705f6568436ec59f6cdc4c2ba91bf63a986508 [file] [edit]
// Copyright (c) 2026, 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/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/ast/ast.dart'; // ignore: implementation_imports
import 'package:analyzer/src/dart/element/type.dart'; // ignore: implementation_imports
import 'package:pub_semver/src/version.dart'; // ignore: implementation_imports
import '../analyzer.dart';
import '../diagnostic.dart' as diag;
const _desc = r'For synchronous values, `Future.syncValue` is more performant.';
class FutureSyncValue extends AnalysisRule {
static final constructorSince = Version(3, 10, 0);
new()
: super(
name: LintNames.future_sync_value,
description: _desc,
state: .stable(since: .new(3, 14, 0)),
);
@override
DiagnosticCode get diagnosticCode => diag.futureSyncValue;
@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
var element = context.libraryElement;
if (element != null &&
element.languageVersion.effective.compareTo(constructorSince) < 0) {
return;
}
var visitor = _FutureSyncValueVisitor(
this,
context.typeProvider,
context.libraryElement!.languageVersion.effective,
);
registry.addInstanceCreationExpression(this, visitor);
registry.addDotShorthandConstructorInvocation(this, visitor);
}
}
class _FutureSyncValueVisitor extends SimpleAstVisitor<void> {
static const valueConstructorName = 'value';
static const syncValueConstructorName = 'syncValue';
final FutureSyncValue rule;
final TypeProvider typeProvider;
final Version version;
new(this.rule, this.typeProvider, this.version);
@override
void visitDotShorthandConstructorInvocation(
DotShorthandConstructorInvocation node,
) {
_report(
node.constructorName.token,
node.staticType,
node.argumentList.arguments.firstOrNull,
);
}
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
_report(
node.constructorName.name?.token,
node.staticType,
node.argumentList.arguments.firstOrNull,
);
}
bool _isFuture(DartType? type) {
if (type == null) return false;
return type.asInstanceOf(typeProvider.futureElement) != null ||
type.asInstanceOf(typeProvider.futureOrElement) != null;
}
void _report(Token? token, DartType? instance, Argument? argument) {
if (token == null || token.lexeme != valueConstructorName) return;
if (instance == null || !instance.isDartAsyncFuture) return;
if (!_valueSyncConstructorExists(instance.element)) return;
if (argument == null) return;
if (_isFuture(argument.argumentExpression.staticType) ||
argument.argumentExpression.staticType == DynamicTypeImpl.instance) {
return;
}
rule.reportAtToken(token);
}
bool _valueSyncConstructorExists(Element? element) =>
element is ClassElement &&
element.constructors.any(
(constructor) => constructor.name == syncValueConstructorName,
);
}