| // 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 = _Visitor( | |
| this, | |
| context.typeProvider, | |
| context.libraryElement!.languageVersion.effective, | |
| ); | |
| registry.addInstanceCreationExpression(this, visitor); | |
| registry.addDotShorthandConstructorInvocation(this, visitor); | |
| } | |
| } | |
| class _Visitor( | |
| final FutureSyncValue rule, | |
| final TypeProvider typeProvider, | |
| final Version version, | |
| ) extends SimpleAstVisitor<void> { | |
| static const valueConstructorName = 'value'; | |
| static const syncValueConstructorName = 'syncValue'; | |
| @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, | |
| ); | |
| } |