From 53193ac08d0aa198ed3f64981c62f4793f7fcfcf Mon Sep 17 00:00:00 2001 From: Vasilii Novozhilov Date: Mon, 28 Apr 2025 10:21:48 +0300 Subject: [PATCH 1/2] new lint rule added --- .../lib/avoid_async_dep_child_scope.dart | 57 ++++++++++++++++++ .../lints/avoid_async_dep_child_scope.dart | 58 +++++++++++++++++++ packages/yx_scope_linter/lib/src/plugin.dart | 2 + packages/yx_scope_linter/lib/src/types.dart | 15 +++++ packages/yx_scope_linter/lib/src/utils.dart | 8 +++ 5 files changed, 140 insertions(+) create mode 100644 packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart create mode 100644 packages/yx_scope_linter/lib/src/lints/avoid_async_dep_child_scope.dart diff --git a/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart b/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart new file mode 100644 index 0000000..049649b --- /dev/null +++ b/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart @@ -0,0 +1,57 @@ +import 'package:yx_scope/yx_scope.dart'; + +class SomeScopeHolder extends ScopeHolder<_ParentScopeContainer> { + @override + _ParentScopeContainer createContainer() => _ParentScopeContainer(); +} + +class _ParentScopeContainer extends ScopeContainer { + @override + List> get initializeQueue => [ + {_childScopeHolderRawAsyncDep, _childScopeHolderAsyncDep} + ]; + + // expect_lint: avoid_async_dep_child_scope + late final _childScopeHolderRawAsyncDep = rawAsyncDep( + () => _ChildScopeHolder(this), + init: (dep) async => await dep.create(), + dispose: (dep) async => await dep.drop(), + ); + + // expect_lint: avoid_async_dep_child_scope + late final _childScopeHolderAsyncDep = + asyncDep(() => _ChildAsyncLifecycleScopeContainer()); +} + +class ParentScopeModule extends ScopeModule<_ParentScopeContainer> { + ParentScopeModule(super.container); + + // expect_lint: avoid_async_dep_child_scope + late final childScopeHolderRawAsyncDep = rawAsyncDep( + () => _ChildScopeHolder(this.container), + init: (dep) async => await dep.create(), + dispose: (dep) async => await dep.drop(), + ); +} + +class _ChildScopeHolder + extends ChildScopeHolder<_ChildScopeContainer, _ParentScopeContainer> { + _ChildScopeHolder(super.parent); + + @override + _ChildScopeContainer createContainer(_ParentScopeContainer parent) => + _ChildScopeContainer(parent: parent); +} + +class _ChildScopeContainer extends ChildScopeContainer<_ParentScopeContainer> { + _ChildScopeContainer({required super.parent}); +} + +class _ChildAsyncLifecycleScopeContainer extends ScopeContainer + implements AsyncLifecycle { + @override + Future dispose() async {} + + @override + Future init() async {} +} \ No newline at end of file diff --git a/packages/yx_scope_linter/lib/src/lints/avoid_async_dep_child_scope.dart b/packages/yx_scope_linter/lib/src/lints/avoid_async_dep_child_scope.dart new file mode 100644 index 0000000..2880880 --- /dev/null +++ b/packages/yx_scope_linter/lib/src/lints/avoid_async_dep_child_scope.dart @@ -0,0 +1,58 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/error/error.dart' hide LintCode; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:yx_scope_linter/src/types.dart'; +import 'package:yx_scope_linter/src/utils.dart'; + +class AvoidChildScopeInInitializeQueue extends DartLintRule { + static const _code = LintCode( + name: 'avoid_async_dep_child_scope', + problemMessage: 'Child scope should not have the same lifecycle as its ' + 'parent, and therefore the child scope should be neither ' + 'an asyncDep nor a rawAsyncDep', + correctionMessage: 'If you need some dependencies with the same lifecycle ' + 'but grouped together, use ScopeModule instead', + errorSeverity: ErrorSeverity.WARNING, + ); + + const AvoidChildScopeInInitializeQueue() : super(code: _code); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addClassDeclaration((node) { + if (!ClassUtils.isScopeNode(node)) { + return; + } + final deps = ClassUtils.getDepDeclarations(node); + for (final dep in deps.values) { + final methodInvocation = dep.field.fields.childEntities + .whereType() + .expand((e) => e.childEntities.whereType()) + .first; + final depClass = (methodInvocation.staticType as InterfaceType) + .typeArguments + .map((e) => e.element) + .whereType() + .first; + final isScopeHolder = childScopeHolderValueType.isSuperOf(depClass); + final isScopeContainer = + scopeContainerValueType.isAssignableFrom(depClass); + final isAsyncLifecycle = asyncLifecycleType.isAssignableFrom(depClass); + if (dep.isAsync && + (isScopeHolder || (isScopeContainer && isAsyncLifecycle))) { + reporter.atToken( + dep.nameToken, + _code, + ); + } + } + }); + } +} diff --git a/packages/yx_scope_linter/lib/src/plugin.dart b/packages/yx_scope_linter/lib/src/plugin.dart index ac41f9b..6b7f1e8 100644 --- a/packages/yx_scope_linter/lib/src/plugin.dart +++ b/packages/yx_scope_linter/lib/src/plugin.dart @@ -1,4 +1,5 @@ import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:yx_scope_linter/src/lints/avoid_async_dep_child_scope.dart'; import 'package:yx_scope_linter/src/lints/consider_dep_suffix.dart'; import 'package:yx_scope_linter/src/lints/dep_cycle.dart'; import 'package:yx_scope_linter/src/lints/pass_async_lifecycle_in_initialize_queue.dart'; @@ -14,5 +15,6 @@ class YXScopedLintsPlugin extends PluginBase { ConsiderDepSuffix(), PassAsyncLifecycleInInitializeQueue(), UseAsyncDepForAsyncLifecycle(), + AvoidChildScopeInInitializeQueue(), ]; } diff --git a/packages/yx_scope_linter/lib/src/types.dart b/packages/yx_scope_linter/lib/src/types.dart index b32cde5..b274e9a 100644 --- a/packages/yx_scope_linter/lib/src/types.dart +++ b/packages/yx_scope_linter/lib/src/types.dart @@ -19,4 +19,19 @@ const asyncLifecycleType = TypeChecker.fromName( packageName: 'yx_scope', ); +const childScopeHolderValueType = TypeChecker.fromName( + 'ChildScopeHolder', + packageName: 'yx_scope', +); + +const scopeContainerValueType = TypeChecker.fromName( + 'ScopeContainer', + packageName: 'yx_scope', +); + const anyDepValueTypes = TypeChecker.any([depValueType, asyncDepValueType]); + +const scopeModuleType = TypeChecker.fromName( + 'ScopeModule', + packageName: 'yx_scope', +); diff --git a/packages/yx_scope_linter/lib/src/utils.dart b/packages/yx_scope_linter/lib/src/utils.dart index e8a62f4..4ca5b8b 100644 --- a/packages/yx_scope_linter/lib/src/utils.dart +++ b/packages/yx_scope_linter/lib/src/utils.dart @@ -21,6 +21,14 @@ class ClassUtils { : false; } + static bool isModuleScope(ClassDeclaration node) { + final element = node.declaredElement; + return element != null ? scopeModuleType.isAssignableFrom(element) : false; + } + + static bool isScopeNode(ClassDeclaration node) => + isScopeContainer(node) || isModuleScope(node); + static Iterable getInstanceFields(ClassDeclaration node) { return node.members .whereType() From 123ddda51c461bee9a8762dd0409a08a91012280 Mon Sep 17 00:00:00 2001 From: Vasilii Novozhilov Date: Mon, 28 Apr 2025 10:36:48 +0300 Subject: [PATCH 2/2] proper files formatting --- .../example/lib/avoid_async_dep_child_scope.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart b/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart index 049649b..06bad05 100644 --- a/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart +++ b/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart @@ -54,4 +54,4 @@ class _ChildAsyncLifecycleScopeContainer extends ScopeContainer @override Future init() async {} -} \ No newline at end of file +}