From ea26e024ce60943ddfe1baa95612181186d8a6bd Mon Sep 17 00:00:00 2001 From: Patrick Stewart Date: Sun, 8 Sep 2024 23:09:22 -0700 Subject: [PATCH] update: updating files with detailed comments --- packages/runtime/lib/runtime.dart | 32 ++++++- packages/runtime/lib/slow_coerce.dart | 29 +++++- packages/runtime/lib/src/analyzer.dart | 31 +++++++ packages/runtime/lib/src/build.dart | 44 ++++++++- packages/runtime/lib/src/build_context.dart | 97 +++++++++++++++++--- packages/runtime/lib/src/build_manager.dart | 37 +++++++- packages/runtime/lib/src/compiler.dart | 54 +++++++++-- packages/runtime/lib/src/context.dart | 46 ++++++++-- packages/runtime/lib/src/exceptions.dart | 25 +++++ packages/runtime/lib/src/generator.dart | 36 ++++++++ packages/runtime/lib/src/mirror_coerce.dart | 26 +++++- packages/runtime/lib/src/mirror_context.dart | 31 ++++++- 12 files changed, 448 insertions(+), 40 deletions(-) diff --git a/packages/runtime/lib/runtime.dart b/packages/runtime/lib/runtime.dart index a89b332..e940c7b 100644 --- a/packages/runtime/lib/runtime.dart +++ b/packages/runtime/lib/runtime.dart @@ -7,10 +7,15 @@ * file that was distributed with this source code. */ +/// The main library for the Protevus runtime package. +/// +/// This library provides core functionality for the Protevus Platform, +/// including compilation, analysis, and runtime management. library runtime; import 'dart:io'; +// Export statements for various components of the runtime package export 'package:protevus_runtime/src/analyzer.dart'; export 'package:protevus_runtime/src/build.dart'; export 'package:protevus_runtime/src/build_context.dart'; @@ -21,19 +26,36 @@ export 'package:protevus_runtime/src/exceptions.dart'; export 'package:protevus_runtime/src/generator.dart'; export 'package:protevus_runtime/src/mirror_coerce.dart'; export 'package:protevus_runtime/src/mirror_context.dart'; - import 'package:protevus_runtime/runtime.dart'; -/// Compiler for the runtime package itself. +/// A specialized compiler for the runtime package itself. /// -/// Removes dart:mirror from a replica of this package, and adds -/// a generated runtime to the replica's pubspec. +/// This compiler is responsible for creating a mirror-free version of the +/// runtime package. It removes dart:mirror dependencies and adds a generated +/// runtime to the package's pubspec. class RuntimePackageCompiler extends Compiler { + /// Compiles the runtime package. + /// + /// This method is currently a no-op, returning an empty map. It may be + /// extended in the future to perform actual compilation tasks. + /// + /// [context] - The mirror context for compilation (unused in this implementation). + /// + /// Returns an empty map, as no compilation is currently performed. @override Map compile(MirrorContext context) => {}; + /// Modifies the package structure to remove mirror dependencies. + /// + /// This method performs the following tasks: + /// 1. Rewrites the main library file to remove mirror-related exports. + /// 2. Updates the context file to use the generated runtime instead of mirror context. + /// 3. Modifies the pubspec.yaml to include the generated runtime as a dependency. + /// + /// [destinationDirectory] - The directory where the modified package will be created. @override void deflectPackage(Directory destinationDirectory) { + // Rewrite the main library file final libraryFile = File.fromUri( destinationDirectory.uri.resolve("lib/").resolve("runtime.dart"), ); @@ -41,6 +63,7 @@ class RuntimePackageCompiler extends Compiler { "library runtime;\nexport 'src/context.dart';\nexport 'src/exceptions.dart';", ); + // Update the context file final contextFile = File.fromUri( destinationDirectory.uri .resolve("lib/") @@ -53,6 +76,7 @@ class RuntimePackageCompiler extends Compiler { ); contextFile.writeAsStringSync(contextFileContents); + // Modify the pubspec.yaml final pubspecFile = File.fromUri(destinationDirectory.uri.resolve("pubspec.yaml")); final pubspecContents = pubspecFile.readAsStringSync().replaceFirst( diff --git a/packages/runtime/lib/slow_coerce.dart b/packages/runtime/lib/slow_coerce.dart index b1f5093..292cf98 100644 --- a/packages/runtime/lib/slow_coerce.dart +++ b/packages/runtime/lib/slow_coerce.dart @@ -9,12 +9,30 @@ import 'package:protevus_runtime/runtime.dart'; +/// Prefix string for List types in type checking. const String _listPrefix = "List<"; + +/// Prefix string for Map types in type checking. const String _mapPrefix = "Map(dynamic input) { try { var typeString = T.toString(); + + // Handle nullable types if (typeString.endsWith('?')) { if (input == null) { return null as T; @@ -22,11 +40,14 @@ T cast(dynamic input) { typeString = typeString.substring(0, typeString.length - 1); } } + + // Handle List types if (typeString.startsWith(_listPrefix)) { if (input is! List) { throw TypeError(); } + // Cast List to various element types if (typeString.startsWith("List")) { return List.from(input) as T; } else if (typeString.startsWith("List")) { @@ -50,12 +71,16 @@ T cast(dynamic input) { } else if (typeString.startsWith("List>")) { return List>.from(input) as T; } - } else if (typeString.startsWith(_mapPrefix)) { + } + // Handle Map types + else if (typeString.startsWith(_mapPrefix)) { if (input is! Map) { throw TypeError(); } final inputMap = input as Map; + + // Cast Map to various value types if (typeString.startsWith("Map")) { return Map.from(inputMap) as T; } else if (typeString.startsWith("Map")) { @@ -79,8 +104,10 @@ T cast(dynamic input) { } } + // If no specific casting is needed, return the input as T return input as T; } on TypeError { + // If a TypeError occurs during casting, throw a TypeCoercionException throw TypeCoercionException(T, input.runtimeType); } } diff --git a/packages/runtime/lib/src/analyzer.dart b/packages/runtime/lib/src/analyzer.dart index d6b2881..412a974 100644 --- a/packages/runtime/lib/src/analyzer.dart +++ b/packages/runtime/lib/src/analyzer.dart @@ -14,7 +14,11 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:path/path.dart'; +/// A class for analyzing Dart code. class CodeAnalyzer { + /// Constructs a CodeAnalyzer with the given URI. + /// + /// Throws an [ArgumentError] if the URI is not absolute or if no analysis context is found. CodeAnalyzer(this.uri) { if (!uri.isAbsolute) { throw ArgumentError("'uri' must be absolute for CodeAnalyzer"); @@ -27,16 +31,23 @@ class CodeAnalyzer { } } + /// Gets the path from the URI. String get path { return getPath(uri); } + /// The URI of the code to analyze. late final Uri uri; + /// The collection of analysis contexts. late AnalysisContextCollection contexts; + /// A cache of resolved ASTs. final _resolvedAsts = {}; + /// Resolves the unit or library at the given URI. + /// + /// Returns a [Future] that completes with an [AnalysisResult]. Future resolveUnitOrLibraryAt(Uri uri) async { if (FileSystemEntity.isFileSync( uri.toFilePath(windows: Platform.isWindows), @@ -47,6 +58,10 @@ class CodeAnalyzer { } } + /// Resolves the library at the given URI. + /// + /// Returns a [Future] that completes with a [ResolvedLibraryResult]. + /// Throws an [ArgumentError] if the URI could not be resolved. Future resolveLibraryAt(Uri uri) async { assert( FileSystemEntity.isDirectorySync( @@ -68,6 +83,10 @@ class CodeAnalyzer { "${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})"); } + /// Resolves the unit at the given URI. + /// + /// Returns a [Future] that completes with a [ResolvedUnitResult]. + /// Throws an [ArgumentError] if the URI could not be resolved. Future resolveUnitAt(Uri uri) async { assert( FileSystemEntity.isFileSync( @@ -89,6 +108,9 @@ class CodeAnalyzer { "${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})"); } + /// Gets the class declaration from the file at the given URI. + /// + /// Returns null if the class is not found or if there's an error. ClassDeclaration? getClassFromFile(String className, Uri fileUri) { try { return _getFileAstRoot(fileUri) @@ -103,6 +125,9 @@ class CodeAnalyzer { } } + /// Gets all subclasses of the given superclass from the file at the given URI. + /// + /// Returns a list of [ClassDeclaration]s. List getSubclassesFromFile( String superclassName, Uri fileUri, @@ -115,6 +140,9 @@ class CodeAnalyzer { .toList(); } + /// Gets the AST root of the file at the given URI. + /// + /// Returns a [CompilationUnit]. CompilationUnit _getFileAstRoot(Uri fileUri) { assert( FileSystemEntity.isFileSync( @@ -135,6 +163,9 @@ class CodeAnalyzer { return unit.unit; } + /// Converts the input URI to a normalized path string. + /// + /// This is a static utility method. static String getPath(dynamic inputUri) { return PhysicalResourceProvider.INSTANCE.pathContext.normalize( PhysicalResourceProvider.INSTANCE.pathContext.fromUri(inputUri), diff --git a/packages/runtime/lib/src/build.dart b/packages/runtime/lib/src/build.dart index bc248ef..03db955 100644 --- a/packages/runtime/lib/src/build.dart +++ b/packages/runtime/lib/src/build.dart @@ -11,16 +11,34 @@ import 'dart:convert'; import 'dart:io'; import 'dart:mirrors'; - import 'package:protevus_runtime/runtime.dart'; import 'package:io/io.dart'; import 'package:package_config/package_config.dart'; +/// A class responsible for building and compiling the application. +/// +/// This class handles the entire build process, including resolving ASTs, +/// generating runtime, compiling packages, and creating the final executable. class Build { + /// Creates a new [Build] instance with the given [BuildContext]. + /// + /// [context] is the build context containing necessary information for the build process. Build(this.context); + /// The build context for this build operation. final BuildContext context; + /// Executes the build process. + /// + /// This method performs the following steps: + /// 1. Resolves ASTs + /// 2. Generates runtime + /// 3. Compiles packages + /// 4. Prepares the build directory + /// 5. Fetches dependencies + /// 6. Compiles the final executable (if not for tests) + /// + /// Throws a [StateError] if any step fails. Future execute() async { final compilers = context.context.compilers; @@ -129,6 +147,10 @@ class Build { } } + /// Fetches dependencies for the project. + /// + /// This method runs 'dart pub get' with the '--offline' and '--no-precompile' flags. + /// It throws a [StateError] if the command fails. Future getDependencies() async { const String cmd = "dart"; @@ -147,6 +169,13 @@ class Build { } } + /// Compiles the source file to an executable. + /// + /// [srcUri] is the URI of the source file to compile. + /// [dstUri] is the URI where the compiled executable will be saved. + /// + /// This method runs 'dart compile exe' with verbose output. + /// It throws a [StateError] if the compilation fails. Future compile(Uri srcUri, Uri dstUri) async { final res = await Process.run( "dart", @@ -173,6 +202,13 @@ class Build { print("${res.stdout}"); } + /// Copies a package from source to destination. + /// + /// [srcUri] is the URI of the source package. + /// [dstUri] is the URI where the package will be copied. + /// + /// This method creates the destination directory if it doesn't exist, + /// copies the package contents, and handles Windows-specific file system issues. Future copyPackage(Uri srcUri, Uri dstUri) async { final dstDir = Directory.fromUri(dstUri); if (!dstDir.existsSync()) { @@ -206,6 +242,12 @@ class Build { ); } + /// Retrieves package information for a given compiler. + /// + /// [compiler] is the compiler instance to get package information for. + /// + /// This method uses reflection to find the source URI of the compiler + /// and then retrieves the corresponding package information. Future _getPackageInfoForCompiler(Compiler compiler) async { final compilerUri = reflect(compiler).type.location!.sourceUri; diff --git a/packages/runtime/lib/src/build_context.dart b/packages/runtime/lib/src/build_context.dart index 7032e5b..d55c472 100644 --- a/packages/runtime/lib/src/build_context.dart +++ b/packages/runtime/lib/src/build_context.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'dart:mirrors'; - import 'package:analyzer/dart/ast/ast.dart'; import 'package:protevus_runtime/runtime.dart'; import 'package:package_config/package_config.dart'; @@ -17,8 +16,18 @@ import 'package:path/path.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:yaml/yaml.dart'; -/// Configuration and context values used during [Build.execute]. +/// BuildContext provides configuration and context values used during the build process. +/// It encapsulates information about the application being compiled, build artifacts, +/// and provides utility methods for file and package management. class BuildContext { + /// Constructs a new BuildContext instance. + /// + /// [rootLibraryFileUri]: The URI of the root library file of the application being compiled. + /// [buildDirectoryUri]: The URI of the directory where build artifacts will be stored. + /// [executableUri]: The URI of the executable build product file. + /// [source]: The source script for the executable. + /// [environment]: Optional map of environment variables. + /// [forTests]: Boolean flag indicating whether this context is for tests (includes dev dependencies). BuildContext( this.rootLibraryFileUri, this.buildDirectoryUri, @@ -30,6 +39,12 @@ class BuildContext { analyzer = CodeAnalyzer(sourceApplicationDirectory.uri); } + /// Creates a BuildContext instance from a map of values. + /// + /// This factory constructor allows for easy serialization and deserialization of BuildContext objects. + /// + /// [map]: A map containing the necessary values to construct a BuildContext. + /// Returns: A new BuildContext instance. factory BuildContext.fromMap(Map map) { return BuildContext( Uri.parse(map['rootLibraryFileUri']), @@ -41,6 +56,10 @@ class BuildContext { ); } + /// Returns a map representation of the BuildContext with safe-to-serialize values. + /// + /// This getter is useful for serialization purposes, ensuring that all values are + /// in a format that can be easily serialized (e.g., converting URIs to strings). Map get safeMap => { 'rootLibraryFileUri': sourceLibraryFile.uri.toString(), 'buildDirectoryUri': buildDirectoryUri.toString(), @@ -50,41 +69,56 @@ class BuildContext { 'forTests': forTests }; + /// The CodeAnalyzer instance used for analyzing Dart code. late final CodeAnalyzer analyzer; - /// A [Uri] to the library file of the application to be compiled. + /// The URI of the root library file of the application being compiled. final Uri rootLibraryFileUri; - /// A [Uri] to the executable build product file. + /// The URI of the executable build product file. final Uri executableUri; - /// A [Uri] to directory where build artifacts are stored during the build process. + /// The URI of the directory where build artifacts are stored during the build process. final Uri buildDirectoryUri; /// The source script for the executable. final String source; - /// Whether dev dependencies of the application package are included in the dependencies of the compiled executable. + /// Indicates whether dev dependencies of the application package should be included + /// in the dependencies of the compiled executable. final bool forTests; + /// Cached PackageConfig instance. PackageConfig? _packageConfig; + /// Optional map of environment variables. final Map? environment; - /// The [RuntimeContext] available during the build process. + /// The current RuntimeContext, cast as a MirrorContext. + /// + /// This getter provides access to the runtime context available during the build process. MirrorContext get context => RuntimeContext.current as MirrorContext; + /// The URI of the target script file. + /// + /// If [forTests] is true, this will be a test file, otherwise it will be the main application file. Uri get targetScriptFileUri => forTests ? getDirectory(buildDirectoryUri.resolve("test/")) .uri .resolve("main_test.dart") : buildDirectoryUri.resolve("main.dart"); + /// The Pubspec of the source application. + /// + /// This getter parses and returns the pubspec.yaml file of the application being compiled. Pubspec get sourceApplicationPubspec => Pubspec.parse( File.fromUri(sourceApplicationDirectory.uri.resolve("pubspec.yaml")) .readAsStringSync(), ); + /// The pubspec of the source application as a map. + /// + /// This getter loads and returns the pubspec.yaml file as a YAML map. Map get sourceApplicationPubspecMap => loadYaml( File.fromUri( sourceApplicationDirectory.uri.resolve("pubspec.yaml"), @@ -101,26 +135,32 @@ class BuildContext { /// The directory where build artifacts are stored. Directory get buildDirectory => getDirectory(buildDirectoryUri); - /// The generated runtime directory + /// The generated runtime directory. Directory get buildRuntimeDirectory => getDirectory(buildDirectoryUri.resolve("generated_runtime/")); - /// Directory for compiled packages + /// Directory for compiled packages. Directory get buildPackagesDirectory => getDirectory(buildDirectoryUri.resolve("packages/")); - /// Directory for compiled application + /// Directory for the compiled application. Directory get buildApplicationDirectory => getDirectory( buildPackagesDirectory.uri.resolve("${sourceApplicationPubspec.name}/"), ); - /// Gets dependency package location relative to [sourceApplicationDirectory]. + /// Gets the dependency package configuration relative to [sourceApplicationDirectory]. + /// + /// This getter lazily loads and caches the package configuration. + /// Returns: A Future that resolves to a PackageConfig instance. Future get packageConfig async { return _packageConfig ??= (await findPackageConfig(sourceApplicationDirectory))!; } - /// Returns a [Directory] at [uri], creates it recursively if it doesn't exist. + /// Returns a [Directory] at the specified [uri], creating it recursively if it doesn't exist. + /// + /// [uri]: The URI of the directory. + /// Returns: A Directory instance. Directory getDirectory(Uri uri) { final dir = Directory.fromUri(uri); if (!dir.existsSync()) { @@ -129,7 +169,10 @@ class BuildContext { return dir; } - /// Returns a [File] at [uri], creates all parent directories recursively if necessary. + /// Returns a [File] at the specified [uri], creating all parent directories recursively if necessary. + /// + /// [uri]: The URI of the file. + /// Returns: A File instance. File getFile(Uri uri) { final file = File.fromUri(uri); if (!file.parent.existsSync()) { @@ -139,6 +182,11 @@ class BuildContext { return file; } + /// Retrieves a Package instance from a given URI. + /// + /// [uri]: The URI to resolve to a package. + /// Returns: A Future that resolves to a Package instance, or null if not found. + /// Throws: ArgumentError if the URI is not absolute or a package URI. Future getPackageFromUri(Uri? uri) async { if (uri == null) { return null; @@ -152,6 +200,13 @@ class BuildContext { return null; } + /// Retrieves import directives from a URI or source code. + /// + /// [uri]: The URI of the file to analyze. + /// [source]: The source code to analyze. + /// [alsoImportOriginalFile]: Whether to include an import for the original file. + /// Returns: A Future that resolves to a list of import directive strings. + /// Throws: ArgumentError for invalid combinations of parameters. Future> getImportDirectives({ Uri? uri, String? source, @@ -201,6 +256,10 @@ class BuildContext { return imports; } + /// Retrieves a ClassDeclaration from a given Type. + /// + /// [type]: The Type to analyze. + /// Returns: A Future that resolves to a ClassDeclaration, or null if not found. Future getClassDeclarationFromType(Type type) async { final classMirror = reflectType(type); Uri uri = classMirror.location!.sourceUri; @@ -214,6 +273,11 @@ class BuildContext { ); } + /// Retrieves a FieldDeclaration from a ClassMirror and property name. + /// + /// [type]: The ClassMirror to analyze. + /// [propertyName]: The name of the property to find. + /// Returns: A Future that resolves to a FieldDeclaration, or null if not found. Future _getField(ClassMirror type, String propertyName) { return getClassDeclarationFromType(type.reflectedType).then((cd) { try { @@ -229,6 +293,13 @@ class BuildContext { }); } + /// Retrieves annotations from a field of a given Type. + /// + /// This method searches for the field in the given type and its superclasses. + /// + /// [type1]: The Type to analyze. + /// [propertyName]: The name of the property to find. + /// Returns: A Future that resolves to a list of Annotations. Future> getAnnotationsFromField( Type type1, String propertyName, diff --git a/packages/runtime/lib/src/build_manager.dart b/packages/runtime/lib/src/build_manager.dart index 45b2214..7e34836 100644 --- a/packages/runtime/lib/src/build_manager.dart +++ b/packages/runtime/lib/src/build_manager.dart @@ -8,20 +8,31 @@ */ import 'dart:io'; - import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:protevus_isolate/isolate.dart'; import 'package:protevus_runtime/runtime.dart'; import 'package:io/io.dart'; +/// A class that represents an executable build process. +/// +/// This class extends the [Executable] class and is responsible for +/// executing a build process based on the provided build context. class BuildExecutable extends Executable { + /// Constructs a new [BuildExecutable] instance. + /// + /// [message] is a map containing the build context information. BuildExecutable(Map message) : super(message) { context = BuildContext.fromMap(message); } + /// The build context for this executable. late final BuildContext context; + /// Executes the build process. + /// + /// This method creates a new [Build] instance with the current context + /// and calls its execute method. @override Future execute() async { final build = Build(context); @@ -29,21 +40,33 @@ class BuildExecutable extends Executable { } } +/// A class that manages the build process for non-mirrored builds. class BuildManager { - /// Creates a new build manager to compile a non-mirrored build. + /// Creates a new [BuildManager] instance. + /// + /// [context] is the [BuildContext] for this build manager. BuildManager(this.context); + /// The build context for this manager. final BuildContext context; + /// Gets the URI of the source directory. Uri get sourceDirectoryUri => context.sourceApplicationDirectory.uri; + /// Performs the build process. + /// + /// This method handles the following steps: + /// 1. Creates the build directory if it doesn't exist. + /// 2. Creates a temporary copy of the script file with the main function stripped. + /// 3. Analyzes the script file and removes all main functions. + /// 4. Copies the 'not_tests' directory if it exists. + /// 5. Runs the build executable in an isolate. Future build() async { if (!context.buildDirectory.existsSync()) { context.buildDirectory.createSync(); } - // Here is where we need to provide a temporary copy of the script file with the main function stripped; - // this is because when the RuntimeGenerator loads, it needs Mirror access to any declarations in this file + // Create a temporary copy of the script file with the main function stripped var scriptSource = context.source; final strippedScriptFile = File.fromUri(context.targetScriptFileUri) ..writeAsStringSync(scriptSource); @@ -52,6 +75,7 @@ class BuildManager { final parsedUnit = analyzerContext.currentSession .getParsedUnit(analyzer.path) as ParsedUnitResult; + // Find and remove all main functions final mainFunctions = parsedUnit.unit.declarations .whereType() .where((f) => f.name.value() == "main") @@ -63,12 +87,14 @@ class BuildManager { strippedScriptFile.writeAsStringSync(scriptSource); + // Copy the 'not_tests' directory if it exists try { await copyPath( context.sourceApplicationDirectory.uri.resolve('test/not_tests').path, context.buildDirectoryUri.resolve('not_tests').path); } catch (_) {} + // Run the build executable in an isolate await IsolateExecutor.run( BuildExecutable(context.safeMap), packageConfigURI: @@ -81,6 +107,9 @@ class BuildManager { ); } + /// Cleans up the build directory. + /// + /// This method deletes the build directory and its contents if it exists. Future clean() async { if (context.buildDirectory.existsSync()) { context.buildDirectory.deleteSync(recursive: true); diff --git a/packages/runtime/lib/src/compiler.dart b/packages/runtime/lib/src/compiler.dart index c463395..8b39f36 100644 --- a/packages/runtime/lib/src/compiler.dart +++ b/packages/runtime/lib/src/compiler.dart @@ -8,32 +8,70 @@ */ import 'dart:io'; - import 'package:protevus_runtime/runtime.dart'; +/// An abstract class that defines the interface for compilers in the Protevus Platform. +/// +/// This class provides methods for modifying packages, compiling runtime objects, +/// and handling post-generation tasks. Implementations of this class are used to +/// remove dart:mirrors usage from packages and prepare them for runtime execution. abstract class Compiler { - /// Modifies a package on the filesystem in order to remove dart:mirrors from the package. + /// Modifies a package on the filesystem to remove dart:mirrors usage. /// - /// A copy of this compiler's package will be written to [destinationDirectory]. - /// This method is overridden to modify the contents of that directory - /// to remove all uses of dart:mirrors. + /// This method creates a copy of the compiler's package in the [destinationDirectory] + /// and modifies its contents to remove all uses of dart:mirrors. This is crucial for + /// preparing packages for environments where reflection is not available or desired. /// /// Packages should export their [Compiler] in their main library file and only /// import mirrors in files directly or transitively imported by the Compiler file. /// This method should remove that export statement and therefore remove all transitive mirror imports. + /// + /// [destinationDirectory] The directory where the modified package will be written. void deflectPackage(Directory destinationDirectory); - /// Returns a map of runtime objects that can be used at runtime while running in mirrored mode. + /// Compiles and returns a map of runtime objects for use in mirrored mode. + /// + /// This method is responsible for creating runtime representations of objects + /// that can be used when the application is running in mirrored mode. + /// + /// [context] The [MirrorContext] providing reflection capabilities for compilation. + /// + /// Returns a [Map] where keys are String identifiers and values are compiled Objects. Map compile(MirrorContext context); + /// A hook method called after package generation is complete. + /// + /// This method can be overridden to perform any necessary cleanup or additional + /// tasks after the package has been generated. + /// + /// [context] The [BuildContext] containing information about the build process. void didFinishPackageGeneration(BuildContext context) {} + /// Returns a list of URIs that need to be resolved during the build process. + /// + /// This method can be overridden to specify additional URIs that should be + /// resolved as part of the compilation process. + /// + /// [context] The [BuildContext] containing information about the build process. + /// + /// Returns a [List] of [Uri] objects to be resolved. List getUrisToResolve(BuildContext context) => []; } -/// Runtimes that generate source code implement this method. +/// An abstract class for compilers that generate source code. +/// +/// This class extends the functionality of [Compiler] to include +/// the ability to generate source code that represents the runtime behavior. abstract class SourceCompiler { - /// The source code, including directives, that declare a class that is equivalent in behavior to this runtime. + /// Generates source code that declares a class equivalent in behavior to this runtime. + /// + /// This method should be implemented to produce a string of Dart source code + /// that includes all necessary directives and class declarations to replicate + /// the behavior of the runtime in a static context. + /// + /// [ctx] The [BuildContext] containing information about the build process. + /// + /// Returns a [Future] that resolves to a [String] containing the generated source code. Future compile(BuildContext ctx) async { throw UnimplementedError(); } diff --git a/packages/runtime/lib/src/context.dart b/packages/runtime/lib/src/context.dart index 6d49164..3fe6607 100644 --- a/packages/runtime/lib/src/context.dart +++ b/packages/runtime/lib/src/context.dart @@ -10,20 +10,26 @@ import 'package:protevus_runtime/runtime.dart'; /// Contextual values used during runtime. +/// +/// This abstract class defines the structure for runtime contexts in the Protevus Platform. +/// It provides access to runtime objects and coercion functionality. abstract class RuntimeContext { /// The current [RuntimeContext] available to the executing application. /// - /// Is either a `MirrorContext` or a `GeneratedContext`, + /// This static property holds either a `MirrorContext` or a `GeneratedContext`, /// depending on the execution type. static final RuntimeContext current = instance; /// The runtimes available to the executing application. + /// + /// This property stores a collection of runtime objects that can be accessed + /// during the application's execution. late RuntimeCollection runtimes; - /// Gets a runtime object for [type]. + /// Gets a runtime object for the specified [type]. /// - /// Callers typically invoke this method, passing their [runtimeType] - /// in order to retrieve their runtime object. + /// Callers typically invoke this operator, passing their [runtimeType] + /// to retrieve their runtime object. /// /// It is important to note that a runtime object must exist for every /// class that extends a class that has a runtime. Use `MirrorContext.getSubclassesOf` when compiling. @@ -31,18 +37,44 @@ abstract class RuntimeContext { /// In other words, if the type `Base` has a runtime and the type `Subclass` extends `Base`, /// `Subclass` must also have a runtime. The runtime objects for both `Subclass` and `Base` /// must be the same type. + /// + /// @param type The Type to retrieve the runtime object for. + /// @return The runtime object associated with the specified type. dynamic operator [](Type type) => runtimes[type]; + /// Coerces the given [input] to the specified type [T]. + /// + /// This method is used to convert or cast the input to the desired type. + /// + /// @param input The input value to be coerced. + /// @return The coerced value of type [T]. T coerce(dynamic input); } +/// A collection of runtime objects indexed by type names. +/// +/// This class provides a way to store and retrieve runtime objects +/// associated with specific types. class RuntimeCollection { + /// Creates a new [RuntimeCollection] with the given [map]. + /// + /// @param map A map where keys are type names and values are runtime objects. RuntimeCollection(this.map); + /// The underlying map storing runtime objects. final Map map; + /// Returns an iterable of all runtime objects in the collection. Iterable get iterable => map.values; + /// Retrieves the runtime object for the specified type [t]. + /// + /// This operator first attempts to find an exact match for the type name. + /// If not found, it tries to match a generic type by removing type parameters. + /// + /// @param t The Type to retrieve the runtime object for. + /// @return The runtime object associated with the specified type. + /// @throws ArgumentError if no runtime object is found for the given type. Object operator [](Type t) { //todo: optimize by keeping a cache where keys are of type [Type] to avoid the // expensive indexOf and substring calls in this method @@ -67,10 +99,10 @@ class RuntimeCollection { } } -/// Prevents a type from being compiled when it otherwise would be. +/// An annotation to prevent a type from being compiled when it otherwise would be. /// -/// Annotate a type with the const instance of this type to prevent its -/// compilation. +/// Use this annotation on a type to exclude it from compilation. class PreventCompilation { + /// Creates a constant instance of [PreventCompilation]. const PreventCompilation(); } diff --git a/packages/runtime/lib/src/exceptions.dart b/packages/runtime/lib/src/exceptions.dart index 1ff625d..83cd428 100644 --- a/packages/runtime/lib/src/exceptions.dart +++ b/packages/runtime/lib/src/exceptions.dart @@ -7,13 +7,38 @@ * file that was distributed with this source code. */ +/// Exception thrown when type coercion fails. +/// +/// This exception is used to indicate that an attempt to convert a value +/// from one type to another has failed. It provides information about +/// the expected type and the actual type of the value. class TypeCoercionException implements Exception { + /// Creates a new [TypeCoercionException]. + /// + /// [expectedType] is the type that was expected for the conversion. + /// [actualType] is the actual type of the value that couldn't be converted. + /// + /// Example: + /// ```dart + /// throw TypeCoercionException(String, int); + /// ``` TypeCoercionException(this.expectedType, this.actualType); + /// The type that was expected for the conversion. final Type expectedType; + + /// The actual type of the value that couldn't be converted. final Type actualType; @override + + /// Returns a string representation of the exception. + /// + /// The returned string includes both the expected type and the actual type + /// to provide clear information about the nature of the coercion failure. + /// + /// Returns: + /// A string describing the type coercion exception. String toString() { return "input is not expected type '$expectedType' (input is '$actualType')"; } diff --git a/packages/runtime/lib/src/generator.dart b/packages/runtime/lib/src/generator.dart index 2545d04..447ecb0 100644 --- a/packages/runtime/lib/src/generator.dart +++ b/packages/runtime/lib/src/generator.dart @@ -10,16 +10,32 @@ import 'dart:async'; import 'dart:io'; +/// Token used to replace directives in the loader shell. const String _directiveToken = "___DIRECTIVES___"; + +/// Token used to replace assignments in the loader shell. const String _assignmentToken = "___ASSIGNMENTS___"; +/// A class responsible for generating runtime code. +/// +/// This class allows adding runtime elements and writing them to a specified directory. class RuntimeGenerator { + /// List of runtime elements to be generated. final _elements = <_RuntimeElement>[]; + /// Adds a new runtime element to the generator. + /// + /// [name] is the name of the runtime element. + /// [source] is the source code of the runtime element. void addRuntime({required String name, required String source}) { _elements.add(_RuntimeElement(name, source)); } + /// Writes the generated runtime code to the specified directory. + /// + /// [directoryUri] is the URI of the directory where the code will be written. + /// This method creates necessary directories, writes the library file, + /// pubspec file, and individual runtime element files. Future writeTo(Uri directoryUri) async { final dir = Directory.fromUri(directoryUri); final libDir = Directory.fromUri(dir.uri.resolve("lib/")); @@ -48,6 +64,7 @@ class RuntimeGenerator { }); } + /// Returns the content of the pubspec.yaml file as a string. String get pubspecSource => """ name: generated_runtime description: A runtime generated by package:conduit_runtime @@ -57,6 +74,9 @@ environment: sdk: '>=3.4.0 <4.0.0' """; + /// Returns the shell of the loader file as a string. + /// + /// This shell contains placeholders for directives and assignments. String get _loaderShell => """ import 'package:conduit_runtime/runtime.dart'; import 'package:conduit_runtime/slow_coerce.dart' as runtime_cast; @@ -80,12 +100,17 @@ class GeneratedContext extends RuntimeContext { } """; + /// Returns the complete source of the loader file. + /// + /// This method replaces the directive and assignment tokens in the loader shell + /// with the actual directives and assignments. String get loaderSource { return _loaderShell .replaceFirst(_directiveToken, _directives) .replaceFirst(_assignmentToken, _assignments); } + /// Generates the import directives for all runtime elements. String get _directives { final buf = StringBuffer(); @@ -98,6 +123,7 @@ class GeneratedContext extends RuntimeContext { return buf.toString(); } + /// Generates the assignments for all runtime elements. String get _assignments { final buf = StringBuffer(); @@ -109,14 +135,24 @@ class GeneratedContext extends RuntimeContext { } } +/// A class representing a single runtime element. class _RuntimeElement { + /// Creates a new runtime element. + /// + /// [typeName] is the name of the type for this runtime element. + /// [source] is the source code of this runtime element. _RuntimeElement(this.typeName, this.source); + /// The name of the type for this runtime element. final String typeName; + + /// The source code of this runtime element. final String source; + /// Returns the relative URI for this runtime element's file. Uri get relativeUri => Uri.file("${typeName.toLowerCase()}.dart"); + /// Returns the import alias for this runtime element. String get importAlias { return "g_${typeName.toLowerCase()}"; } diff --git a/packages/runtime/lib/src/mirror_coerce.dart b/packages/runtime/lib/src/mirror_coerce.dart index 557db7c..e0a2d51 100644 --- a/packages/runtime/lib/src/mirror_coerce.dart +++ b/packages/runtime/lib/src/mirror_coerce.dart @@ -8,9 +8,23 @@ */ import 'dart:mirrors'; - import 'package:protevus_runtime/runtime.dart'; +/// Attempts to cast an object to a specified type at runtime. +/// +/// This function uses Dart's mirror system to perform runtime type checking +/// and casting. It handles casting to List and Map types, including their +/// generic type arguments. +/// +/// Parameters: +/// - [object]: The object to be cast. +/// - [intoType]: A [TypeMirror] representing the type to cast into. +/// +/// Returns: +/// The object cast to the specified type. +/// +/// Throws: +/// - [TypeCoercionException] if the casting fails. Object runtimeCast(Object object, TypeMirror intoType) { final exceptionToThrow = TypeCoercionException(intoType.reflectedType, object.runtimeType); @@ -51,6 +65,16 @@ Object runtimeCast(Object object, TypeMirror intoType) { throw exceptionToThrow; } +/// Determines if a given type is fully primitive. +/// +/// A type is considered fully primitive if it's a basic type (num, String, bool), +/// dynamic, or a collection (List, Map) where all nested types are also primitive. +/// +/// Parameters: +/// - [type]: A [TypeMirror] representing the type to check. +/// +/// Returns: +/// true if the type is fully primitive, false otherwise. bool isTypeFullyPrimitive(TypeMirror type) { if (type == reflectType(dynamic)) { return true; diff --git a/packages/runtime/lib/src/mirror_context.dart b/packages/runtime/lib/src/mirror_context.dart index c4e94aa..e9c0cb7 100644 --- a/packages/runtime/lib/src/mirror_context.dart +++ b/packages/runtime/lib/src/mirror_context.dart @@ -8,12 +8,19 @@ */ import 'dart:mirrors'; - import 'package:protevus_runtime/runtime.dart'; +/// Global instance of the MirrorContext. RuntimeContext instance = MirrorContext._(); +/// A runtime context implementation using Dart's mirror system. +/// +/// This class provides runtime type information and compilation capabilities +/// using reflection. class MirrorContext extends RuntimeContext { + /// Private constructor to ensure singleton instance. + /// + /// Initializes the context by compiling all available runtimes. MirrorContext._() { final m = {}; @@ -31,6 +38,9 @@ class MirrorContext extends RuntimeContext { runtimes = RuntimeCollection(m); } + /// List of all class mirrors in the current mirror system. + /// + /// Excludes classes marked with @PreventCompilation. final List types = currentMirrorSystem() .libraries .values @@ -40,6 +50,9 @@ class MirrorContext extends RuntimeContext { .where((cm) => firstMetadataOfType(cm) == null) .toList(); + /// List of all available compilers. + /// + /// Returns instances of non-abstract classes that are subclasses of Compiler. List get compilers { return types .where((b) => b.isSubclassOf(reflectClass(Compiler)) && !b.isAbstract) @@ -47,6 +60,10 @@ class MirrorContext extends RuntimeContext { .toList(); } + /// Retrieves all non-abstract subclasses of a given type. + /// + /// [type] The base type to find subclasses of. + /// Returns a list of ClassMirror objects representing the subclasses. List getSubclassesOf(Type type) { final mirror = reflectClass(type); return types.where((decl) { @@ -68,6 +85,13 @@ class MirrorContext extends RuntimeContext { }).toList(); } + /// Coerces an input to a specified type T. + /// + /// Attempts to cast the input directly, and if that fails, + /// uses runtime casting. + /// + /// [input] The object to be coerced. + /// Returns the coerced object of type T. @override T coerce(dynamic input) { try { @@ -78,6 +102,11 @@ class MirrorContext extends RuntimeContext { } } +/// Retrieves the first metadata annotation of a specific type from a declaration. +/// +/// [dm] The DeclarationMirror to search for metadata. +/// [dynamicType] Optional TypeMirror to use instead of T. +/// Returns the first metadata of type T, or null if not found. T? firstMetadataOfType(DeclarationMirror dm, {TypeMirror? dynamicType}) { final tMirror = dynamicType ?? reflectType(T); try {