From f745547761c32df96b58c7c29ae80d538f7bc67a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 12 Jun 2024 22:40:16 +0800 Subject: [PATCH] Streamline nitrogen generator --- nitrogen/README.md | 58 ++---- nitrogen/lib/nitrogen.dart | 70 +++----- nitrogen/lib/nitrogen_extension.dart | 2 +- nitrogen/lib/src/configuration/assets.dart | 60 ------- .../lib/src/configuration/configuration.dart | 79 ++++---- .../configuration_exception.dart | 2 - nitrogen/lib/src/configuration/key.dart | 6 +- ...ic_generator.dart => asset_generator.dart} | 60 +++++-- .../generators/assets/standard_generator.dart | 73 -------- .../flutter_extension_generator.dart | 20 --- .../{assets => }/theme_generator.dart | 23 ++- .../lib/src/{generators => }/libraries.dart | 0 .../lib/src/lints/reserved_keyword_lint.dart | 73 ++++++++ nitrogen/lib/src/nitrogen_exception.dart | 2 + nitrogen/pubspec.yaml | 3 + .../test/src/configuration/assets_test.dart | 50 ------ .../src/configuration/configuration_test.dart | 83 +++++---- nitrogen/test/src/configuration/key_test.dart | 10 +- ...or_test.dart => asset_generator_test.dart} | 136 ++++++++++++-- .../assets/standard_generator_test.dart | 170 ------------------ .../flutter_extension_generator_test.dart | 91 ---------- .../{assets => }/theme_generator_test.dart | 2 +- .../src/{generators => }/libraries_test.dart | 0 .../src/lints/reserved_keyword_lint_test.dart | 73 ++++++++ nitrogen_types/lib/nitrogen_types.dart | 1 + .../lib/src}/image_asset_extension.dart | 20 +-- nitrogen_types/pubspec.yaml | 5 + 27 files changed, 493 insertions(+), 679 deletions(-) delete mode 100644 nitrogen/lib/src/configuration/assets.dart delete mode 100644 nitrogen/lib/src/configuration/configuration_exception.dart rename nitrogen/lib/src/generators/{assets/basic_generator.dart => asset_generator.dart} (65%) delete mode 100644 nitrogen/lib/src/generators/assets/standard_generator.dart delete mode 100644 nitrogen/lib/src/generators/extensions/flutter_extension_generator.dart rename nitrogen/lib/src/generators/{assets => }/theme_generator.dart (87%) rename nitrogen/lib/src/{generators => }/libraries.dart (100%) create mode 100644 nitrogen/lib/src/lints/reserved_keyword_lint.dart create mode 100644 nitrogen/lib/src/nitrogen_exception.dart delete mode 100644 nitrogen/test/src/configuration/assets_test.dart rename nitrogen/test/src/generators/{assets/basic_generator_test.dart => asset_generator_test.dart} (58%) delete mode 100644 nitrogen/test/src/generators/assets/standard_generator_test.dart delete mode 100644 nitrogen/test/src/generators/extensions/flutter_extension_generator_test.dart rename nitrogen/test/src/generators/{assets => }/theme_generator_test.dart (98%) rename nitrogen/test/src/{generators => }/libraries_test.dart (100%) create mode 100644 nitrogen/test/src/lints/reserved_keyword_lint_test.dart rename {nitrogen/lib/src/generators/extensions => nitrogen_types/lib/src}/image_asset_extension.dart (82%) diff --git a/nitrogen/README.md b/nitrogen/README.md index 0e233c6..ff95ece 100644 --- a/nitrogen/README.md +++ b/nitrogen/README.md @@ -49,20 +49,20 @@ Install the following: ```yaml dependencies: nitrogen_types: + nitrogen_flutter_svg: # Optional: include when using SVG images + nitrogen_lottie: # Optional: include when using Lottie animations dev_dependencies: build_runner: nitrogen: - nitrogen_flutter_svg: # Optional: include when using SVG images - nitrogen_lottie: # Optional: include when using Lottie animations ``` Alternatively: ```shell -dart pub add nitrogen_types - # Include nitrogen_flutter_svg and nitrogen_lottie when using SVG images and Lottie animations respectively. -dart pub add --dev build_runner nitrogen nitrogen_flutter_svg nitrogen_lottie +dart pub add nitrogen_types nitrogen_flutter_svg nitrogen_lottie + +dart pub add --dev build_runner nitrogen ``` To generate bindings: @@ -72,7 +72,7 @@ dart run build_runner build ## Configuration -Nitrogen's configuration can be set in the `nitrogen` section of your pubspec.yaml. For most cases, the default +Nitrogen's configuration can be set in the `nitrogen` section of your pubspec.yaml. In most cases, the default configuration works out of the box. ### Example @@ -82,12 +82,9 @@ A simple configuration looks like: nitrogen: package: true prefix: 'MyPrefix' - flutter-extension: true - asset-key: file - assets: - theme: - path: assets/themes - fallback: light + key: file + themes: + fallback: assets/themes/light ``` ### `package` @@ -109,11 +106,7 @@ class MyPrefixAssets { } ``` -### `flutter-extension` - -Optional. Defaults to `true`. Controls whether to generate the Flutter extension. - -### `asset-key` +### `key` Optional. Defaults to `file`. Controls the generated assets' key parameters. The following options are supported: @@ -123,33 +116,14 @@ Optional. Defaults to `file`. Controls the generated assets' key parameters. The | grpc-enum | parent directory and file name, without the extension | `assets/images/foo.png` | `IMAGES_FOO` | -### `assets` +### `themes` -Optional. Defaults to `standard`. Controls the structure of generated classes. The following options are supported: +Optional. Defaults to `null`. Controls whether to generate an additional `asset_themes.nitrogen.dart` file. Useful for +working with theme-specific assets. -#### Basic: - -Generates bare-bones classes without additional utilities. ```yaml nitrogen: - generation: basic -``` - -#### Standard (default): - -Generates classes with an additional `contents` map for working with assets in that directory. -```yaml -nitrogen: - generation: standard -``` - -#### Theme: - -Generates an additional `asset_themes.nitrogen.dart` file. Useful for working with theme-specific assets. -```yaml -nitrogen: - assets: - theme: - path: assets/themes # Path to themes, relative to package root. Assumes all themes are directly under assets/themes. - fallback: light # A fallback theme for when an asset is not specified, relative to 'path', i.e. assets/themes/light. + themes: + # Path to fallback theme, relative to package root. Assumes that all themes are under 'assets/themes'. + fallback: assets/themes/light ``` diff --git a/nitrogen/lib/nitrogen.dart b/nitrogen/lib/nitrogen.dart index 5aac29a..9068afe 100644 --- a/nitrogen/lib/nitrogen.dart +++ b/nitrogen/lib/nitrogen.dart @@ -2,14 +2,12 @@ import 'dart:async'; import 'package:build/build.dart'; import 'package:glob/glob.dart'; -import 'package:nitrogen/src/configuration/assets.dart'; import 'package:nitrogen/src/configuration/configuration.dart'; -import 'package:nitrogen/src/configuration/configuration_exception.dart'; import 'package:nitrogen/src/file_system.dart'; -import 'package:nitrogen/src/generators/assets/basic_generator.dart'; -import 'package:nitrogen/src/generators/assets/standard_generator.dart'; -import 'package:nitrogen/src/generators/assets/theme_generator.dart'; -import 'package:nitrogen/src/generators/extensions/flutter_extension_generator.dart'; +import 'package:nitrogen/src/generators/asset_generator.dart'; +import 'package:nitrogen/src/generators/theme_generator.dart'; +import 'package:nitrogen/src/lints/reserved_keyword_lint.dart'; +import 'package:nitrogen/src/nitrogen_exception.dart'; import 'package:nitrogen/src/walker.dart'; import 'package:path/path.dart'; import 'package:yaml/yaml.dart'; @@ -27,6 +25,8 @@ class NitrogenBuilder extends Builder { FutureOr build(BuildStep buildStep) async { try { final pubspec = loadYaml(await buildStep.readAsString(await buildStep.findAssets(_pubspec).first)); + + Configuration.lint(pubspec); final configuration = Configuration.parse(pubspec); final walker = Walker(configuration.package, configuration.flutterAssets, configuration.key.call); @@ -36,50 +36,35 @@ class NitrogenBuilder extends Builder { walker.walk(assets, asset); } - final assetsOutput = AssetId(buildStep.inputId.package, join('lib', 'src', 'assets.nitrogen.dart')); - switch (configuration.assets) { - case NoAssets _: - return; - - case BasicAssets _: - await buildStep.writeAsString( - assetsOutput, - BasicGenerator(configuration.prefix, assets).generate(), - ); + lintReservedKeyword(assets); - case StandardAssets _: - await buildStep.writeAsString( - assetsOutput, - StandardGenerator(configuration.prefix, assets, {}).generate(), - ); - - case ThemeAssets(:final path, :final fallback): - var themes = assets; - for (final segment in split(path).skip(1)) { - themes = themes.children[segment]! as AssetDirectory; - } - - final fallbackTheme = themes.children[fallback]! as AssetDirectory; + final assetsOutput = AssetId(buildStep.inputId.package, join('lib', 'src', 'assets.nitrogen.dart')); + if (configuration.fallbackTheme case final fallback?) { + var themes = assets; + var fallbackTheme = assets; + for (final segment in split(fallback).skip(1)) { + themes = fallbackTheme; + fallbackTheme = fallbackTheme.children[segment]! as AssetDirectory; + } - await buildStep.writeAsString( - assetsOutput, - StandardGenerator(configuration.prefix, assets, { themes }).generate(), - ); + await buildStep.writeAsString( + assetsOutput, + AssetGenerator(configuration.prefix, assets, { themes }).generate(), + ); - await buildStep.writeAsString( - AssetId(buildStep.inputId.package, join('lib', 'src', 'asset_themes.nitrogen.dart')), - ThemeGenerator(configuration.prefix, themes, fallbackTheme).generate(), - ); - } + await buildStep.writeAsString( + AssetId(buildStep.inputId.package, join('lib', 'src', 'asset_themes.nitrogen.dart')), + ThemeGenerator(configuration.prefix, themes, fallbackTheme).generate(), + ); - if (configuration.flutterExtension) { + } else { await buildStep.writeAsString( - AssetId(buildStep.inputId.package, join('lib', 'src', 'flutter_extension.nitrogen.dart')), - FlutterExtensionGenerator().generate(), + assetsOutput, + AssetGenerator(configuration.prefix, assets, {}).generate(), ); } - } on ConfigurationException { + } on NitrogenException { return; } } @@ -89,7 +74,6 @@ class NitrogenBuilder extends Builder { r'$package$': [ 'lib/src/assets.nitrogen.dart', 'lib/src/asset_themes.nitrogen.dart', - 'lib/src/flutter_extension.nitrogen.dart' ], }; diff --git a/nitrogen/lib/nitrogen_extension.dart b/nitrogen/lib/nitrogen_extension.dart index 4a1871d..d669235 100644 --- a/nitrogen/lib/nitrogen_extension.dart +++ b/nitrogen/lib/nitrogen_extension.dart @@ -1 +1 @@ -export 'package:nitrogen/src/generators/libraries.dart'; +export 'package:nitrogen/src/libraries.dart'; diff --git a/nitrogen/lib/src/configuration/assets.dart b/nitrogen/lib/src/configuration/assets.dart deleted file mode 100644 index b5b35b8..0000000 --- a/nitrogen/lib/src/configuration/assets.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:build/build.dart'; -import 'package:nitrogen/src/configuration/configuration_exception.dart'; -import 'package:yaml/yaml.dart'; - -/// The generation type. -sealed class Assets { - - /// Parses the `assets` node if valid. - static Assets parse(YamlNode? generation) { - switch (generation?.value) { - case 'none': - return NoAssets(); - - case 'basic': - return BasicAssets(); - - case 'standard' || null: - return StandardAssets(); - - case { 'theme': { 'fallback': final String fallback, 'path': final String path } }: - return ThemeAssets(fallback: fallback, path: path); - - case _: - log.severe(generation!.span.message('Unable to configure asset generation. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#assets.')); - throw ConfigurationException(); - } - } - -} - -/// Represents no generation. -final class NoAssets extends Assets {} - -/// Represents basic generation. -final class BasicAssets extends Assets {} - -/// Represents standard generation. -final class StandardAssets extends Assets {} - -/// Represents theme generation. -final class ThemeAssets extends Assets { - /// The fallback theme. - final String fallback; - /// The path to the themes folder, relative to the package root. - final String path; - - /// Creates a [ThemeAssets]. - ThemeAssets({required this.fallback, required this.path}); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is ThemeAssets && runtimeType == other.runtimeType && fallback == other.fallback && path == other.path; - - @override - int get hashCode => fallback.hashCode ^ path.hashCode; - - @override - String toString() => 'ThemeAssets{fallback: $fallback, path: $path}'; -} diff --git a/nitrogen/lib/src/configuration/configuration.dart b/nitrogen/lib/src/configuration/configuration.dart index fbbf46a..ceb4aab 100644 --- a/nitrogen/lib/src/configuration/configuration.dart +++ b/nitrogen/lib/src/configuration/configuration.dart @@ -1,24 +1,39 @@ import 'package:build/build.dart'; import 'package:meta/meta.dart'; -import 'package:nitrogen/src/configuration/assets.dart'; -import 'package:nitrogen/src/configuration/configuration_exception.dart'; +import 'package:nitrogen/src/nitrogen_exception.dart'; import 'package:nitrogen/src/configuration/key.dart'; import 'package:yaml/yaml.dart'; /// Nitrogen's configuration. final class Configuration { + /// The Nitrogen section's valid keys. + static const keys = { 'package', 'prefix', 'key', 'themes' }; + + /// Lints the pubspec. + static void lint(YamlMap? pubspec) { + final nitrogen = pubspec?.nodes['nitrogen'].as(); + + for (final key in {...?nitrogen?.keys }..removeAll(keys)) { + log.warning(nitrogen?.nodes[key]!.span.message('Unknown key, "$key", in pubspec.yaml\'s nitrogen section. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#configuration for valid configuration options.')); + } + + final themes = nitrogen?.nodes['themes'].as(); + for (final key in {...?themes?.keys }..removeAll({ 'fallback' })) { + log.warning(themes?.nodes[key]!.span.message('Unknown key, "$key", in pubspec.yaml\'s nitrogen section. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#themes for valid configuration options.')); + } + } + + /// Parses the configuration from the project's pubspec.yaml. factory Configuration.parse(YamlMap pubspec) { final nitrogen = pubspec.nodes['nitrogen'].as(); - final assets = Assets.parse(nitrogen?.nodes['assets']); return Configuration( package: parsePackage(pubspec.nodes['name'], nitrogen?.nodes['package']), prefix: parsePrefix(nitrogen?.nodes['prefix']), - flutterExtension: parseFlutterExtension(nitrogen?.nodes['flutter-extension']), - key: Key.parse(nitrogen?.nodes['asset-key']), - assets: assets, - flutterAssets: parseFlutterAssets(assets, pubspec.nodes['flutter'].as()?.nodes['assets']), + key: Key.parse(nitrogen?.nodes['key']), + fallbackTheme: parseFallbackTheme(nitrogen?.nodes['themes']), + flutterAssets: parseFlutterAssets(pubspec.nodes['flutter'].as()?.nodes['assets']), ); } @@ -30,15 +45,15 @@ final class Configuration { return name; case (_, true): - log.severe(name?.span.message("Unable to read package name from project's pubspec.yaml. See https://dart.dev/tools/pub/pubspec#name.") ?? 'Package name is not specified.'); - throw ConfigurationException(); + log.severe(name?.span.message('Unable to read package name in pubspec.yaml. See https://dart.dev/tools/pub/pubspec#name.') ?? 'Package name is not specified.'); + throw NitrogenException(); case (_, bool? _): return null; default: - log.severe(enabled?.span.message('Unable to configure package name. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#package.')); - throw ConfigurationException(); + log.severe(enabled?.span.message('Unable to read package name. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#package.')); + throw NitrogenException(); } } @@ -50,50 +65,55 @@ final class Configuration { return prefix ?? ''; default: - log.severe(node!.span.message('Unable to configure class prefix. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#prefix.')); - throw ConfigurationException(); + log.severe(node!.span.message('Unable to read prefix. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#prefix.')); + throw NitrogenException(); } } - /// Parses the flutter extension from the nitrogen section in the project's pubspec.yaml. + /// Parses the path to the fallback theme. @visibleForTesting - static bool parseFlutterExtension(YamlNode? node) { + static String? parseFallbackTheme(YamlNode? node) { switch (node?.value) { - case final bool? extension: - return extension ?? true; + case null: + return null; - default: - log.severe(node!.span.message('Unable to configure flutter extension. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#flutter-extension.')); - throw ConfigurationException(); + case { 'fallback': final String fallback }: + return fallback; + + case _: + log.severe(node!.span.message('Unable to read themes. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#themes.')); + throw NitrogenException(); } } /// Parses the flutter assets from the project's pubspec.yaml. @visibleForTesting - static Set parseFlutterAssets(Assets assets, YamlNode? node) { + static Set parseFlutterAssets(YamlNode? node) { switch (node) { - case _ when node == null || assets is NoAssets: + case null: return {}; case final YamlList list: return { ...list }; default: - log.severe(node.span.message('Unable to parse flutter assets. See https://docs.flutter.dev/tools/pubspec#assets.')); - throw ConfigurationException(); + log.severe(node.span.message('Unable to read flutter assets. See https://docs.flutter.dev/tools/pubspec#assets.')); + throw NitrogenException(); } } /// The package name. final String? package; + /// The class prefix. final String prefix; - /// Whether to generate the flutter extension. - final bool flutterExtension; + /// The function for generating asset keys. final String Function(List) key; - /// The assets configuration. - final Assets assets; + + /// The path to the fallback theme. + final String? fallbackTheme; + /// The flutter assets. final Set flutterAssets; @@ -101,9 +121,8 @@ final class Configuration { Configuration({ required this.package, required this.prefix, - required this.flutterExtension, required this.key, - required this.assets, + required this.fallbackTheme, required this.flutterAssets, }); diff --git a/nitrogen/lib/src/configuration/configuration_exception.dart b/nitrogen/lib/src/configuration/configuration_exception.dart deleted file mode 100644 index 629900e..0000000 --- a/nitrogen/lib/src/configuration/configuration_exception.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// An exception thrown when there is an error in the configuration. -class ConfigurationException implements Exception {} diff --git a/nitrogen/lib/src/configuration/key.dart b/nitrogen/lib/src/configuration/key.dart index 26c220a..2dec369 100644 --- a/nitrogen/lib/src/configuration/key.dart +++ b/nitrogen/lib/src/configuration/key.dart @@ -1,5 +1,5 @@ import 'package:build/build.dart'; -import 'package:nitrogen/src/configuration/configuration_exception.dart'; +import 'package:nitrogen/src/nitrogen_exception.dart'; import 'package:path/path.dart'; import 'package:sugar/sugar.dart'; import 'package:yaml/yaml.dart'; @@ -17,8 +17,8 @@ extension Key on Never { return fileName; default: - log.severe(key!.span.message('Unable to configure asset key. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#asset-key.')); - throw ConfigurationException(); + log.severe(key!.span.message('Unable to read asset key. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#key.')); + throw NitrogenException(); } } diff --git a/nitrogen/lib/src/generators/assets/basic_generator.dart b/nitrogen/lib/src/generators/asset_generator.dart similarity index 65% rename from nitrogen/lib/src/generators/assets/basic_generator.dart rename to nitrogen/lib/src/generators/asset_generator.dart index 5e485c8..253b719 100644 --- a/nitrogen/lib/src/generators/assets/basic_generator.dart +++ b/nitrogen/lib/src/generators/asset_generator.dart @@ -1,18 +1,23 @@ import 'package:code_builder/code_builder.dart'; import 'package:nitrogen/src/file_system.dart'; -import 'package:nitrogen/src/generators/libraries.dart'; +import 'package:nitrogen/src/libraries.dart'; +import 'package:nitrogen_types/nitrogen_types.dart'; import 'package:sugar/sugar.dart'; -/// A generator for basic class representations of asset directories. -class BasicGenerator { +/// A generator for standard class representations of asset directories. +class AssetGenerator { - final BasicClass _basicClass; + final AssetClass _standardClass; final AssetDirectory _assets; + final Set _excluded; - /// Creates a [BasicGenerator]. - BasicGenerator(String prefix, this._assets): - _basicClass = BasicClass(directories: AssetDirectoryExpressions(prefix)); + /// Creates a [AssetGenerator]. + AssetGenerator(String prefix, this._assets, this._excluded): + _standardClass = AssetClass( + directories: AssetDirectoryExpressions(prefix), + excluded: _excluded, + ); /// Generates basic asset classes. String generate() { @@ -28,17 +33,47 @@ class BasicGenerator { } void _generate(AssetDirectory directory, List classes, {bool static = false}) { - classes.add(_basicClass.generate(directory, static: static).build()); - for (final child in directory.children.values.whereType()) { + classes.add(_standardClass.generate(directory, static: static).build()); + for (final child in directory.children.values.whereType().where((d) => !_excluded.contains(d))) { _generate(child, classes); } } } +/// Contains functions for generating a standard class representation of a directory. +class AssetClass extends BasicAssetClass { + + /// An [Asset] type. + static final assetType = refer('Asset', 'package:nitrogen_types/nitrogen_types.dart'); + + /// Creates a [AssetClass]. + const AssetClass({required super.directories, super.excluded, super.files}); + + /// Generates a basic class that contains only nested folders and assets. + @override + ClassBuilder generate(AssetDirectory directory, {bool static = false}) => super.generate(directory, static: static) + ..methods.add(_contents(directory, static: static)); + + /// Returns a + Method _contents(AssetDirectory directory, {bool static = false}) => Method((builder) => builder + ..static = static + ..returns = TypeReference((builder) => builder..symbol = 'Map'..types.addAll([refer('String'), assetType])) + ..type = MethodType.getter + ..name = 'contents' + ..lambda = true + ..body = Block.of([ + const Code('const {'), + for (final file in directory.children.values.whereType()) + Block.of([literal(file.asset.key).code, const Code(': '), files.invocation(file).code, const Code(', ')]), + const Code('}'), + ]) + ); + +} /// Contains functions for generating a basic class representation of a directory. -class BasicClass { +class BasicAssetClass { /// The excluded directories. final Set excluded; @@ -47,14 +82,13 @@ class BasicClass { /// The file expressions. final AssetFileExpressions files; - /// Creates a [BasicClass]. - const BasicClass({ + /// Creates a [BasicAssetClass]. + const BasicAssetClass({ required this.directories, this.excluded = const {}, this.files = const AssetFileExpressions(), }); - /// Generates a basic class that contains only nested folders and assets. ClassBuilder generate(AssetDirectory directory, {bool static = false}) => ClassBuilder() ..name = directories.type(directory).symbol! diff --git a/nitrogen/lib/src/generators/assets/standard_generator.dart b/nitrogen/lib/src/generators/assets/standard_generator.dart deleted file mode 100644 index 46835df..0000000 --- a/nitrogen/lib/src/generators/assets/standard_generator.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:code_builder/code_builder.dart'; -import 'package:nitrogen/src/file_system.dart'; -import 'package:nitrogen/src/generators/assets/basic_generator.dart'; -import 'package:nitrogen/src/generators/libraries.dart'; -import 'package:nitrogen_types/nitrogen_types.dart'; - - -/// A generator for standard class representations of asset directories. -class StandardGenerator { - - final StandardClass _standardClass; - final AssetDirectory _assets; - final Set _excluded; - - /// Creates a [StandardGenerator]. - StandardGenerator(String prefix, this._assets, this._excluded): - _standardClass = StandardClass( - directories: AssetDirectoryExpressions(prefix), - excluded: _excluded, - ); - - /// Generates basic asset classes. - String generate() { - final classes = []; - _generate(_assets, classes, static: true); - - final library = LibraryBuilder() - ..directives.add(Libraries.importNitrogenTypes) - ..body.add(Libraries.header()) - ..body.addAll(classes); - - return library.build().format(); - } - - void _generate(AssetDirectory directory, List classes, {bool static = false}) { - classes.add(_standardClass.generate(directory, static: static).build()); - for (final child in directory.children.values.whereType().where((d) => !_excluded.contains(d))) { - _generate(child, classes); - } - } - -} - -/// Contains functions for generating a standard class representation of a directory. -class StandardClass extends BasicClass { - - /// An [Asset] type. - static final assetType = refer('Asset', 'package:nitrogen_types/nitrogen_types.dart'); - - /// Creates a [StandardClass]. - const StandardClass({required super.directories, super.excluded, super.files}); - - @override - ClassBuilder generate(AssetDirectory directory, {bool static = false}) => - super.generate(directory, static: static) - ..methods.add(_contents(directory, static: static)); - - - Method _contents(AssetDirectory directory, {bool static = false}) => Method((builder) => builder - ..static = static - ..returns = TypeReference((builder) => builder..symbol = 'Map'..types.addAll([refer('String'), assetType])) - ..type = MethodType.getter - ..name = 'contents' - ..lambda = true - ..body = Block.of([ - const Code('const {'), - for (final file in directory.children.values.whereType()) - Block.of([literal(file.asset.key).code, const Code(': '), files.invocation(file).code, const Code(', ')]), - const Code('}'), - ]) - ); - -} diff --git a/nitrogen/lib/src/generators/extensions/flutter_extension_generator.dart b/nitrogen/lib/src/generators/extensions/flutter_extension_generator.dart deleted file mode 100644 index 86db5d0..0000000 --- a/nitrogen/lib/src/generators/extensions/flutter_extension_generator.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:code_builder/code_builder.dart'; -import 'package:nitrogen/src/generators/extensions/image_asset_extension.dart'; -import 'package:nitrogen/src/generators/libraries.dart'; -import 'package:nitrogen_types/nitrogen_types.dart'; - -/// A generator for Flutter extensions on [Asset]s. -class FlutterExtensionGenerator { - - /// Generates extensions for interfacing between assets and Flutter. - String generate() { - final library = LibraryBuilder() - ..directives.add(Libraries.importNitrogenTypes) - ..directives.addAll(Image.imports) - ..body.add(Libraries.header()) - ..body.add(Image.extension); - - return library.build().format(); - } - -} diff --git a/nitrogen/lib/src/generators/assets/theme_generator.dart b/nitrogen/lib/src/generators/theme_generator.dart similarity index 87% rename from nitrogen/lib/src/generators/assets/theme_generator.dart rename to nitrogen/lib/src/generators/theme_generator.dart index be565d9..428f8ec 100644 --- a/nitrogen/lib/src/generators/assets/theme_generator.dart +++ b/nitrogen/lib/src/generators/theme_generator.dart @@ -2,9 +2,8 @@ import 'dart:collection'; import 'package:code_builder/code_builder.dart'; import 'package:nitrogen/src/file_system.dart'; -import 'package:nitrogen/src/generators/assets/basic_generator.dart'; -import 'package:nitrogen/src/generators/assets/standard_generator.dart'; -import 'package:nitrogen/src/generators/libraries.dart'; +import 'package:nitrogen/src/generators/asset_generator.dart'; +import 'package:nitrogen/src/libraries.dart'; import 'package:sugar/sugar.dart'; @@ -16,8 +15,8 @@ class ThemeGenerator { static int _name((String, int) a, (String, int) b) => a.$1.compareTo(b.$1); final ThemeExtension _extension; - final StandardClass _fallbackClass; - final StandardClass _themeSubclass; + final AssetClass _fallbackClass; + final AssetClass _themeSubclass; final ThemeClass _themeClass; final AssetDirectory _themes; final AssetDirectory _fallbackTheme; @@ -25,8 +24,8 @@ class ThemeGenerator { /// Creates a [ThemeGenerator]. ThemeGenerator(String prefix, this._themes, this._fallbackTheme): _extension = ThemeExtension(prefix), - _fallbackClass = StandardClass(directories: FallbackAssetDirectoryExpressions(prefix, _fallbackTheme)), - _themeSubclass = StandardClass(directories: ThemeAssetDirectoryExpressions(prefix, _themes)), + _fallbackClass = AssetClass(directories: FallbackAssetDirectoryExpressions(prefix, _fallbackTheme)), + _themeSubclass = AssetClass(directories: ThemeAssetDirectoryExpressions(prefix, _themes)), _themeClass = ThemeClass(ThemeAssetDirectoryExpressions(prefix, _themes)); /// Generates themed asset classes. @@ -107,20 +106,20 @@ class ThemeExtension { /// Contains functions for generating a theme class representation of a directory. class ThemeClass { - final BasicClass _basic; + final BasicAssetClass _assets; /// Creates a [ThemeClass]. - ThemeClass(AssetDirectoryExpressions directories): _basic = BasicClass(directories: directories); + ThemeClass(AssetDirectoryExpressions directories): _assets = BasicAssetClass(directories: directories); /// Generates a theme class that extends [fallback]. - ClassBuilder generate(AssetDirectory directory, Class fallback) => _basic.generate(directory) + ClassBuilder generate(AssetDirectory directory, Class fallback) => _assets.generate(directory) ..extend = refer(fallback.name) ..modifier = ClassModifier.final$ ..methods.add(_contents(directory, fallback)); Method _contents(AssetDirectory directory, Class fallback, {bool static = false}) => Method((builder) => builder ..static = static - ..returns = TypeReference((builder) => builder..symbol = 'Map'..types.addAll([refer('String'), StandardClass.assetType])) + ..returns = TypeReference((builder) => builder..symbol = 'Map'..types.addAll([refer('String'), AssetClass.assetType])) ..type = MethodType.getter ..name = 'contents' ..lambda = true @@ -128,7 +127,7 @@ class ThemeClass { const Code('Map.unmodifiable({'), Code('...${static ? fallback.name : 'const ${fallback.name}()'}.contents,'), for (final file in directory.children.values.whereType()) - Block.of([literal(file.asset.key).code, const Code(': '), _basic.files.invocation(file).code, const Code(', ')]), + Block.of([literal(file.asset.key).code, const Code(': '), _assets.files.invocation(file).code, const Code(', ')]), const Code('})'), ]) ); diff --git a/nitrogen/lib/src/generators/libraries.dart b/nitrogen/lib/src/libraries.dart similarity index 100% rename from nitrogen/lib/src/generators/libraries.dart rename to nitrogen/lib/src/libraries.dart diff --git a/nitrogen/lib/src/lints/reserved_keyword_lint.dart b/nitrogen/lib/src/lints/reserved_keyword_lint.dart new file mode 100644 index 0000000..8990fa1 --- /dev/null +++ b/nitrogen/lib/src/lints/reserved_keyword_lint.dart @@ -0,0 +1,73 @@ +import 'package:build/build.dart'; +import 'package:nitrogen/src/file_system.dart'; +import 'package:nitrogen/src/nitrogen_exception.dart'; + +const _dartKeywords = { + 'assert', + 'await', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'default', + 'do', + 'else', + 'enum', + 'extends', + 'false', + 'final', + 'finally', + 'for', + 'Function', + 'if', + 'in', + 'is', + 'new', + 'null', + 'rethrow', + 'return', + 'sealed', + 'super', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'var', + 'void', + 'with', + 'while', + 'yield', +}; + +const _nitrogenKeywords = { 'contents' }; + +/// Lints the assets' paths for reserved keywords. +void lintReservedKeyword(AssetDirectory directory) { + if (_lintReservedKeyword(directory)) { + throw NitrogenException(); + } +} + +bool _lintReservedKeyword(AssetDirectory directory) { + var error = false; + for (final entity in directory.children.values) { + switch (entity.rawName) { + case _ when _dartKeywords.contains(entity.rawName): + log.severe('"${entity.path.join('/')}" contains a reserved keyword. Please rename the directory/file. See https://dart.dev/language/keywords.'); + error = true; + + case _ when _nitrogenKeywords.contains(entity.rawName): + log.severe('"${entity.path.join('/')}" contains a reserved identifier. Please rename the directory/file. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#reserved-keywords.'); + error = true; + } + + if (entity is AssetDirectory) { + error |= _lintReservedKeyword(entity); + } + } + + return error; +} diff --git a/nitrogen/lib/src/nitrogen_exception.dart b/nitrogen/lib/src/nitrogen_exception.dart new file mode 100644 index 0000000..6429432 --- /dev/null +++ b/nitrogen/lib/src/nitrogen_exception.dart @@ -0,0 +1,2 @@ +/// An exception thrown when there is an error when generating assets. +class NitrogenException implements Exception {} diff --git a/nitrogen/pubspec.yaml b/nitrogen/pubspec.yaml index 41c24f7..d5714e4 100644 --- a/nitrogen/pubspec.yaml +++ b/nitrogen/pubspec.yaml @@ -8,12 +8,15 @@ repository: https://github.com/forus-labs/cauldron/ environment: sdk: '>=3.3.0 <4.0.0' + flutter: ">=3.3.0" dependencies: build: ^2.4.1 build_runner: ^2.4.6 code_builder: ^4.10.0 dart_style: ^2.3.6 + flutter: + sdk: flutter glob: ^2.1.2 meta: ^1.9.1 nitrogen_types: ^0.2.0 diff --git a/nitrogen/test/src/configuration/assets_test.dart b/nitrogen/test/src/configuration/assets_test.dart deleted file mode 100644 index 44285f1..0000000 --- a/nitrogen/test/src/configuration/assets_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:build/build.dart'; -import 'package:build_test/build_test.dart'; -import 'package:nitrogen/src/configuration/assets.dart'; -import 'package:nitrogen/src/configuration/configuration_exception.dart'; -import 'package:test/test.dart'; -import 'package:yaml/yaml.dart'; - -// ignore_for_file: avoid_dynamic_calls - -const _theme = ''' -assets: - theme: - fallback: some-theme - path: assets/path/to/themes -'''; - -void main() { - group('parse(...)', () { - test('none', () { - expect(Assets.parse(loadYaml('assets: none').nodes['assets']), isA()); - }); - - test('basic', () { - expect(Assets.parse(loadYaml('assets: basic').nodes['assets']), isA()); - }); - - test('standard', () { - expect(Assets.parse(loadYaml('assets: standard').nodes['assets']), isA()); - }); - - test('null', () { - expect(Assets.parse(null), isA()); - }); - - test('theme', () { - expect( - Assets.parse(loadYaml(_theme).nodes['assets']), - ThemeAssets(fallback: 'some-theme', path: 'assets/path/to/themes'), - ); - }); - - test('invalid', () { - expectLater( - log.onRecord, - emits(severeLogOf(contains('Unable to configure asset generation. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#assets.'))), - ); - expect(() => Assets.parse(loadYaml('assets: invalid').nodes['assets']), throwsA(isA())); - }); - }); -} diff --git a/nitrogen/test/src/configuration/configuration_test.dart b/nitrogen/test/src/configuration/configuration_test.dart index d3db17e..fcca49d 100644 --- a/nitrogen/test/src/configuration/configuration_test.dart +++ b/nitrogen/test/src/configuration/configuration_test.dart @@ -1,14 +1,30 @@ import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; -import 'package:nitrogen/src/configuration/assets.dart'; import 'package:nitrogen/src/configuration/configuration.dart'; -import 'package:nitrogen/src/configuration/configuration_exception.dart'; import 'package:nitrogen/src/configuration/key.dart'; +import 'package:nitrogen/src/nitrogen_exception.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; // ignore_for_file: avoid_dynamic_calls +const _invalid = ''' +name: hi +flutter: + assets: + - path/to/first/ + - path/to/second/ +nitrogen: + package: true + prefix: 'MyPrefix' + key: grpc-enum + invalid-nitrogen: true + themes: + fallback: true + invalid-theme: true +'''; + + const _pubspec = ''' name: hi flutter: @@ -18,9 +34,9 @@ flutter: nitrogen: package: true prefix: 'MyPrefix' - flutter-extension: true - asset-key: grpc-enum - assets: basic + key: grpc-enum + themes: + fallback: assets/path/to/first/fallback '''; const _noNitrogen = ''' @@ -38,15 +54,28 @@ assets: '''; void main() { + group('lint(...)', () { + test('invalid pubspec', () { + expectLater( + log.onRecord, + emitsInOrder([ + warningLogOf(contains('Unknown key, "invalid-nitrogen", in pubspec.yaml\'s nitrogen section. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#configuration for valid configuration options.')), + warningLogOf(contains('Unknown key, "invalid-theme", in pubspec.yaml\'s nitrogen section. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#themes for valid configuration options.')), + ]), + ); + + Configuration.lint(loadYaml(_invalid)); + }); + }); + group('parse(...)', () { test('valid pubspec', () { final configuration = Configuration.parse(loadYaml(_pubspec)); expect(configuration.package, 'hi'); expect(configuration.prefix, 'MyPrefix'); - expect(configuration.flutterExtension, true); expect(configuration.key, Key.grpcEnum); - expect(configuration.assets, isA()); + expect(configuration.fallbackTheme, 'assets/path/to/first/fallback'); expect(configuration.flutterAssets, { 'path/to/first/', 'path/to/second/' }); }); @@ -55,9 +84,8 @@ void main() { expect(configuration.package, null); expect(configuration.prefix, ''); - expect(configuration.flutterExtension, true); expect(configuration.key, Key.fileName); - expect(configuration.assets, isA()); + expect(configuration.fallbackTheme, null); expect(configuration.flutterAssets, { 'path/to/first/', 'path/to/second/' }); }); }); @@ -68,11 +96,11 @@ void main() { test('use package name & invalid name', () { expectLater( log.onRecord, - emits(severeLogOf(contains("Unable to read package name from project's pubspec.yaml. See https://dart.dev/tools/pub/pubspec#name."))), + emits(severeLogOf(contains('Unable to read package name in pubspec.yaml. See https://dart.dev/tools/pub/pubspec#name.'))), ); expect( () => Configuration.parsePackage(loadYamlNode('true'), loadYamlNode('true')), - throwsA(isA()), + throwsA(isA()), ); }); @@ -83,11 +111,11 @@ void main() { test('invalid package usage', () { expectLater( log.onRecord, - emits(severeLogOf(contains('Unable to configure package name. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#package.'))), + emits(severeLogOf(contains('Unable to read package name. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#package.'))), ); expect( () => Configuration.parsePackage(loadYamlNode('nitrogen'), loadYamlNode('invalid')), - throwsA(isA()), + throwsA(isA()), ); }); }); @@ -100,50 +128,43 @@ void main() { test('invalid', () { expectLater( log.onRecord, - emits(severeLogOf(contains('Unable to configure class prefix. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#prefix.'))), + emits(severeLogOf(contains('Unable to read prefix. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#prefix.'))), ); - expect(() => Configuration.parsePrefix(loadYamlNode('true')), throwsA(isA())); + expect(() => Configuration.parsePrefix(loadYamlNode('true')), throwsA(isA())); }); }); - group('parseFlutterExtension(...)', () { - test('true', () => expect(Configuration.parseFlutterExtension(loadYamlNode('true')), true)); - - test('false', () => expect(Configuration.parseFlutterExtension(loadYamlNode('false')), false)); + group('parseFallbackTheme(...)', () { + test('fallback theme', () => expect(Configuration.parseFallbackTheme(loadYamlNode('fallback: assets/path/to/fallback')), 'assets/path/to/fallback')); - test('null', () => expect(Configuration.parseFlutterExtension(null), true)); + test('null', () => expect(Configuration.parseFallbackTheme(null), null)); test('invalid', () { expectLater( log.onRecord, - emits(severeLogOf(contains('Unable to configure flutter extension. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#flutter-extension.'))), + emits(severeLogOf(contains('Unable to read themes. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#themes.'))), ); - expect(() => Configuration.parseFlutterExtension(loadYamlNode('invalid')), throwsA(isA())); + expect(() => Configuration.parseFallbackTheme(loadYamlNode('true')), throwsA(isA())); }); }); group('parseFlutterAssets(...)', () { test('assets', () => expect( - Configuration.parseFlutterAssets(BasicAssets(), loadYaml(_flutterAssets).nodes['assets']), + Configuration.parseFlutterAssets(loadYaml(_flutterAssets).nodes['assets']), { 'path/to/first/', 'path/to/second/' }, )); test('null', () => expect( - Configuration.parseFlutterAssets(BasicAssets(), null), - {}, - )); - - test('NoAssets', () => expect( - Configuration.parseFlutterAssets(NoAssets(), loadYaml(_flutterAssets).nodes['assets']), + Configuration.parseFlutterAssets(null), {}, )); test('invalid', () { expectLater( log.onRecord, - emits(severeLogOf(contains('Unable to parse flutter assets. See https://docs.flutter.dev/tools/pubspec#assets.'))), + emits(severeLogOf(contains('Unable to read flutter assets. See https://docs.flutter.dev/tools/pubspec#assets.'))), ); - expect(() => Configuration.parseFlutterAssets(StandardAssets(), loadYaml('assets: invalid').nodes['assets']), throwsA(isA())); + expect(() => Configuration.parseFlutterAssets(loadYaml('assets: invalid').nodes['assets']), throwsA(isA())); }); }); } diff --git a/nitrogen/test/src/configuration/key_test.dart b/nitrogen/test/src/configuration/key_test.dart index 5a0f4b4..f5d7149 100644 --- a/nitrogen/test/src/configuration/key_test.dart +++ b/nitrogen/test/src/configuration/key_test.dart @@ -1,6 +1,6 @@ import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; -import 'package:nitrogen/src/configuration/configuration_exception.dart'; +import 'package:nitrogen/src/nitrogen_exception.dart'; import 'package:nitrogen/src/configuration/key.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; @@ -10,12 +10,12 @@ import 'package:yaml/yaml.dart'; void main() { group('parse', () { test('grpc-enum', () { - final key = Key.parse(loadYaml('asset-key: grpc-enum').nodes['asset-key']); + final key = Key.parse(loadYaml('key: grpc-enum').nodes['key']); expect(key(['path', 'to', 'file.png']), 'TO_FILE'); }); test('file-name', () { - final key = Key.parse(loadYaml('asset-key: file-name').nodes['asset-key']); + final key = Key.parse(loadYaml('key: file-name').nodes['key']); expect(key(['path', 'to', 'file.png']), 'file'); }); @@ -27,9 +27,9 @@ void main() { test('invalid', () { expectLater( log.onRecord, - emits(severeLogOf(contains('Unable to configure asset key. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#asset-key.'))), + emits(severeLogOf(contains('Unable to read asset key. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#key.'))), ); - expect(() => Key.parse(loadYaml('asset-key: invalid').nodes['asset-key']), throwsA(isA())); + expect(() => Key.parse(loadYaml('key: invalid').nodes['key']), throwsA(isA())); }); }); } diff --git a/nitrogen/test/src/generators/assets/basic_generator_test.dart b/nitrogen/test/src/generators/asset_generator_test.dart similarity index 58% rename from nitrogen/test/src/generators/assets/basic_generator_test.dart rename to nitrogen/test/src/generators/asset_generator_test.dart index f2747bb..f66f75c 100644 --- a/nitrogen/test/src/generators/assets/basic_generator_test.dart +++ b/nitrogen/test/src/generators/asset_generator_test.dart @@ -1,7 +1,7 @@ import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'package:nitrogen/src/file_system.dart'; -import 'package:nitrogen/src/generators/assets/basic_generator.dart'; +import 'package:nitrogen/src/generators/asset_generator.dart'; import 'package:nitrogen_types/nitrogen_types.dart'; import 'package:test/test.dart'; @@ -27,6 +27,14 @@ class $PrefixPathToDirectory { 'bar', 'path/to/directory/bar.txt', ); + + static Map get contents => const { + 'bar': const GenericAsset( + 'test_package', + 'bar', + 'path/to/directory/bar.txt', + ), + }; } class $PrefixPathToDirectorySubdirectory { @@ -37,10 +45,18 @@ class $PrefixPathToDirectorySubdirectory { 'foo', 'path/to/directory/subdirectory/foo.png', ); + + Map get contents => const { + 'foo': const ImageAsset( + 'test_package', + 'foo', + 'path/to/directory/subdirectory/foo.png', + ), + }; } '''; -const _nonStatic = r''' +const _nonStaticAssetClass = r''' class $PrefixPathToDirectory { const $PrefixPathToDirectory(); @@ -51,10 +67,18 @@ class $PrefixPathToDirectory { 'bar', 'path/to/directory/bar.txt', ); + + Map get contents => const { + 'bar': const GenericAsset( + 'test_package', + 'bar', + 'path/to/directory/bar.txt', + ), + }; } '''; -const _static = r''' +const _staticAssetClass = r''' class $PrefixPathToDirectory { const $PrefixPathToDirectory(); @@ -65,10 +89,66 @@ class $PrefixPathToDirectory { 'bar', 'path/to/directory/bar.txt', ); + + static Map get contents => const { + 'bar': const GenericAsset( + 'test_package', + 'bar', + 'path/to/directory/bar.txt', + ), + }; +} +'''; + +const _excludedAssetClass = r''' +class $PrefixPathToDirectory { + const $PrefixPathToDirectory(); + + GenericAsset get bar => const GenericAsset( + 'test_package', + 'bar', + 'path/to/directory/bar.txt', + ); + + Map get contents => const { + 'bar': const GenericAsset( + 'test_package', + 'bar', + 'path/to/directory/bar.txt', + ), + }; } '''; -const _excluded = r''' +const _nonStaticBasicClass = r''' +class $PrefixPathToDirectory { + const $PrefixPathToDirectory(); + + $PrefixPathToDirectorySubdirectory get subdirectory => const $PrefixPathToDirectorySubdirectory(); + + GenericAsset get bar => const GenericAsset( + 'test_package', + 'bar', + 'path/to/directory/bar.txt', + ); +} +'''; + +const _staticBasicClass = r''' +class $PrefixPathToDirectory { + const $PrefixPathToDirectory(); + + static $PrefixPathToDirectorySubdirectory get subdirectory => const $PrefixPathToDirectorySubdirectory(); + + static GenericAsset get bar => const GenericAsset( + 'test_package', + 'bar', + 'path/to/directory/bar.txt', + ); +} +'''; + +const _excludedBasicClass = r''' class $PrefixPathToDirectory { const $PrefixPathToDirectory(); @@ -97,37 +177,61 @@ void main() { const GenericAsset('test_package', 'bar', 'path/to/directory/bar.txt'), ); - - test('BasicGenerator', () { - final generator = BasicGenerator('Prefix', directory); + test('AssetGenerator', () { + final generator = AssetGenerator('Prefix', directory, {}); expect(formatter.format(generator.generate()), _classes); }); - group('BasicClass', () { + group('AssetClass', () { group('generate(...)', () { test('non static', () { - const basic = BasicClass(directories: AssetDirectoryExpressions('Prefix')); + const basic = AssetClass(directories: AssetDirectoryExpressions('Prefix')); final type = basic.generate(directory).build(); - expect(formatter.format(type.accept(emitter).toString()), formatter.format(_nonStatic)); + expect(formatter.format(type.accept(emitter).toString()), formatter.format(_nonStaticAssetClass)); }); test('static', () { - const basic = BasicClass(directories: AssetDirectoryExpressions('Prefix')); + const basic = AssetClass(directories: AssetDirectoryExpressions('Prefix')); final type = basic.generate(directory, static: true).build(); - expect(formatter.format(type.accept(emitter).toString()), formatter.format(_static)); + expect(formatter.format(type.accept(emitter).toString()), formatter.format(_staticAssetClass)); }); test('excluded', () { - final basic = BasicClass(directories: const AssetDirectoryExpressions('Prefix'), excluded: { subdirectory }); + final basic = AssetClass(directories: const AssetDirectoryExpressions('Prefix'), excluded: { subdirectory }); final type = basic.generate(directory).build(); - expect(formatter.format(type.accept(emitter).toString()), formatter.format(_excluded)); + expect(formatter.format(type.accept(emitter).toString()), formatter.format(_excludedAssetClass)); }); }); }); - + + group('BasicAssetClass', () { + group('generate(...)', () { + test('non static', () { + const basic = BasicAssetClass(directories: AssetDirectoryExpressions('Prefix')); + final type = basic.generate(directory).build(); + + expect(formatter.format(type.accept(emitter).toString()), formatter.format(_nonStaticBasicClass)); + }); + + test('static', () { + const basic = BasicAssetClass(directories: AssetDirectoryExpressions('Prefix')); + final type = basic.generate(directory, static: true).build(); + + expect(formatter.format(type.accept(emitter).toString()), formatter.format(_staticBasicClass)); + }); + + test('excluded', () { + final basic = BasicAssetClass(directories: const AssetDirectoryExpressions('Prefix'), excluded: { subdirectory }); + final type = basic.generate(directory).build(); + + expect(formatter.format(type.accept(emitter).toString()), formatter.format(_excludedBasicClass)); + }); + }); + }); + group('AssetDirectoryExpressions', () { final directory = AssetDirectory( ['path', 'to', 'directory-name'], @@ -148,7 +252,7 @@ void main() { }); }); - + group('AssetFileExpressions', () { final file = AssetFile( ['path', 'to', 'file-name.png'], diff --git a/nitrogen/test/src/generators/assets/standard_generator_test.dart b/nitrogen/test/src/generators/assets/standard_generator_test.dart deleted file mode 100644 index a75b3c9..0000000 --- a/nitrogen/test/src/generators/assets/standard_generator_test.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'package:code_builder/code_builder.dart'; -import 'package:dart_style/dart_style.dart'; -import 'package:nitrogen/src/file_system.dart'; -import 'package:nitrogen/src/generators/assets/basic_generator.dart'; -import 'package:nitrogen/src/generators/assets/standard_generator.dart'; -import 'package:nitrogen_types/nitrogen_types.dart'; -import 'package:test/test.dart'; - -const _classes = r''' -import 'package:nitrogen_types/nitrogen_types.dart'; - -// GENERATED CODE - DO NOT MODIFY BY HAND -// -// ************************************************************************** -// nitrogen -// ************************************************************************** -// -// ignore_for_file: type=lint -// ignore_for_file: deprecated_member_use - -class $PrefixPathToDirectory { - const $PrefixPathToDirectory(); - - static $PrefixPathToDirectorySubdirectory get subdirectory => const $PrefixPathToDirectorySubdirectory(); - - static GenericAsset get bar => const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ); - - static Map get contents => const { - 'bar': const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ), - }; -} - -class $PrefixPathToDirectorySubdirectory { - const $PrefixPathToDirectorySubdirectory(); - - ImageAsset get foo => const ImageAsset( - 'test_package', - 'foo', - 'path/to/directory/subdirectory/foo.png', - ); - - Map get contents => const { - 'foo': const ImageAsset( - 'test_package', - 'foo', - 'path/to/directory/subdirectory/foo.png', - ), - }; -} -'''; - -const _nonStatic = r''' -class $PrefixPathToDirectory { - const $PrefixPathToDirectory(); - - $PrefixPathToDirectorySubdirectory get subdirectory => const $PrefixPathToDirectorySubdirectory(); - - GenericAsset get bar => const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ); - - Map get contents => const { - 'bar': const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ), - }; -} -'''; - -const _static = r''' -class $PrefixPathToDirectory { - const $PrefixPathToDirectory(); - - static $PrefixPathToDirectorySubdirectory get subdirectory => const $PrefixPathToDirectorySubdirectory(); - - static GenericAsset get bar => const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ); - - static Map get contents => const { - 'bar': const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ), - }; -} -'''; - -const _excluded = r''' -class $PrefixPathToDirectory { - const $PrefixPathToDirectory(); - - GenericAsset get bar => const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ); - - Map get contents => const { - 'bar': const GenericAsset( - 'test_package', - 'bar', - 'path/to/directory/bar.txt', - ), - }; -} -'''; - -void main() { - final formatter = DartFormatter(pageWidth: 160); - final emitter = DartEmitter(useNullSafetySyntax: true); - - final subdirectory = AssetDirectory(['path', 'to', 'directory', 'subdirectory']); - subdirectory.children['foo.png'] = AssetFile( - ['path', 'to', 'directory', 'subdirectory', 'foo.png'], - const ImageAsset('test_package', 'foo', 'path/to/directory/subdirectory/foo.png'), - ); - - final directory = AssetDirectory(['path', 'to', 'directory']); - directory.children['subdirectory'] = subdirectory; - directory.children['bar.txt'] = AssetFile( - ['path', 'to', 'directory', 'bar.txt'], - const GenericAsset('test_package', 'bar', 'path/to/directory/bar.txt'), - ); - - test('StandardGenerator', () { - final generator = StandardGenerator('Prefix', directory, {}); - expect(formatter.format(generator.generate()), _classes); - }); - - group('StandardClass', () { - group('generate(...)', () { - test('non static', () { - const basic = StandardClass(directories: AssetDirectoryExpressions('Prefix')); - final type = basic.generate(directory).build(); - - expect(formatter.format(type.accept(emitter).toString()), formatter.format(_nonStatic)); - }); - - test('static', () { - const basic = StandardClass(directories: AssetDirectoryExpressions('Prefix')); - final type = basic.generate(directory, static: true).build(); - - expect(formatter.format(type.accept(emitter).toString()), formatter.format(_static)); - }); - - test('excluded', () { - final basic = StandardClass(directories: const AssetDirectoryExpressions('Prefix'), excluded: { subdirectory }); - final type = basic.generate(directory).build(); - - expect(formatter.format(type.accept(emitter).toString()), formatter.format(_excluded)); - }); - }); - }); -} diff --git a/nitrogen/test/src/generators/extensions/flutter_extension_generator_test.dart b/nitrogen/test/src/generators/extensions/flutter_extension_generator_test.dart deleted file mode 100644 index e00c09f..0000000 --- a/nitrogen/test/src/generators/extensions/flutter_extension_generator_test.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:dart_style/dart_style.dart'; -import 'package:nitrogen/src/generators/extensions/flutter_extension_generator.dart'; -import 'package:test/test.dart'; - -const _expected = ''' -import 'package:flutter/widgets.dart'; -import 'package:nitrogen_types/nitrogen_types.dart'; - -// GENERATED CODE - DO NOT MODIFY BY HAND -// -// ************************************************************************** -// nitrogen -// ************************************************************************** -// -// ignore_for_file: type=lint -// ignore_for_file: deprecated_member_use - -/// Tested against Flutter 3.19.4. -/// -/// Example: -/// ```dart -/// class Foo extends StatelessWidget { -/// final ImageAsset asset; -/// -/// @override -/// Widget build(BuildContext context) => Container( -/// child: asset( -/// width: 100, -/// height: 100, -/// ), -/// ); -/// } -/// ``` -extension ImageAssetExtension on ImageAsset { - Image call({ - Key? key, - ImageFrameBuilder? frameBuilder, - ImageErrorWidgetBuilder? errorBuilder, - String? semanticLabel, - bool excludeFromSemantics = false, - double? scale, - double? width, - double? height, - Color? color, - Animation? opacity, - BlendMode? colorBlendMode, - BoxFit? fit, - AlignmentGeometry alignment = Alignment.center, - ImageRepeat repeat = ImageRepeat.noRepeat, - Rect? centerSlice, - bool matchTextDirection = false, - bool gaplessPlayback = false, - bool isAntiAlias = false, - FilterQuality filterQuality = FilterQuality.low, - int? cacheWidth, - int? cacheHeight, - }) => Image.asset( - path, - package: package, - frameBuilder: frameBuilder, - errorBuilder: errorBuilder, - semanticLabel: semanticLabel, - excludeFromSemantics: excludeFromSemantics, - scale: scale, - width: width, - height: height, - color: color, - opacity: opacity, - colorBlendMode: colorBlendMode, - fit: fit, - alignment: alignment, - repeat: repeat, - centerSlice: centerSlice, - matchTextDirection: matchTextDirection, - gaplessPlayback: gaplessPlayback, - isAntiAlias: isAntiAlias, - filterQuality: filterQuality, - cacheWidth: cacheWidth, - cacheHeight: cacheHeight, - key: key, - ); -} -'''; - -void main() { - test('generate()', () { - final generator = FlutterExtensionGenerator(); - expect(generator.generate(), DartFormatter(pageWidth: 160).format(_expected)); - - }); -} diff --git a/nitrogen/test/src/generators/assets/theme_generator_test.dart b/nitrogen/test/src/generators/theme_generator_test.dart similarity index 98% rename from nitrogen/test/src/generators/assets/theme_generator_test.dart rename to nitrogen/test/src/generators/theme_generator_test.dart index 39beb63..d605e29 100644 --- a/nitrogen/test/src/generators/assets/theme_generator_test.dart +++ b/nitrogen/test/src/generators/theme_generator_test.dart @@ -1,6 +1,6 @@ import 'package:code_builder/code_builder.dart'; import 'package:nitrogen/src/file_system.dart'; -import 'package:nitrogen/src/generators/assets/theme_generator.dart'; +import 'package:nitrogen/src/generators/theme_generator.dart'; import 'package:nitrogen_types/nitrogen_types.dart'; import 'package:test/test.dart'; diff --git a/nitrogen/test/src/generators/libraries_test.dart b/nitrogen/test/src/libraries_test.dart similarity index 100% rename from nitrogen/test/src/generators/libraries_test.dart rename to nitrogen/test/src/libraries_test.dart diff --git a/nitrogen/test/src/lints/reserved_keyword_lint_test.dart b/nitrogen/test/src/lints/reserved_keyword_lint_test.dart new file mode 100644 index 0000000..cf92d45 --- /dev/null +++ b/nitrogen/test/src/lints/reserved_keyword_lint_test.dart @@ -0,0 +1,73 @@ +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:nitrogen/src/file_system.dart'; +import 'package:nitrogen/src/lints/reserved_keyword_lint.dart'; +import 'package:nitrogen/src/nitrogen_exception.dart'; +import 'package:nitrogen_types/nitrogen_types.dart'; +import 'package:test/test.dart'; + +void main() { + group('lintReservedKeyword', () { + test('contains no keywords', () { + final subdirectory = AssetDirectory(['path', 'to', 'directory', 'subdirectory']); + subdirectory.children['foo.png'] = AssetFile( + ['path', 'to', 'directory', 'subdirectory', 'foo.png'], + const ImageAsset('test_package', 'foo', 'path/to/directory/subdirectory/foo.png'), + ); + + final directory = AssetDirectory(['path', 'to', 'directory']); + directory.children['subdirectory'] = subdirectory; + directory.children['bar.txt'] = AssetFile( + ['path', 'to', 'directory', 'bar.txt'], + const GenericAsset('test_package', 'bar', 'path/to/directory/bar.txt'), + ); + + + expect(() => lintReservedKeyword(directory), returnsNormally); + }); + + test('contains Dart keywords in directory name', () { + final subdirectory = AssetDirectory(['path', 'to', 'directory', 'class']); + subdirectory.children['foo.png'] = AssetFile( + ['path', 'to', 'directory', 'class', 'foo.png'], + const ImageAsset('test_package', 'foo', 'path/to/directory/subdirectory/foo.png'), + ); + + final directory = AssetDirectory(['path', 'to', 'class']); + directory.children['class'] = subdirectory; + directory.children['bar.txt'] = AssetFile( + ['path', 'to', 'directory', 'bar.txt'], + const GenericAsset('test_package', 'bar', 'path/to/directory/bar.txt'), + ); + + expectLater( + log.onRecord, + emits(severeLogOf(contains('contains a reserved keyword. Please rename the directory/file. See https://dart.dev/language/keywords.'))), + ); + + expect(() => lintReservedKeyword(directory), throwsA(isA())); + }); + + test('contains Nitrogen keywords', () { + final subdirectory = AssetDirectory(['path', 'to', 'directory', 'contents']); + subdirectory.children['foo.png'] = AssetFile( + ['path', 'to', 'directory', 'contents', 'foo.png'], + const ImageAsset('test_package', 'foo', 'path/to/directory/subdirectory/foo.png'), + ); + + final directory = AssetDirectory(['path', 'to', 'contents']); + directory.children['contents'] = subdirectory; + directory.children['bar.txt'] = AssetFile( + ['path', 'to', 'directory', 'bar.txt'], + const GenericAsset('test_package', 'bar', 'path/to/directory/bar.txt'), + ); + + expectLater( + log.onRecord, + emits(severeLogOf(contains('contains a reserved identifier. Please rename the directory/file. See https://github.com/forus-labs/cauldron/tree/master/nitrogen#reserved-keywords.'))), + ); + + expect(() => lintReservedKeyword(directory), throwsA(isA())); + }); + }); +} \ No newline at end of file diff --git a/nitrogen_types/lib/nitrogen_types.dart b/nitrogen_types/lib/nitrogen_types.dart index 39e2b39..42707d1 100644 --- a/nitrogen_types/lib/nitrogen_types.dart +++ b/nitrogen_types/lib/nitrogen_types.dart @@ -3,3 +3,4 @@ library nitrogen_types; export 'src/assets.dart'; +export 'src/image_asset_extension.dart'; diff --git a/nitrogen/lib/src/generators/extensions/image_asset_extension.dart b/nitrogen_types/lib/src/image_asset_extension.dart similarity index 82% rename from nitrogen/lib/src/generators/extensions/image_asset_extension.dart rename to nitrogen_types/lib/src/image_asset_extension.dart index 89e8f04..27a4aea 100644 --- a/nitrogen/lib/src/generators/extensions/image_asset_extension.dart +++ b/nitrogen_types/lib/src/image_asset_extension.dart @@ -1,16 +1,7 @@ -import 'package:code_builder/code_builder.dart'; +import 'package:flutter/widgets.dart'; +import 'package:nitrogen_types/nitrogen_types.dart'; -/// The extension to generate for image extension v1. -extension Image on Never { - - /// The imports. - static final imports = [ - Directive.import('package:flutter/widgets.dart'), - ]; - - /// The extension. - static const extension = Code(''' -/// Tested against Flutter 3.19.4. +/// Provides functions for converting an [ImageAsset] to a Flutter [Image]. /// /// Example: /// ```dart @@ -28,6 +19,7 @@ extension Image on Never { /// ``` extension ImageAssetExtension on ImageAsset { + /// Converts an [ImageAsset] to an [Image]. Image call({ Key? key, ImageFrameBuilder? frameBuilder, @@ -76,8 +68,4 @@ extension ImageAssetExtension on ImageAsset { key: key, ); -} - '''); - - } diff --git a/nitrogen_types/pubspec.yaml b/nitrogen_types/pubspec.yaml index 2fdfbd7..ed5eb27 100644 --- a/nitrogen_types/pubspec.yaml +++ b/nitrogen_types/pubspec.yaml @@ -7,6 +7,11 @@ repository: https://github.com/forus-labs/cauldron/ environment: sdk: '>=3.3.0 <4.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter dev_dependencies: flint: ^2.7.0