blob: 3696511739d90cd91fdf34017eedee543307cbc4 [file] [edit]
// 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:_macro_host/macro_host.dart';
// ignore: implementation_imports
import 'package:analyzer/src/summary2/macro_declarations.dart' as analyzer;
// ignore: implementation_imports
import 'package:analyzer/src/summary2/macro_injected_impl.dart' as injected;
import 'package:dart_model/dart_model.dart';
import 'package:macro_service/macro_service.dart';
import 'package:macros/macros.dart';
// ignore: implementation_imports
import 'package:macros/src/executor.dart' as injected;
import 'query_service.dart';
/// Injected macro implementation for the analyzer.
class AnalyzerMacroImplementation implements injected.MacroImplementation {
final Uri packageConfig;
final MacroHost _host;
AnalyzerMacroImplementation._(this.packageConfig, this._host);
/// Starts the macro host.
///
/// [packageConfig] is the package config of the workspace of the code being
/// edited.
static Future<AnalyzerMacroImplementation> start({
required Uri packageConfig,
}) async =>
AnalyzerMacroImplementation._(
packageConfig,
await MacroHost.serve(
packageConfig: packageConfig,
queryService: AnalyzerQueryService()));
@override
injected.MacroPackageConfigs get packageConfigs =>
AnalyzerMacroPackageConfigs(this);
@override
injected.MacroRunner get runner => AnalyzerMacroRunner(this);
}
class AnalyzerMacroPackageConfigs implements injected.MacroPackageConfigs {
final AnalyzerMacroImplementation _impl;
AnalyzerMacroPackageConfigs(this._impl);
@override
bool isMacro(Uri uri, String name) =>
_impl._host.isMacro(QualifiedName('$uri#$name'));
}
class AnalyzerMacroRunner implements injected.MacroRunner {
final AnalyzerMacroImplementation _impl;
AnalyzerMacroRunner(this._impl);
@override
injected.RunningMacro run(Uri uri, String name) => AnalyzerRunningMacro.run(
_impl,
QualifiedName('$uri#$name'),
_impl._host.lookupMacroImplementation(QualifiedName('$uri#$name'))!);
}
class AnalyzerRunningMacro implements injected.RunningMacro {
final AnalyzerMacroImplementation _impl;
final QualifiedName name;
final QualifiedName implementation;
late final Future _started;
AnalyzerRunningMacro._(this._impl, this.name, this.implementation);
// TODO(davidmorgan): should this be async, removing the need for `_started`?
// If so the API injected into analyzer+CFE needs to change to be async.
static AnalyzerRunningMacro run(AnalyzerMacroImplementation impl,
QualifiedName name, QualifiedName implementation) {
final result = AnalyzerRunningMacro._(impl, name, implementation);
// TODO(davidmorgan): this is currently what starts the macro running,
// make it explicit.
result._started =
impl._host.queryMacroPhases(impl.packageConfig, implementation);
return result;
}
@override
Future<AnalyzerMacroExecutionResult> executeDeclarationsPhase(
MacroTarget target,
DeclarationPhaseIntrospector declarationsPhaseIntrospector) async {
await _started;
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
introspector = declarationsPhaseIntrospector;
return AnalyzerMacroExecutionResult(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 2, target: target.qualifiedName)));
}
@override
Future<AnalyzerMacroExecutionResult> executeDefinitionsPhase(
MacroTarget target,
DefinitionPhaseIntrospector definitionPhaseIntrospector) async {
await _started;
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
introspector = definitionPhaseIntrospector;
return AnalyzerMacroExecutionResult(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 3, target: target.qualifiedName)));
}
@override
Future<AnalyzerMacroExecutionResult> executeTypesPhase(
MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async {
await _started;
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
introspector = typePhaseIntrospector;
return AnalyzerMacroExecutionResult(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 1, target: target.qualifiedName)));
}
}
/// Converts [AugmentResponse] to [injected.MacroExecutionResult].
///
/// TODO(davidmorgan): add to `AugmentationResponse` to cover all the
/// functionality of `MacroExecutionResult`.
class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
final MacroTarget target;
final AugmentResponse augmentResponse;
AnalyzerMacroExecutionResult(this.target, this.augmentResponse);
@override
List<Diagnostic> get diagnostics => [];
@override
Map<Identifier, Iterable<DeclarationCode>> get enumValueAugmentations => {};
@override
MacroException? get exception => null;
@override
Map<Identifier, NamedTypeAnnotationCode> get extendsTypeAugmentations => {};
@override
Map<Identifier, Iterable<TypeAnnotationCode>> get interfaceAugmentations =>
{};
@override
Iterable<DeclarationCode> get libraryAugmentations => {};
@override
Map<Identifier, Iterable<TypeAnnotationCode>> get mixinAugmentations => {};
@override
Iterable<String> get newTypeNames => [];
@override
void serialize(Object serializer) => throw UnimplementedError();
@override
Map<Identifier, Iterable<DeclarationCode>> get typeAugmentations => {
// TODO(davidmorgan): this assumes augmentations are for the macro
// application target. Instead, it should be explicit in
// `AugmentResponse`.
(target as Declaration).identifier: augmentResponse.augmentations
.map((a) => DeclarationCode.fromParts([a.code]))
.toList(),
};
}
extension MacroTargetExtension on MacroTarget {
QualifiedName get qualifiedName {
final element =
((this as Declaration).identifier as analyzer.IdentifierImpl).element!;
return QualifiedName(
'${element.library!.definingCompilationUnit.source.uri}#'
'${element.displayName}');
}
}