Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Set<AsyncDep>> 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<void> dispose() async {}

@override
Future<void> init() async {}
}
Original file line number Diff line number Diff line change
@@ -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<VariableDeclaration>()
.expand((e) => e.childEntities.whereType<MethodInvocation>())
.first;
final depClass = (methodInvocation.staticType as InterfaceType)
.typeArguments
.map((e) => e.element)
.whereType<ClassElement>()
.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,
);
}
}
});
}
}
2 changes: 2 additions & 0 deletions packages/yx_scope_linter/lib/src/plugin.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,5 +15,6 @@ class YXScopedLintsPlugin extends PluginBase {
ConsiderDepSuffix(),
PassAsyncLifecycleInInitializeQueue(),
UseAsyncDepForAsyncLifecycle(),
AvoidChildScopeInInitializeQueue(),
];
}
15 changes: 15 additions & 0 deletions packages/yx_scope_linter/lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
8 changes: 8 additions & 0 deletions packages/yx_scope_linter/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<FieldDeclaration> getInstanceFields(ClassDeclaration node) {
return node.members
.whereType<FieldDeclaration>()
Expand Down