Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support inject in constructor #237

Merged
merged 3 commits into from
Sep 29, 2024
Merged

Conversation

killagu
Copy link
Contributor

@killagu killagu commented Sep 27, 2024

Checklist
  • npm test passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)
Description of change

Summary by CodeRabbit

  • New Features

    • Introduced new dependency injection methods and decorators, enhancing flexibility in how dependencies are managed.
    • Added a new example demonstrating the use of the Inject annotation within the HelloService class.
    • New ConstructorObject class created to showcase dependency injection with various decorators.
    • Added a static method for initializing array metadata in the MetadataUtil class.
  • Bug Fixes

    • Improved error handling during object initialization to ensure better management of dependencies and lifecycle events.
  • Tests

    • Expanded test coverage for decorators and injection functionality, ensuring correct behavior of new features.
    • Added new test cases for verifying constructor injection and qualifier retrieval processes.
  • Documentation

    • Updated README.md with examples and explanations of new features and usage patterns.

Copy link

coderabbitai bot commented Sep 27, 2024

Walkthrough

The changes enhance the dependency injection framework within the Egg.js ecosystem. Key updates include the addition of new decorators for managing injections, modifications to existing decorator functions to support constructor injection, and the introduction of new utility methods for handling metadata and constructor argument names. New classes and interfaces have been created to facilitate these features, along with corresponding test cases to ensure functionality. Additionally, several configuration files and modules have been added to support dynamic features and application structure.

Changes

File(s) Change Summary
README.md Added an example for the HelloService class demonstrating the use of the Inject annotation in the constructor.
core/common-util/src/ObjectUtils.ts Introduced getConstructorArgNameList method for retrieving constructor argument names from a class.
core/common-util/test/ObjectUtil.test.ts Added decorators InitTypeQualifier, ModuleQualifier, and Inject, along with tests for getConstructorArgNameList.
core/core-decorator/src/decorator/*.ts Updated several decorators (ConfigSourceQualifier, EggQualifier, InitTypeQualifier, Inject, ModuleQualifier) to accept an optional parameterIndex to enhance injection flexibility.
core/core-decorator/src/util/*.ts Introduced new methods in MetadataUtil, PrototypeUtil, and QualifierUtil to manage injection types and metadata.
core/metadata/src/impl/*.ts Modified EggPrototypeBuilder and EggPrototypeImpl to support constructor injection and manage injection types effectively.
core/runtime/src/impl/*.ts Enhanced EggObjectImpl and EggObjectUtil to support constructor-based dependency injection and proxy creation for dynamic property manipulation.
core/types/core-decorator/enum/*.ts Added InjectType enum and CONSTRUCTOR_QUALIFIER_META_DATA symbol for managing injection metadata.
core/types/core-decorator/model/*.ts Introduced InjectConstructorInfo and InjectConstructorProto interfaces for managing constructor injection details.
plugin/tegg/test/*.ts Added tests for module configuration and controller functionality in the Egg.js application.
plugin/tegg/test/fixtures/apps/constructor-module-config/*.ts Introduced various classes, controllers, and configuration files to support dynamic module features and application structure.
plugin/tegg/test/fixtures/apps/constructor-module-config/config/*.js Added configuration files for module settings and dynamic features.
plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/*.ts Created new modules and classes with dependency injection capabilities.
plugin/tegg/test/fixtures/apps/constructor-module-config/package.json Introduced package.json for the application and modules to define dependencies and configurations.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant Controller
    participant Service

    User->>App: Request /config
    App->>Controller: Call baseDir()
    Controller->>Service: Retrieve configuration
    Service-->>Controller: Return config values
    Controller-->>App: Send response with config
    App-->>User: Return response
Loading

🐰 In the code, we find new ways,
To inject and log our days.
With constructors now in play,
Dependencies dance and sway.
In Egg.js, we hop with glee,
Celebrating changes, oh so free! 🎉


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 35e4a06 and 16ee600.

📒 Files selected for processing (3)
  • core/core-decorator/src/util/QualifierUtil.ts (2 hunks)
  • core/core-decorator/test/decorators.test.ts (3 hunks)
  • core/metadata/test/LoadUnit.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/core-decorator/test/decorators.test.ts
  • core/metadata/test/LoadUnit.test.ts
🔇 Additional comments (1)
core/core-decorator/src/util/QualifierUtil.ts (1)

27-35: 🛠️ Refactor suggestion

⚠️ Potential issue

Address potential issues in addInjectQualifier method

There are a few potential issues in this method that need to be addressed:

  1. Potential out-of-bounds access when using parameterIndex.
  2. Possible undefined access when using property!.

To resolve these issues, please consider the following changes:

static addInjectQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, attribute: QualifierAttribute, value: QualifierValue) {
  if (typeof parameterIndex === 'number') {
    const argNames = ObjectUtils.getConstructorArgNameList(clazz);
+   if (parameterIndex >= 0 && parameterIndex < argNames.length) {
      const argName = argNames[parameterIndex];
      QualifierUtil.addProperQualifier(clazz, argName, attribute, value);
+   } else {
+     throw new Error(`Invalid parameterIndex ${parameterIndex} for class ${clazz.name}`);
+   }
  } else {
+   if (property !== undefined) {
      QualifierUtil.addProperQualifier((clazz as any).constructor, property!, attribute, value);
+   } else {
+     throw new Error(`Property key is undefined for class ${clazz.name}`);
+   }
  }
}

These changes will:

  1. Add a bounds check for parameterIndex to prevent out-of-bounds access.
  2. Add a check for property to ensure it's not undefined before using it.
  3. Throw informative errors when invalid inputs are provided.

Additionally, consider refactoring the method to improve readability and reduce duplication:

static addInjectQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, attribute: QualifierAttribute, value: QualifierValue) {
  let targetClass: EggProtoImplClass;
  let targetProperty: PropertyKey;

  if (typeof parameterIndex === 'number') {
    const argNames = ObjectUtils.getConstructorArgNameList(clazz);
    if (parameterIndex < 0 || parameterIndex >= argNames.length) {
      throw new Error(`Invalid parameterIndex ${parameterIndex} for class ${clazz.name}`);
    }
    targetClass = clazz;
    targetProperty = argNames[parameterIndex];
  } else if (property !== undefined) {
    targetClass = (clazz as any).constructor;
    targetProperty = property;
  } else {
    throw new Error(`Either parameterIndex or property must be provided for class ${clazz.name}`);
  }

  QualifierUtil.addProperQualifier(targetClass, targetProperty, attribute, value);
}

This refactored version:

  1. Centralizes the error handling.
  2. Reduces duplication by using a single call to addProperQualifier.
  3. Improves readability by clearly separating the logic for determining the target class and property.

Note: This review comment addresses the issues raised in the past review comments, which are still valid for this code segment.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

core/common-util/src/ObjectUtils.ts Dismissed Show dismissed Hide dismissed
core/common-util/src/ObjectUtils.ts Dismissed Show dismissed Hide dismissed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 25

🧹 Outside diff range and nitpick comments (39)
plugin/tegg/test/fixtures/apps/constructor-module-config/package.json (1)

1-3: LGTM! Minimal package.json for test fixture.

The package.json file is appropriately minimal for a test fixture, containing only the essential "name" field. The name "egg-app" is suitable for a generic test application.

Consider adding a "private": true field to prevent accidental publication of this test package. Additionally, if the test case requires simulating a more realistic package.json, you might want to include fields like "version" and "description". However, this is optional and depends on the specific testing requirements.

plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/module.unittest.yml (1)

1-3: LGTM! Consider adding documentation for this configuration file.

The YAML structure is correct, and the configuration seems appropriate for its purpose. As this file is located in a test fixtures directory and named with 'unittest', it's clear that it's intended for testing scenarios.

Consider adding a comment at the top of the file to explain its purpose and how it's used in the unit tests. This would help other developers understand the context quickly. For example:

# Configuration file for testing dynamic feature handling in module-with-config
# Used in unit tests to verify proper loading and interpretation of module configurations
features:
  dynamic:
    bar: 'foo'
plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/module.yml (1)

1-3: LGTM! Consider expanding test coverage.

The YAML structure is correct and provides a good basic test case for dynamic feature configuration. The features.dynamic.foo: 'bar' entry should allow testing of the module's ability to read and utilize dynamic configuration settings.

To enhance test coverage, consider adding more test cases:

  1. Include multiple key-value pairs under dynamic to test handling of multiple configurations.
  2. Add different data types (e.g., numbers, booleans, lists) to ensure proper parsing of various value types.
  3. Consider adding a comment to explain the purpose of this configuration file for other developers.

Example expansion:

features:
  dynamic:
    # Test cases for dynamic feature configuration
    foo: 'bar'
    number_test: 42
    boolean_test: true
    list_test:
      - item1
      - item2

This expansion would provide more comprehensive test scenarios for the module's configuration handling capabilities.

core/types/core-decorator/enum/InjectType.ts (1)

1-4: LGTM! Consider minor optimizations and adding a comment.

The InjectType enum is well-structured and aligns with the PR objective of supporting constructor injection. It follows TypeScript best practices and provides clear semantics for different injection types.

Consider the following suggestions:

  1. Remove redundant string values:
export enum InjectType {
  PROPERTY,
  CONSTRUCTOR,
}

This change doesn't affect functionality but makes the code more concise.

  1. Add a brief comment explaining the enum's purpose:
/**
 * Defines the types of dependency injection supported in the framework.
 * Used to distinguish between property injection and constructor injection.
 */
export enum InjectType {
  PROPERTY,
  CONSTRUCTOR,
}

These changes are optional and don't affect the functionality, but they can improve code readability and maintainability.

core/runtime/test/fixtures/modules/inject-constructor-context-to-singleton/package.json (1)

1-6: LGTM! The package.json file is correctly structured.

The file is well-formed and includes the necessary information for an Egg.js module. The eggModule property with the name field set to "extendsModule" aligns with the PR objective of supporting injection in constructors.

Consider adding more standard fields to the package.json file to improve its completeness:

 {
   "name": "multi-module-service",
+  "version": "1.0.0",
+  "description": "A module for testing constructor injection in Egg.js",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
   "eggModule": {
     "name": "extendsModule"
   }
 }

These additional fields provide more context about the module and can be helpful for maintenance and documentation purposes.

plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/package.json (1)

1-6: LGTM! Consider adding a version field for completeness.

The package.json file is well-structured and correctly defines the module name and eggModule configuration. This setup aligns with the PR objective of supporting dependency injection in constructors, as it provides the necessary module configuration for the Egg.js framework.

For consistency with standard package.json files, consider adding a version field:

 {
   "name": "constructorSimple",
+  "version": "1.0.0",
   "eggModule": {
     "name": "constructorSimple"
   }
 }
plugin/tegg/test/fixtures/apps/constructor-module-config/app/router.ts (1)

3-5: Consider adding a comment explaining the purpose of this route.

Since this file is located in a test fixtures directory, it would be helpful to add a comment explaining that this route is intended for testing purposes. This can prevent confusion and potential security concerns if the code were mistakenly used in a production environment.

Here's a suggested modification:

 import { Application } from 'egg';

 module.exports = (app: Application) => {
+  // Test route: Returns the base directory for configuration testing
   app.router.get('/config', app.controller.app.baseDir);
 };
plugin/tegg/test/fixtures/apps/constructor-module-config/config/plugin.js (2)

1-2: Remove redundant 'use strict' directive

The 'use strict' directive is unnecessary in JavaScript modules as they are always in strict mode by default.

Apply this diff to remove the redundant directive:

-'use strict';
-
🧰 Tools
🪛 Biome

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)


13-13: LGTM: Watcher configuration is clear, consider adding a comment

The watcher configuration is correctly set to false, disabling this functionality. This is likely intentional for this test fixture.

Consider adding a brief comment explaining why the watcher is disabled in this context, for better clarity:

+// Disable watcher for this test fixture
exports.watcher = false;
plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/application.unittest.ts (1)

4-10: LGTM: mockUser method implementation is correct.

The mockUser method is well-implemented:

  • It correctly uses this: MockApplication for type safety.
  • The mock context is set up with a basic user object.

Consider enhancing the mock user object with additional properties that might be relevant for more comprehensive testing scenarios. For example:

this.mockContext({
  user: {
    userName: 'mock_user',
    id: 'mock_id',
    email: '[email protected]',
    // Add any other relevant properties
  },
});

This would provide a more robust mock user object for various test cases.

core/metadata/test/fixtures/modules/app-graph-modules/root/RootConstructor.ts (2)

4-5: LGTM: Class declaration is clear. Consider adding documentation.

The RootConstructorProto class is correctly declared and exported, using the @SingletonProto() decorator to ensure a single instance.

Consider adding a brief JSDoc comment to describe the purpose of this class and explain the 'Proto' suffix in its name. This would enhance code readability and maintainability.


6-7: LGTM: Constructor correctly uses dependency injection. Consider adding type annotation.

The constructor is well-implemented, using the @Inject() decorator for dependency injection and readonly for immutability.

Consider adding an explicit return type annotation to the constructor for better code clarity:

constructor(@Inject() readonly usedProto: UsedProto): void {
}

This makes the constructor's return type explicit, although it's generally understood that constructors don't have a return type.

plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/context.ts (1)

4-9: LGTM: Well-implemented counter with a minor optimization suggestion

The counter getter is well-implemented. It correctly initializes the counter if it doesn't exist and uses the post-increment operator to return the current value before incrementing.

Consider using the pre-increment operator (++this[COUNTER]) instead of the post-increment operator (this[COUNTER]++) if you want to return the incremented value. This is a minor optimization as it avoids creating a temporary copy of the value. However, if the current behavior (returning the value before incrementing) is intended, then the current implementation is correct.

plugin/tegg/test/fixtures/apps/constructor-module-config/app/typings/index.d.ts (2)

1-3: Remove unused import.

The Bar type is imported but not used in this file. Consider removing this import to keep the code clean and avoid potential confusion.

Apply this diff to remove the unused import:

 import 'egg';
 import { Foo } from '../../modules/module-with-config/foo';
-import { Bar } from '../../modules/module-with-overwrite-config/bar';

6-10: Enhance clarity and add documentation for the new property.

While the interface extension is correctly implemented, consider the following improvements:

  1. The property name 'constructorSimple' might not be descriptive enough. Consider a more specific name that reflects its purpose or content.
  2. Add JSDoc comments to document the purpose and usage of this new property.

Here's a suggested improvement:

export interface EggModule {
  /**
   * Represents the configuration for constructor injection.
   * @property {Foo} foo - The Foo instance to be injected.
   */
  constructorInjectionConfig: {
    foo: Foo;
  },
}
core/types/core-decorator/model/InjectConstructorInfo.ts (2)

4-15: Consider standardizing JSDoc comment style.

For improved consistency, consider standardizing the JSDoc comments. Currently, two styles are used: 'inject args index' and "obj's name will be injected". It's generally better to stick to one style throughout the codebase.

Here's a suggested improvement for consistency:

 /**
-  * inject args index
+  * Inject args index
  */
 refIndex: number;
 /**
-  * inject args name
+  * Inject args name
  */
 refName: string;
 /**
-  * obj's name will be injected
+  * Name of the object to be injected
  */
 objName: EggObjectName;

1-16: Summary: New interface aligns well with PR objectives.

This new InjectConstructorInfo interface provides a solid foundation for implementing constructor injection support, which directly addresses the main objective of the PR. The interface's structure allows for precise control over which dependencies are injected and where, enhancing the flexibility of the dependency injection system in the Egg.js framework.

To further improve the implementation:

  1. Ensure that this interface is used consistently across the codebase where constructor injection is implemented.
  2. Consider adding unit tests that validate the correct usage of this interface in the injection process.
  3. Update the project documentation to explain how developers can use this new constructor injection feature, including examples of how to properly populate the InjectConstructorInfo interface.
core/core-decorator/src/decorator/InitTypeQualifier.ts (1)

6-7: LGTM! Consider enhancing type safety.

The changes effectively support constructor injection by modifying the decorator function signature and updating the QualifierUtil method call. This aligns well with the PR objective of supporting inject in constructors.

To improve type safety, consider using more specific types for the target parameter:

-  return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
+  return function(target: object, propertyKey?: PropertyKey, parameterIndex?: number) {

This change ensures that only objects (which includes classes and their prototype chains) can be decorated, preventing potential misuse on primitive values.

core/types/core-decorator/index.ts (2)

9-9: LGTM! Consider updating documentation for InjectConstructorInfo.

The addition of InjectConstructorInfo export is consistent with the file structure and supports the new constructor injection feature.

To ensure proper understanding and usage of this new model, consider updating the relevant documentation to include information about InjectConstructorInfo and its role in constructor injection.


5-9: Summary: New exports enhance dependency injection capabilities.

The additions of InjectType and InjectConstructorInfo exports are well-structured and align with the PR objective of supporting injection in constructors. These new types will likely play a crucial role in implementing and managing the new injection features throughout the codebase.

As you continue to develop this feature:

  1. Ensure consistent usage of these new types across the codebase.
  2. Consider creating or updating developer documentation to explain the new injection capabilities and how to use them effectively.
  3. Be mindful of potential breaking changes for existing users of the dependency injection system.
core/core-decorator/test/fixtures/decators/ConstructorObject.ts (1)

10-16: LGTM: Constructor implements dependency injection as intended.

The constructor correctly implements dependency injection using the @Inject() decorator, which aligns with the PR objective. The use of additional qualifiers for the xCache parameter and the readonly modifier for both parameters are good practices.

Consider renaming the parameters for better clarity:

  • xCache could be renamed to fooCache to match its injection name.
  • cache could be more descriptive, e.g., defaultCache.

Example:

constructor(
  @InitTypeQualifier(ObjectInitType.SINGLETON)
  @ModuleQualifier('foo')
  @Inject({ name: 'fooCache'}) readonly fooCache: ICache,
  @Inject() readonly defaultCache: ICache,
) {
}
plugin/tegg/test/ConstructorModuleConfig.test.ts (1)

29-39: LGTM with suggestions: Test case is good, but could be more comprehensive.

The test case correctly checks the /config endpoint and asserts the expected response structure. However, consider the following suggestions:

  1. Add a comment explaining what ConstructorModuleConfig is and why these specific values are expected.
  2. Consider adding more test cases to cover different scenarios or edge cases.
  3. It might be helpful to test for error cases as well, such as when the config is not available.

Here's an example of how you could add more context and test cases:

// Test the ConstructorModuleConfig
// This config is expected to inject values into the constructor of a module
describe('ConstructorModuleConfig', () => {
  it('should return the correct config values', async () => {
    // Existing test case
  });

  it('should handle missing config gracefully', async () => {
    // Test case for when config is not available
  });

  it('should update config values correctly', async () => {
    // Test case for updating config values
  });
});
core/metadata/test/ModuleGraph.test.ts (1)

15-28: LGTM! Consider enhancing test coverage.

The new test case effectively verifies the sorting functionality of ModuleGraph for constructor-extended classes. The structure and implementation of the test are sound.

To further improve the test coverage and clarity, consider the following suggestions:

  1. Add a brief comment explaining the expected order and why it's correct for constructor-extended classes.
  2. Consider adding an assertion to verify the initial order of classes before sorting, to ensure the test is robust against changes in the loading order.
  3. If possible, add a test case with a more complex inheritance structure to ensure the sorting algorithm handles various scenarios correctly.

Here's an example of how you might implement these suggestions:

it('should sort constructor extends class success', () => {
  const modulePath = path.join(__dirname, './fixtures/modules/extends-constructor-module');
  const loader = new TestLoader(modulePath);
  const clazzList = loader.load();
  
  // Assert initial order to ensure test robustness
  const initialOrder = clazzList.map(t => t.name);
  console.log('Initial order:', initialOrder);
  
  const graph = new ModuleGraph(clazzList, modulePath, 'foo');
  graph.sort();
  
  // Expected order: base classes first, then derived classes
  const expectedOrder = [
    'Logger',
    'Bar',
    'ConstructorBase',
    'FooConstructor',
    'FooConstructorLogger',
  ];
  
  assert.deepStrictEqual(graph.clazzList.map(t => t.name), expectedOrder,
    'Classes should be sorted with base classes first, followed by derived classes');
});

This enhancement provides more context and ensures the test is more robust against potential changes in the loading order of classes.

core/core-decorator/src/util/MetadataUtil.ts (1)

34-46: LGTM! New method looks good, but consider addressing static analysis warnings.

The new initArrayMetaData method is well-implemented and properly documented. It correctly initializes array metadata without inheriting from parent metadata, which aligns with the intended behavior.

Consider addressing the static analysis warnings about using this in static methods. While this is a common practice in TypeScript and works as intended, you might want to use the class name MetadataUtil instead of this for clarity:

static initArrayMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass, defaultValue: Array<T>): Array<T> {
  const ownMetaData: Array<T> | undefined = MetadataUtil.getOwnMetaData(metadataKey, clazz);
  if (!ownMetaData) {
    MetadataUtil.defineMetaData(metadataKey, defaultValue, clazz);
  }
  return MetadataUtil.getOwnMetaData<Array<T>>(metadataKey, clazz)!;
}

This change would resolve the static analysis warnings while maintaining the same functionality.

🧰 Tools
🪛 Biome

[error] 41-41: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 43-43: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 45-45: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

core/core-decorator/test/decorators.test.ts (3)

71-77: LGTM! Consider adding a descriptive test name.

The new test case effectively verifies the functionality of constructor injection using PrototypeUtil.getInjectConstructors. It checks both the reference indices and names for the injected dependencies, which is crucial for ensuring correct dependency injection in constructors.

Consider updating the test description to be more specific:

-it('constructor should work', () => {
+it('should correctly retrieve constructor injection information', () => {

This change would make the test's purpose clearer at a glance.


101-112: LGTM! Consider enhancing test clarity.

The new test case effectively verifies the functionality of qualifiers in constructors using QualifierUtil.getProperQualifiers. It checks both LoadUnitName and InitType qualifiers for two different properties, ensuring consistency in qualifier attributes.

To improve clarity, consider:

  1. Updating the test description to be more specific:
-it('constructor should work', () => {
+it('should correctly retrieve qualifiers for constructor properties', () => {
  1. Adding a comment to explain why you're testing two different properties:
// Test qualifiers for two different properties to ensure consistency
const constructorQualifiers = QualifierUtil.getProperQualifiers(ConstructorObject, 'xCache');
const constructorQualifiers2 = QualifierUtil.getProperQualifiers(ConstructorObject, 'cache');

These changes would make the test's purpose and structure clearer.


Line range hint 18-112: Overall, excellent additions to the test suite!

The new test cases for constructor injection and qualifiers in constructors are well-implemented and effectively cover the new functionality. They maintain consistency with the existing test patterns and provide good coverage for the new features.

If you'd like any assistance in implementing the suggested improvements or adding any additional test cases to further strengthen the test coverage, please let me know. I'd be happy to help!

core/runtime/test/EggObject.test.ts (3)

119-126: Clarify the purpose of mocking ContextHandler twice.

The ContextHandler is mocked twice with different behaviors. It might be helpful to add comments explaining the purpose of each mock:

// Mock ContextHandler to return undefined before creating the singleton
mm(ContextHandler, 'getContext', () => {
  return;
});

// ... (code to create load unit instance)

// Mock ContextHandler to return the test context when creating the singleton
mm(ContextHandler, 'getContext', () => {
  return ctx;
});

This will make the test's intention clearer and easier to understand.


129-130: Consider adding more specific assertions.

The current assertion only checks if the returned message is 'hello from depth2'. It might be beneficial to add more specific assertions to ensure that the context is properly injected into the singleton's constructor. For example:

const msg = await bar.hello();
assert.strictEqual(msg, 'hello from depth2', 'Unexpected message from singleton');
assert(bar.context === ctx, 'Context was not properly injected into singleton constructor');

This will provide more confidence that the constructor injection is working as expected.


131-132: Ensure proper cleanup in case of test failure.

The current cleanup process is good, but it might not run if an assertion fails. Consider using a try-finally block to ensure cleanup always occurs:

try {
  // ... (test code)
  assert(msg === 'hello from depth2');
} finally {
  await TestUtil.destroyLoadUnitInstance(instance);
  await ctx.destroy({});
}

This ensures that resources are always cleaned up, even if the test fails.

core/metadata/test/LoadUnit.test.ts (2)

10-30: LGTM! Well-structured test case with clear assertions.

The test case is well-organized and follows good practices:

  • It uses async/await syntax correctly.
  • It sets up the test environment with TestLoader and LoadUnit.
  • It makes specific assertions about the structure of the EggPrototypes.
  • It cleans up resources by destroying the LoadUnit after the test.

Consider adding comments to explain the significance of not inheriting from the parent class constructor. This would improve the test's readability and maintainability.

To enhance the test coverage, consider adding the following scenarios:

  1. A negative test case where inheritance does occur unexpectedly.
  2. Edge cases with different numbers of constructor arguments.
  3. A test case with multiple levels of inheritance to ensure the behavior is consistent.

Example:

it('should throw an error when inheritance occurs unexpectedly', async () => {
  // Setup code similar to the existing test
  // ...
  await assert.rejects(
    async () => {
      // Call a method that should throw an error if inheritance occurs
    },
    {
      name: 'AssertionError',
      message: /expected error message/
    }
  );
});

10-30: Consider grouping related test suites for better organization.

The new test suite for "inject with constructor" is well-placed as a separate suite. However, for better organization and readability, consider grouping it with other related test suites if they exist.

If there are other test suites related to constructor injection or similar functionality, consider placing this new suite adjacent to them. This grouping can improve the overall structure of the test file and make it easier for developers to find and maintain related tests.

For example:

describe('Constructor Injection', () => {
  describe('inject with constructor', () => {
    // ... (your new test case)
  });

  describe('other constructor injection scenarios', () => {
    // ... (other related test cases)
  });
});

This structure helps in maintaining a logical flow in the test file and makes it easier to add more related tests in the future.

core/common-util/test/ObjectUtil.test.ts (1)

41-41: Remove or clarify the incomplete comment

On line 41, there is a comment // fpp... which appears to be incomplete or a typo. Please clarify its purpose or remove it to improve code readability.

core/core-decorator/src/decorator/Inject.ts (2)

30-43: Address the TODO in constructorInject to improve objName resolution

There's a TODO comment indicating that retrieving objName from design:type is pending implementation in the constructorInject function. Completing this will ensure that the correct object name is injected when param is not provided, enhancing reliability.

Would you like assistance in implementing this functionality? I can help provide a code solution or open a GitHub issue to track this task.


Line range hint 21-24: Refactor duplicated logic when creating injectObject

The code for creating injectObject in both propertyInject (lines 21-24) and constructorInject (lines 35-39) shares similar structure. Refactoring this into a shared helper function would reduce duplication and improve maintainability.

Apply this diff to extract a helper function:

+  function createInjectObject(refName: PropertyKey | string | undefined, objName: PropertyKey | string | undefined) {
+    return {
+      refName,
+      objName: objName || refName,
+    };
+  }

   function propertyInject(target: any, propertyKey: PropertyKey) {
     let objName: PropertyKey | undefined;
     // existing logic to determine objName
     // ...

-    const injectObject: InjectObjectInfo = {
-      refName: propertyKey,
-      objName: objName || propertyKey,
-    };
+    const injectObject = createInjectObject(propertyKey, objName);

     PrototypeUtil.setInjectType(target.constructor, InjectType.PROPERTY);
     PrototypeUtil.addInjectObject(target.constructor as EggProtoImplClass, injectObject);
   }

   function constructorInject(target: any, parameterIndex: number) {
     // existing logic to determine argName and objName
     // ...

-    const injectObject: InjectConstructorInfo = {
-      refIndex: parameterIndex,
-      refName: argName,
-      objName: objName || argName,
-    };
+    const injectObject = {
+      refIndex: parameterIndex,
+      ...createInjectObject(argName, objName),
+    };

     PrototypeUtil.setInjectType(target, InjectType.CONSTRUCTOR);
     PrototypeUtil.addInjectConstructor(target as EggProtoImplClass, injectObject);
   }

Also applies to: 35-39

core/types/metadata/model/EggPrototype.ts (1)

34-55: Improve property comments in InjectConstructorProto for clarity

The comments for the properties in the InjectConstructorProto interface could be clearer and more consistent:

  • refIndex: The comment currently reads "inject args index". Consider rephrasing to "Index of the constructor argument to be injected."
  • refName: The comment is "property name obj inject to". Adjust to "Name of the property where the object is injected."
  • objName: Currently "obj's name will be injected". Change to "Name of the object to be injected."
  • qualifiers: Improve the comment to "Qualifiers used for injection."
  • proto: Update the comment to "The prototype of the object to be injected."
core/core-decorator/src/util/PrototypeUtil.ts (3)

154-154: Improve error message clarity in 'setInjectType' method

The error message in the throw new Error statement has grammatical errors and could be clearer.

Consider updating it as follows:

-      throw new Error(`class ${clazz.name} already use inject type ${injectType} can not use ${type}`);
+      throw new Error(`Class ${clazz.name} already uses inject type ${injectType}; cannot use ${type}.`);

189-195: Remove commented-out code to enhance maintainability

The code segment from lines 189 to 195 is commented out. Removing unused commented code can improve code readability and maintainability.

Consider deleting the following code:

-  // static getInjectConstructors(clazz: EggProtoImplClass): Array<InjectConstructorInfo> {
-  //   return MetadataUtil.getArrayMetaData<InjectConstructorInfo>(PrototypeUtil.INJECT_CONSTRUCTOR_NAME_SET, clazz)
-  //     .sort((a, b) => {
-  //       return a.refIndex - b.refIndex;
-  //     });
-  // }

149-187: Add JSDoc comments to newly introduced methods

Several newly added methods lack JSDoc comments. Adding documentation will improve understandability and maintainability.

Consider adding JSDoc comments to the following methods:

  • setInjectType
  • addInjectObject
  • addInjectConstructor
  • getInjectType
  • getInjectObjects
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6e13b26 and 202fdf7.

📒 Files selected for processing (45)
  • README.md (1 hunks)
  • core/common-util/src/ObjectUtils.ts (2 hunks)
  • core/common-util/test/ObjectUtil.test.ts (2 hunks)
  • core/core-decorator/src/decorator/ConfigSource.ts (1 hunks)
  • core/core-decorator/src/decorator/EggQualifier.ts (1 hunks)
  • core/core-decorator/src/decorator/InitTypeQualifier.ts (1 hunks)
  • core/core-decorator/src/decorator/Inject.ts (2 hunks)
  • core/core-decorator/src/decorator/ModuleQualifier.ts (1 hunks)
  • core/core-decorator/src/util/MetadataUtil.ts (1 hunks)
  • core/core-decorator/src/util/PrototypeUtil.ts (9 hunks)
  • core/core-decorator/src/util/QualifierUtil.ts (2 hunks)
  • core/core-decorator/test/decorators.test.ts (3 hunks)
  • core/core-decorator/test/fixtures/decators/ConstructorObject.ts (1 hunks)
  • core/dal-decorator/src/decorator/DataSourceQualifier.ts (1 hunks)
  • core/metadata/src/impl/EggPrototypeBuilder.ts (5 hunks)
  • core/metadata/src/impl/EggPrototypeImpl.ts (5 hunks)
  • core/metadata/test/LoadUnit.test.ts (1 hunks)
  • core/metadata/test/ModuleGraph.test.ts (2 hunks)
  • core/metadata/test/fixtures/modules/app-graph-modules/root/RootConstructor.ts (1 hunks)
  • core/metadata/test/fixtures/modules/extends-constructor-module/Base.ts (1 hunks)
  • core/metadata/test/fixtures/modules/extends-constructor-module/package.json (1 hunks)
  • core/runtime/src/impl/EggObjectImpl.ts (3 hunks)
  • core/runtime/src/impl/EggObjectUtil.ts (1 hunks)
  • core/runtime/test/EggObject.test.ts (2 hunks)
  • core/runtime/test/fixtures/modules/inject-constructor-context-to-singleton/object.ts (1 hunks)
  • core/runtime/test/fixtures/modules/inject-constructor-context-to-singleton/package.json (1 hunks)
  • core/types/core-decorator/enum/InjectType.ts (1 hunks)
  • core/types/core-decorator/enum/Qualifier.ts (1 hunks)
  • core/types/core-decorator/index.ts (1 hunks)
  • core/types/core-decorator/model/InjectConstructorInfo.ts (1 hunks)
  • core/types/metadata/model/EggPrototype.ts (5 hunks)
  • plugin/tegg/test/ConstructorModuleConfig.test.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/app.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/app/controller/app.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/application.unittest.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/context.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/app/router.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/app/typings/index.d.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/config/config.default.js (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/config/plugin.js (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/foo.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/module.unittest.yml (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/module.yml (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/package.json (1 hunks)
  • plugin/tegg/test/fixtures/apps/constructor-module-config/package.json (1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
core/common-util/src/ObjectUtils.ts

[failure] 45-45: Polynomial regular expression used on uncontrolled data
This regular expression that depends on library input may run slow on strings starting with 'constructor(' and with many repetitions of 'constructor(('.
This regular expression that depends on library input may run slow on strings starting with 'constructor(' and with many repetitions of 'constructor(('.


[failure] 50-50: Polynomial regular expression used on uncontrolled data
This regular expression that depends on library input may run slow on strings with many repetitions of 'a'.
This regular expression that depends on library input may run slow on strings with many repetitions of ' '.
This regular expression that depends on library input may run slow on strings with many repetitions of 'a'.
This regular expression that depends on library input may run slow on strings with many repetitions of ' '.

🪛 Biome
core/common-util/test/ObjectUtil.test.ts

[error] 10-16: Do not export from a test file.

(lint/suspicious/noExportsInTest)


[error] 17-23: Do not export from a test file.

(lint/suspicious/noExportsInTest)

core/core-decorator/src/util/MetadataUtil.ts

[error] 41-41: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 43-43: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 45-45: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

core/runtime/src/impl/EggObjectUtil.ts

[error] 40-40: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 45-45: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 50-50: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 59-59: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 67-67: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 84-84: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 89-89: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 102-102: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 108-108: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 114-114: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 124-124: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 134-134: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 155-155: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 161-161: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

plugin/tegg/test/fixtures/apps/constructor-module-config/config/config.default.js

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)

plugin/tegg/test/fixtures/apps/constructor-module-config/config/plugin.js

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)

🔇 Additional comments (67)
core/metadata/test/fixtures/modules/extends-constructor-module/package.json (1)

1-6: LGTM! Well-structured package.json for test module.

The package.json file is correctly structured and contains the necessary information for the "multi-module-service" test module. It properly defines the module name and its dependency on the "extendsModule" using the eggModule property. This setup is suitable for testing the module extension functionality in the Egg.js framework.

plugin/tegg/test/fixtures/apps/constructor-module-config/app/router.ts (1)

1-5: LGTM! The router configuration looks correct.

The implementation follows Egg.js conventions and correctly sets up a GET route for '/config' using the Application instance.

plugin/tegg/test/fixtures/apps/constructor-module-config/config/plugin.js (3)

3-6: LGTM: Tracer configuration looks good

The configuration for the 'egg-tracer' package is correctly set up and enabled. This will be useful for tracing and debugging purposes in the application.


8-11: LGTM: teggConfig configuration is properly set up

The configuration for the '@eggjs/tegg-config' package is correctly defined and enabled. This will provide additional configuration capabilities for the Egg.js framework in the application.


1-13: Overall, the plugin configuration looks good and aligns with PR objectives

This configuration file sets up the necessary plugins for testing the new constructor injection feature. The teggConfig plugin is particularly relevant as it likely supports the new dependency injection capabilities. The tracer plugin will be useful for debugging during development and testing.

A few minor improvements were suggested:

  1. Removing the redundant 'use strict' directive.
  2. Adding a comment to explain the disabled watcher.

These configurations provide a solid foundation for testing the new "inject in constructor" feature in a controlled environment.

🧰 Tools
🪛 Biome

[error] 1-1: Redundant use strict directive.

The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
Safe fix: Remove the redundant use strict directive.

(lint/suspicious/noRedundantUseStrict)

plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/application.unittest.ts (3)

1-1: LGTM: Import statement is correct.

The import of MockApplication from 'egg-mock' is properly typed and used in the file.


3-3: LGTM: Default export object is well-structured.

The default export object is correctly defined and contains the mockUser method as expected.

Also applies to: 11-11


1-11: Overall assessment: Well-implemented test fixture.

This file effectively extends the MockApplication for testing purposes:

  • The import and export statements are correct.
  • The mockUser method is well-typed and implements a basic mock user context.
  • The code follows TypeScript best practices.

The file serves its purpose well in the context of testing fixtures. Consider the earlier suggestion to enhance the mock user object for more comprehensive testing scenarios if needed.

core/metadata/test/fixtures/modules/app-graph-modules/root/RootConstructor.ts (2)

1-2: LGTM: Imports are correctly structured.

The import statements are well-organized, importing necessary decorators from the core library and a related type from a sibling module. This structure promotes modularity and clear dependencies.


1-8: Overall, the implementation looks good and aligns with the PR objectives.

This new file successfully introduces a class that supports constructor injection, which is the main goal of the PR. The code is concise, uses TypeScript features effectively, and follows good practices for dependency injection and immutability. The minor suggestions for documentation and type annotation, if implemented, would further enhance the code quality.

plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/context.ts (3)

1-1: LGTM: Good use of Symbol for private state management

The use of a Symbol for COUNTER is a good practice for maintaining private state. The naming convention 'Context#counter' clearly indicates its intended use.


1-14: Overall review: Good implementation with minor suggestions for improvement

This file, being part of a test fixture, implements a counter and a user getter. The counter implementation is well done, with a minor suggestion for optimization. The user getter might benefit from clarification on its intended use.

Given that this is a test fixture, some simplifications are expected. However, ensuring clarity and following best practices even in test code is beneficial for maintainability and preventing confusion.


11-13: Clarify the purpose of the user getter

The user getter currently returns a new empty object on each call. This implementation might lead to unexpected behavior if callers expect to modify the returned object and have those modifications persist.

Consider the following:

  1. Is this a placeholder for future implementation?
  2. Should it return a shared object instead of a new one each time?
  3. Is there any specific reason for returning an empty object?

Please clarify the intended behavior. If it's not a placeholder, consider one of these alternatives:

private _user = {};

get user() {
  return this._user;
}

or

get user() {
  if (!this._user) {
    this._user = {};
  }
  return this._user;
}

These alternatives would provide a consistent object reference across multiple calls.

To verify the usage of this getter in the codebase, you can run the following script:

This will help understand how the user getter is being used and whether the current implementation meets those use cases.

✅ Verification successful

The user getter is not used in the codebase

Verification shows that the user getter is not referenced anywhere. If this getter is intended for future use, consider documenting its purpose. Otherwise, consider removing it to simplify the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for usage of the user getter in the codebase

rg --type typescript -A 5 'context\.user'

Length of output: 78


Script:

#!/bin/bash
# Description: Search for usage of the user getter in the codebase

rg --type ts -A 5 'context\.user'

Length of output: 35

plugin/tegg/test/fixtures/apps/constructor-module-config/app/controller/app.ts (2)

1-1: LGTM: Import statement is correct.

The import of Controller from 'egg' is appropriate for extending the Controller class in this context.


3-10: 🛠️ Refactor suggestion

Consider renaming the baseDir method for clarity.

The current method name baseDir doesn't clearly reflect its functionality of retrieving configuration values. Consider renaming it to something more descriptive, such as getConfigValues or retrieveConstructorConfig.

Add error handling and property validation.

The method assumes that this.app.module.constructorSimple.foo and its properties always exist. Consider adding error handling and property validation to make the code more robust.

Here's a suggested implementation:

async getConfigValues() {
  try {
    const config = this.app.module.constructorSimple?.foo;
    if (!config || typeof config.foo === 'undefined' || typeof config.bar === 'undefined') {
      throw new Error('Invalid configuration');
    }
    this.ctx.body = {
      foo: config.foo,
      bar: config.bar,
    };
  } catch (error) {
    this.ctx.status = 500;
    this.ctx.body = { error: 'Failed to retrieve configuration' };
  }
}

Clarify the purpose of this controller in the context of constructor injection.

While this controller demonstrates accessing values from this.app.module.constructorSimple.foo, it's not immediately clear how this relates to the PR objective of supporting injection in constructors. Could you provide more context on how this controller is used to test or demonstrate the new injection capabilities?

To better understand the context, let's check for related test files:

plugin/tegg/test/fixtures/apps/constructor-module-config/app/typings/index.d.ts (2)

5-11: LGTM: Correct module declaration.

The module declaration for 'egg' is correctly implemented, allowing for the extension of the existing module.


1-11: Overall assessment: Good implementation with room for minor improvements.

This file successfully extends the EggModule interface to support constructor injection, which aligns with the PR objective of "feat: support inject in constructor". The implementation is correct and type-safe.

To further improve the code:

  1. Remove the unused import of Bar.
  2. Consider renaming the constructorSimple property to something more descriptive.
  3. Add JSDoc comments to document the new property.

These changes will enhance code clarity and maintainability while fully achieving the PR's objectives.

core/types/core-decorator/model/InjectConstructorInfo.ts (1)

1-16: Well-structured interface with clear documentation.

The InjectConstructorInfo interface is well-defined and properly documented. It provides a clear structure for managing constructor injection information, which aligns well with the PR objective of supporting injection in constructors. The use of TypeScript and JSDoc comments enhances code readability and maintainability.

plugin/tegg/test/fixtures/apps/constructor-module-config/app.ts (3)

1-5: LGTM: Class structure and import look good.

The import of Application from 'egg' and the class structure of AppBoot are well-defined and follow common practices for Egg.js applications. The class name suggests its purpose in the application boot process.


6-8: LGTM: Constructor implements dependency injection correctly.

The constructor follows the dependency injection pattern by accepting an Application instance and storing it as a class property. This approach facilitates easier testing and promotes loose coupling between the AppBoot class and the Egg.js Application.


10-14: 🛠️ Refactor suggestion

Consider improving type safety and documenting the runtime configuration modification.

The configWillLoad method modifies the application configuration at runtime, which can lead to unexpected behavior. Consider the following suggestions:

  1. Use more specific types instead of Record<string, any> to improve type safety.
  2. Document the purpose of this runtime configuration modification and its potential impacts.
  3. Consider using a more robust configuration management system that allows for such overrides without direct mutation.

Here's a suggested refactor to improve type safety:

interface OverwriteConfig {
  features: {
    dynamic: {
      bar: string;
    };
  };
}

configWillLoad() {
  const overwriteConfig = this.app.moduleConfigs?.overwrite?.config as OverwriteConfig;
  if (overwriteConfig) {
    overwriteConfig.features.dynamic.bar = 'overwrite foo';
  }
}

To ensure this configuration override doesn't conflict with other parts of the application, let's search for other usages of this configuration:

✅ Verification successful

Configuration modification verified with limited usages.

The features.dynamic.bar configuration is only used in the intended locations, minimizing the risk of unexpected behavior from runtime modifications. Proceed with the suggested improvements to type safety and documentation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of the 'bar' configuration
rg --type typescript "features\.dynamic\.bar"

Length of output: 82


Script:

#!/bin/bash
# Search for other usages of the 'bar' configuration in .ts and .tsx files
fd --extension ts --extension tsx --exec rg "features\.dynamic\.bar" {}

Length of output: 347

plugin/tegg/test/fixtures/apps/constructor-module-config/config/config.default.js (4)

5-6: Function structure looks good.

The exported function follows the standard pattern for Egg.js configuration files. It correctly takes appInfo as a parameter, allowing for dynamic configuration based on application information.


8-12: Custom logger configuration looks good.

The custom logger configuration is well-structured and uses the appInfo.root to set the log file path, which is a good practice for maintaining consistent file locations across different environments.


13-17: Good security practice for CSRF protection.

Setting ignoreJSON: false for CSRF protection is a good security practice as it ensures that JSON requests are not exempt from CSRF checks.


19-20: Return statement is correct.

The function correctly returns the config object, following the expected pattern for Egg.js configuration files.

plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/foo.ts (2)

1-5: LGTM: Imports and decorator usage are correct.

The imports from '@eggjs/tegg' and the usage of the @SingletonProto decorator with public access level are appropriate for the implemented functionality.


6-8: LGTM: Class declaration and properties are well-defined.

The Foo class is correctly exported, and the use of readonly properties for foo and bar promotes immutability, which is a good practice.

core/core-decorator/src/decorator/ModuleQualifier.ts (3)

6-6: LGTM: Function signature updated to support constructor injection.

The updated function signature now includes an optional parameterIndex parameter and makes propertyKey optional. This change allows the ModuleQualifier decorator to be used for both property injection and constructor parameter injection, aligning with the PR objective of supporting injection in constructors while maintaining backward compatibility.


Line range hint 1-9: Overall assessment: Changes successfully implement constructor injection support.

The modifications to the ModuleQualifier function effectively implement support for constructor injection while maintaining compatibility with existing property injection. The changes are concise, focused, and align well with the PR objectives. Good job on keeping the implementation clean and backward-compatible.


7-7: Implementation updated to support both property and constructor injection.

The change to use QualifierUtil.addInjectQualifier instead of addProperQualifier appears to be a more flexible approach that can handle both property and constructor parameter injection. This is consistent with the function signature update and aligns with the PR objective.

To ensure the correctness of this change, please verify the implementation of QualifierUtil.addInjectQualifier. Run the following script to check its implementation:

✅ Verification successful

Verified the implementation of QualifierUtil.addInjectQualifier.

The addInjectQualifier method correctly handles both property and constructor parameter injection by differentiating based on the presence of parameterIndex. Its implementation aligns with the intended functionality, ensuring flexibility and consistency.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of QualifierUtil.addInjectQualifier

# Test: Search for the addInjectQualifier method in QualifierUtil
ast-grep --lang typescript --pattern $'class QualifierUtil {
  $$$
  static addInjectQualifier($_) {
    $$$
  }
  $$$
}'

Length of output: 122


Script:

#!/bin/bash
# Description: Search for any occurrence of addInjectQualifier in TypeScript files

rg 'addInjectQualifier' --glob '*.ts'

Length of output: 1148


Script:

#!/bin/bash
# Description: Verify the implementation of QualifierUtil.addInjectQualifier with a flexible pattern

ast-grep --lang typescript --pattern $'class QualifierUtil {
  $$$
  static addInjectQualifier($_, $_, $_, $_, $_) {
    $$$
  }
  $$$
}'

Length of output: 6707


Script:

# Script 1: Flexible ast-grep pattern to find addInjectQualifier method
ast-grep --lang typescript --pattern $'class QualifierUtil {
  $$$
  static addInjectQualifier($_) {
    $$$
  }
  $$$
}'

Length of output: 122


Script:

# Script 2: Extract the implementation of addInjectQualifier using rg
rg -A 5 'static addInjectQualifier' --glob '*.ts' core/core-decorator/src/util/QualifierUtil.ts

Length of output: 528

core/core-decorator/src/decorator/ConfigSource.ts (1)

6-7: LGTM! Changes align with PR objective.

The modifications to the ConfigSourceQualifier function successfully implement support for constructor injection. The addition of the parameterIndex parameter and the use of QualifierUtil.addInjectQualifier allow this decorator to be used on constructor parameters, which is consistent with the PR objective of supporting injection in constructors.

core/core-decorator/src/decorator/InitTypeQualifier.ts (1)

6-7: Verify implementation and usage of the new feature.

To ensure the changes are correctly implemented and used:

  1. Let's verify the implementation of QualifierUtil.addInjectQualifier.
  2. Check for usage of InitTypeQualifier decorator on constructor parameters.
  3. Look for documentation updates related to this new feature.

Run the following script to perform these checks:

These checks will help ensure that the new feature is properly implemented, used, and documented across the codebase.

core/types/core-decorator/enum/Qualifier.ts (1)

12-13: LGTM! Verify usage in related files.

The addition of CONSTRUCTOR_QUALIFIER_META_DATA is consistent with existing qualifier metadata constants and follows the established naming convention. This suggests an expansion of the qualifier system to include constructor-level qualifiers, which aligns with the PR objective of supporting injection in constructors.

To ensure this change is properly integrated, please verify its usage in related files:

✅ Verification successful

Verified! The CONSTRUCTOR_QUALIFIER_META_DATA constant is successfully added and utilized within QualifierUtil.ts, aligning with the PR's objective of expanding the qualifier system.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usage of the new CONSTRUCTOR_QUALIFIER_META_DATA constant

# Search for imports or usage of CONSTRUCTOR_QUALIFIER_META_DATA
rg --type typescript "CONSTRUCTOR_QUALIFIER_META_DATA"

# Search for potential decorator implementations that might use this constant
ast-grep --lang typescript --pattern $'@$_($$$) class $_ { constructor($$$) { $$$ } }'

Length of output: 177


Script:

#!/bin/bash
# Description: Check for usage of the new CONSTRUCTOR_QUALIFIER_META_DATA constant

# Search for imports or usage of CONSTRUCTOR_QUALIFIER_META_DATA in .ts and .tsx files
rg "CONSTRUCTOR_QUALIFIER_META_DATA" --glob "*.ts" --glob "*.tsx"

# Search for potential decorator implementations that might use this constant
ast-grep --lang typescript --pattern $'@$_($$$) class $_ { constructor($$$) { $$$ } }'

Length of output: 672

core/types/core-decorator/index.ts (1)

5-5: LGTM! Verify usage of new InjectType enum.

The addition of InjectType export is consistent with the file structure and aligns with the PR objective of supporting injection in constructors.

To ensure proper integration, let's verify the usage of InjectType:

✅ Verification successful

InjectType Export Verified

The InjectType enum is correctly exported and utilized across multiple files, ensuring consistent injection functionalities.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of InjectType in the codebase

# Test: Search for InjectType usage
rg --type typescript "InjectType" -g '!core/types/core-decorator/index.ts'

Length of output: 109


Script:

#!/bin/bash
# Description: Check usage of InjectType in the TypeScript codebase

# Test: Search for InjectType usage using the correct type identifier
rg --type ts "InjectType" -g '!core/types/core-decorator/index.ts'

Length of output: 2535

core/metadata/test/fixtures/modules/extends-constructor-module/Base.ts (7)

1-1: LGTM: Imports are correctly specified and used.

The import statement is concise and imports only the necessary decorators from '@eggjs/core-decorator'.


3-5: LGTM: Logger class is correctly defined with @ContextProto() decorator.

The Logger class is properly decorated with @ContextProto(). Note that the class body is empty, which is unusual but may be intentional for this test fixture.


7-9: LGTM: Bar class is correctly defined with @ContextProto() decorator.

The Bar class is properly decorated with @ContextProto(). Similar to the Logger class, the body is empty, which may be intentional for this test fixture.


11-15: LGTM: ConstructorBase class demonstrates correct use of decorators and dependency injection.

The ConstructorBase class is well-structured:

  1. It's properly decorated with @ContextProto().
  2. The constructor correctly uses @Inject() for dependency injection of the Logger instance.
  3. The injected Logger is appropriately typed and marked as readonly.

This class serves as a good base for demonstrating constructor injection in the test fixture.


24-29: LGTM: FooConstructorLogger class demonstrates correct inheritance and multiple constructor injections.

The FooConstructorLogger class is well-implemented:

  1. It's properly decorated with @ContextProto().
  2. It correctly extends ConstructorBase.
  3. The constructor uses @Inject() for dependency injection of both Bar and Logger instances.
  4. The super() call correctly passes the injected logger to the superclass constructor.

This class effectively demonstrates how to handle multiple injections and proper superclass constructor calling in the context of this test fixture.


1-29: Overall: Well-structured test fixture for constructor injection scenarios.

This file effectively demonstrates various use cases for constructor injection and class inheritance within the context of the @eggjs/core-decorator framework. Key points:

  1. Proper use of @ContextProto() and @Inject() decorators throughout.
  2. Clear demonstration of class inheritance with ConstructorBase as the parent class.
  3. Multiple scenarios of constructor injection, including single and multiple parameter injections.
  4. Clean and easy-to-understand structure that serves well as a test fixture.

The only point to double-check is the use of console in the FooConstructor class's super() call, which might be intentional for testing but differs from the expected Logger type.

Great job on creating a comprehensive test fixture for these scenarios!


17-22: LGTM with a note: FooConstructor class demonstrates inheritance and constructor injection.

The FooConstructor class is well-structured:

  1. It's properly decorated with @ContextProto().
  2. It correctly extends ConstructorBase.
  3. The constructor uses @Inject() for dependency injection of the Bar instance.

However, note that the super() call uses console instead of a Logger instance. While this might be intentional for testing purposes, it's worth verifying if this is the desired behavior, as it doesn't match the type expected by the ConstructorBase.

✅ Verification successful

LGTM with confirmation: The use of super(console) in FooConstructor is unique within the codebase, indicating it's an intentional design choice.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify if console is used elsewhere in a similar context
rg --type typescript 'super\(console\)' -g '!**/Base.ts'

Length of output: 93


Script:

#!/bin/bash
# Verify if console is used elsewhere in a similar context
rg --type ts 'super\(console\)' -g '!**/Base.ts'

Length of output: 50

core/core-decorator/test/fixtures/decators/ConstructorObject.ts (2)

1-6: LGTM: Imports are well-structured and necessary.

The imports are correctly structured and all seem to be used in the implementation. They suggest a well-organized project structure.


8-9: LGTM: Class declaration is well-structured.

The ConstructorObject class is correctly declared and exported. The use of the @SingletonProto() decorator aligns with the PR objective of supporting dependency injection in constructors.

plugin/tegg/test/ConstructorModuleConfig.test.ts (4)

1-7: LGTM: Imports and test setup look good.

The import statements are appropriate for the test file, and the describe block is correctly set up with the proper file path. The fixture directory is also well-defined.


9-15: LGTM: Proper cleanup in after and afterEach hooks.

The after hook correctly closes the app, which is good for cleaning up resources. The afterEach hook restores the mock state, ensuring proper test isolation.


1-40: 🛠️ Refactor suggestion

Enhance test coverage and provide context on PR objective.

While the test file correctly sets up the environment and includes a basic test case, there are opportunities for improvement:

  1. Add more test cases to cover different scenarios and edge cases of ConstructorModuleConfig.
  2. Include comments or documentation explaining what ConstructorModuleConfig is and how it relates to the PR objective of supporting inject in constructor.
  3. Consider testing the injection behavior directly, not just the config values.

Here's a suggestion for expanding the test suite:

import { Container } from 'your-di-container'; // Import your DI container

describe('ConstructorModuleConfig', () => {
  // Existing test case

  it('should inject config values into constructor', async () => {
    const container = new Container();
    const module = container.resolve('YourModule');
    assert(module.foo === 'bar');
    assert(module.bar === 'foo');
  });

  it('should throw error when required config is missing', async () => {
    // Test case for error handling
  });
});

To better understand the context, let's search for ConstructorModuleConfig usage:

#!/bin/bash
# Description: Search for ConstructorModuleConfig usage

# Test: Look for ConstructorModuleConfig in TypeScript files
rg --type typescript 'ConstructorModuleConfig' -C 5

17-27: LGTM with a minor query: Test environment setup looks good.

The before hook correctly sets up the test environment by mocking necessary variables and creating the mock app. However, could you please clarify why process.cwd() is being mocked? It might be helpful to add a comment explaining the reason for this mock.

To understand the necessity of mocking process.cwd(), let's check if it's used in the application setup:

core/runtime/test/EggObject.test.ts (1)

117-134: Overall, good test coverage for constructor injection.

The new test suite effectively covers the functionality of injecting context into a singleton via its constructor, which aligns well with the PR objective. The test case is structured logically and tests the core functionality. With the suggested improvements, this test will provide robust coverage for the new feature.

Good job on adding this test coverage!

core/metadata/test/LoadUnit.test.ts (1)

10-30: Overall, good addition to the test suite with room for minor improvements.

The new test suite for "inject with constructor" is a valuable addition to the LoadUnit tests. It effectively verifies that parent class constructors are not inherited, which seems to be an important aspect of the implementation.

Summary of suggestions:

  1. Add comments explaining the significance of not inheriting from the parent class constructor.
  2. Enhance test coverage with additional scenarios and edge cases.
  3. Strengthen assertions to provide more comprehensive verification.
  4. Consider grouping related test suites for better organization.

These improvements will further enhance the robustness and maintainability of the test suite. Great job on implementing this new feature and its corresponding test!

README.md (1)

501-519: LGTM! Clear example of constructor injection.

The added example effectively demonstrates the use of constructor injection as an alternative to property injection. It's well-formatted and consistent with the rest of the documentation.

A few points to note:

  1. The comment correctly emphasizes that property and constructor injection should not be mixed.
  2. The example clearly shows how to use the @Inject() decorator in the constructor.
  3. The readonly modifier on the injected property is a good practice for immutability.

This addition enhances the documentation by providing users with multiple options for dependency injection, allowing for more flexible code organization.

core/runtime/test/fixtures/modules/inject-constructor-context-to-singleton/object.ts (3)

4-11: Well-defined context-scoped class ContextFooDepth2.

The ContextFooDepth2 class is correctly annotated with @ContextProto and has a public access level. The hello method is properly implemented and returns the expected string.


25-35: Proper singleton class SingletonBarConstructorDepth2 with constructor injection.

The SingletonBarConstructorDepth2 class is correctly defined as a singleton with a public access level. It properly injects SingletonConstructorBarDepth3 via its constructor, and the hello method delegates correctly.


38-48: Well-structured context-scoped class ContextConstructorFoo.

The ContextConstructorFoo class is appropriately annotated with @ContextProto and injects SingletonBarConstructorDepth2 in its constructor. The hello method is correctly implemented, maintaining proper use of dependency injection.

core/core-decorator/src/decorator/Inject.ts (1)

45-51: Logic for injection type determination is correctly implemented

The returned decorator function correctly determines whether to perform property injection or constructor injection based on the presence of parameterIndex. The use of typeof parameterIndex === 'undefined' ensures that parameter index 0 is handled appropriately.

core/metadata/src/impl/EggPrototypeImpl.ts (5)

1-1: Importing InjectType is appropriate

The addition of InjectType to the imports ensures that the class can reference the injection types needed for the new functionality.


8-9: Including InjectConstructorProto enhances injection capabilities

By importing InjectConstructorProto, the class now supports constructor-based injection prototypes, expanding its flexibility.


25-26: Enhanced flexibility with updated injectObjects and the new injectType

Updating injectObjects to accept both InjectObjectProto and InjectConstructorProto increases the versatility of the injection system. The addition of the injectType property allows for distinguishing between property and constructor injection types.


37-41: Constructor parameters updated to support new injection types

The constructor now accepts the updated injectObjectMap and an optional injectType, aligning with the new properties and ensuring proper initialization of instances with the correct injection configuration.


53-53: Defaulting injectType to InjectType.PROPERTY ensures backward compatibility

By setting a default value for injectType, existing functionality remains unaffected when injectType is not provided, preserving backward compatibility.

core/types/metadata/model/EggPrototype.ts (2)

135-135: Verify implementations of constructEggObject after method signature change

The constructEggObject method signature has been updated to accept a variable number of arguments (...args: any). This change may affect classes that implement the EggPrototype interface, as they need to update their method signatures to match this change.

Please ensure that all implementations of EggPrototype have updated the constructEggObject method accordingly. Use the following script to find and inspect implementations:

#!/bin/bash
# Description: Find classes implementing `EggPrototype` and check `constructEggObject` methods.

ast-grep --lang typescript --pattern $'class $_ implements EggPrototype {
  $$$
  constructEggObject($_$$): $_
  $$$
}'

109-109: Ensure proper handling of updated injectObjects property type

The injectObjects property type has been changed to Array<InjectObjectProto | InjectConstructorProto>. This change may impact code that iterates over injectObjects and assumes a single type. Ensure that all code handling this property correctly accounts for both types in the union to prevent runtime errors or type issues.

Run the following script to find all usages of injectObjects and check for proper type handling:

core/core-decorator/src/util/QualifierUtil.ts (1)

1-2: Imports are correctly updated

The import statements appropriately include ObjectUtils and CONSTRUCTOR_QUALIFIER_META_DATA, which are used in the updated methods.

core/metadata/src/impl/EggPrototypeBuilder.ts (4)

2-2: Imports updated to include injection-related types.

The import statements now include InjectType, InjectConstructor, and InjectConstructorProto, which are necessary for supporting constructor injection.

Also applies to: 8-20


33-34: Added injectType property and updated injectObjects to include constructor injection.

The addition of injectType and updating injectObjects to include InjectConstructor enables the builder to handle both property and constructor injections.


50-50: Setting injectType in the builder.

The injectType is set using PrototypeUtil.getInjectType(clazz), allowing the builder to determine the injection method.


142-142: Passing injectType to EggPrototypeImpl constructor.

The injectType is now passed to EggPrototypeImpl, enabling it to handle injection types appropriately.

core/runtime/src/impl/EggObjectImpl.ts (1)

145-151: Initialization properly delegates based on injectType

The init method correctly delegates to initWithInjectConstructor or initWithInjectProperty depending on the injectType. This effectively supports constructor-based injection as intended.

Comment on lines +6 to +7
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
QualifierUtil.addInjectQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, EggQualifierAttribute, eggType);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Action Required: Remaining addProperQualifier Usages Detected

  • Multiple files still use QualifierUtil.addProperQualifier, which may need to be updated to addInjectQualifier to ensure consistency.
🔗 Analysis chain

LGTM! Enhanced flexibility for dependency injection.

The changes to the EggQualifier decorator function improve its flexibility by allowing it to be used for both property and parameter decorators. This aligns with the PR objective of supporting injection in constructors.

Key improvements:

  1. The inner function now accepts an optional parameterIndex, enabling its use as a parameter decorator.
  2. The use of QualifierUtil.addInjectQualifier instead of addProperQualifier suggests a more specialized handling of inject qualifiers.

These changes appear to be part of a larger refactoring to support constructor injection in the Egg.js framework.

To ensure these changes are consistent with the rest of the codebase, let's verify the usage of QualifierUtil.addInjectQualifier:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of QualifierUtil.addInjectQualifier

# Test: Search for other occurrences of addInjectQualifier
echo "Searching for other occurrences of addInjectQualifier:"
rg "QualifierUtil\.addInjectQualifier" --type typescript

# Test: Check if there are any remaining uses of addProperQualifier
echo "Checking for any remaining uses of addProperQualifier:"
rg "QualifierUtil\.addProperQualifier" --type typescript

# Test: Look for other decorators that might need similar updates
echo "Searching for other decorator functions that might need similar updates:"
rg "function.*\(target: any, propertyKey\?: PropertyKey(, parameterIndex\?: number)?\)" --type typescript

Length of output: 723


Script:

#!/bin/bash
# Description: Verify the usage of QualifierUtil.addInjectQualifier

# Test: Search for other occurrences of addInjectQualifier
echo "Searching for other occurrences of addInjectQualifier:"
rg "QualifierUtil\.addInjectQualifier" --type ts

# Test: Check if there are any remaining uses of addProperQualifier
echo "Checking for any remaining uses of addProperQualifier:"
rg "QualifierUtil\.addProperQualifier" --type ts

# Test: Look for other decorators that might need similar updates
echo "Searching for other decorator functions that might need similar updates:"
rg "function.*\(target: any, propertyKey\?: PropertyKey(, parameterIndex\?: number)?\)" --type ts

Length of output: 3408

Comment on lines +6 to +7
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
QualifierUtil.addInjectQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, ConfigSourceQualifierAttribute, moduleName);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Potential breaking changes detected related to ConfigSourceQualifier and QualifierUtil usage.

  • Existing usages of ConfigSourceQualifier:

    • standalone/standalone/test/fixtures/multi-modules/foo/foo.ts
    • standalone/standalone/test/fixtures/dependency/foo.ts
    • standalone/standalone/src/ConfigSourceLoadUnitHook.ts
    • plugin/tegg/lib/ConfigSourceLoadUnitHook.ts
    • core/core-decorator/src/util/QualifierUtil.ts
  • Potential calls to update from addProperQualifier to addInjectQualifier:

    • standalone/standalone/test/fixtures/multi-callback-instance-module/logger/DynamicLogger.ts
    • plugin/tegg/lib/EggQualifierProtoHook.ts
    • plugin/eventbus/lib/EventbusLoadUnitHook.ts
    • core/core-decorator/src/util/QualifierUtil.ts
  • Documentation files that need updating:

    • standalone/standalone/README.md
🔗 Analysis chain

Verify impact on existing usage and documentation.

While the changes look good, it's important to ensure that this modification doesn't introduce breaking changes for existing usages of ConfigSourceQualifier. Additionally, please update any relevant documentation to reflect this new capability.

To help verify the impact, you can run the following script:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing usages of ConfigSourceQualifier and potential breaking changes

# Search for existing usages of ConfigSourceQualifier
echo "Existing usages of ConfigSourceQualifier:"
rg --type typescript "ConfigSourceQualifier\(" -C 3

# Check if there are any direct calls to QualifierUtil.addProperQualifier that might need updating
echo "Potential calls to update from addProperQualifier to addInjectQualifier:"
rg --type typescript "QualifierUtil\.addProperQualifier" -C 3

# Search for documentation files that might need updating
echo "Documentation files that might need updating:"
fd -e md | xargs rg "ConfigSourceQualifier" -l

Length of output: 615


Script:

#!/bin/bash
# Description: Check for existing usages of ConfigSourceQualifier and potential breaking changes with correct TypeScript file detection

# Search for existing usages of ConfigSourceQualifier
echo "Existing usages of ConfigSourceQualifier:"
fd -e ts -e tsx | xargs rg "ConfigSourceQualifier\(" -C 3

# Check if there are any direct calls to QualifierUtil.addProperQualifier that might need updating
echo "Potential calls to update from addProperQualifier to addInjectQualifier:"
fd -e ts -e tsx | xargs rg "QualifierUtil\.addProperQualifier" -C 3

# Search for documentation files that might need updating
echo "Documentation files that might need updating:"
fd -e md | xargs rg "ConfigSourceQualifier" -l

Length of output: 6924

Comment on lines +98 to +170
static contextEggObjectProxy(proto: EggPrototype, objName: PropertyKey): PropertyDescriptor {
const PROTO_OBJ_PROXY = Symbol(`EggPrototype#objProxy#${String(objName)}`);
if (!proto[PROTO_OBJ_PROXY]) {
proto[PROTO_OBJ_PROXY] = new Proxy({}, {
defineProperty(_target: {}, property: string | symbol, attributes: PropertyDescriptor): boolean {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
Object.defineProperty(obj, property, attributes);
return true;
},
deleteProperty(_target: {}, p: string | symbol): boolean {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
delete obj[p];
return true;
},
get(target: {}, p: string | symbol): any {
// make get be lazy
if (p === 'then') return;
if (Object.prototype[p]) {
return target[p];
}
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
return obj[p];
},
getOwnPropertyDescriptor(_target: {}, p: string | symbol): PropertyDescriptor | undefined {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
return Object.getOwnPropertyDescriptor(obj, p);
},
getPrototypeOf(): object | null {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
return Object.getPrototypeOf(obj);
},
has(_target: {}, p: string | symbol): boolean {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
return p in obj;
},
isExtensible(): boolean {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
return Object.isExtensible(obj);
},
ownKeys(): ArrayLike<string | symbol> {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
return Reflect.ownKeys(obj);
},
preventExtensions(): boolean {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
Object.preventExtensions(obj);
return true;
},
set(_target: {}, p: string | symbol, newValue: any): boolean {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
obj[p] = newValue;
return true;
},
setPrototypeOf(_target: {}, v: object | null): boolean {
const eggObject = EggContainerFactory.getEggObject(proto, objName);
const obj = eggObject.obj;
Object.setPrototypeOf(obj, v);
return true;
},
});
}
return proto[PROTO_OBJ_PROXY];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor contextEggObjectProxy to reduce code duplication

The handler methods in contextEggObjectProxy repeatedly retrieve eggObject and obj. Refactoring can enhance maintainability by eliminating this duplication.

Introduce a helper function to retrieve obj, similar to getObj in eggObjectProxy:

static contextEggObjectProxy(proto: EggPrototype, objName: PropertyKey): PropertyDescriptor {
  const PROTO_OBJ_PROXY = Symbol(`EggPrototype#objProxy#${String(objName)}`);
  if (!proto[PROTO_OBJ_PROXY]) {
    function getObj() {
      const eggObject = EggContainerFactory.getEggObject(proto, objName);
      return eggObject.obj;
    }
    proto[PROTO_OBJ_PROXY] = new Proxy({}, {
      defineProperty(_target: object, property: string | symbol, attributes: PropertyDescriptor): boolean {
        const obj = getObj();
        Object.defineProperty(obj, property, attributes);
        return true;
      },
      deleteProperty(_target: object, p: string | symbol): boolean {
        const obj = getObj();
        delete obj[p];
        return true;
      },
      get(target: object, p: string | symbol): any {
        // make get be lazy
        if (p === 'then') return;
        if (Object.prototype[p]) {
          return target[p];
        }
        const obj = getObj();
        return obj[p];
      },
      // ...other handler methods using getObj()...
    });
  }
  return proto[PROTO_OBJ_PROXY];
}

This refactoring reduces repetitive calls to EggContainerFactory.getEggObject(proto, objName) and improves code readability.

🧰 Tools
🪛 Biome

[error] 102-102: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 108-108: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 114-114: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 124-124: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 134-134: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 155-155: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 161-161: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

const PROTO_OBJ_PROXY = Symbol(`EggPrototype#objProxy#${String(objName)}`);
if (!proto[PROTO_OBJ_PROXY]) {
proto[PROTO_OBJ_PROXY] = new Proxy({}, {
defineProperty(_target: {}, property: string | symbol, attributes: PropertyDescriptor): boolean {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace '{}' with 'object' in type annotations

Similar to the previous function, update the type annotations from '{}' to 'object' in the handler methods to accurately reflect the intended type.

Apply the following changes:

- defineProperty(_target: {}, property: string | symbol, attributes: PropertyDescriptor): boolean {
+ defineProperty(_target: object, property: string | symbol, attributes: PropertyDescriptor): boolean {

- deleteProperty(_target: {}, p: string | symbol): boolean {
+ deleteProperty(_target: object, p: string | symbol): boolean {

- get(target: {}, p: string | symbol): any {
+ get(target: object, p: string | symbol): any {

- getOwnPropertyDescriptor(_target: {}, p: string | symbol): PropertyDescriptor | undefined {
+ getOwnPropertyDescriptor(_target: object, p: string | symbol): PropertyDescriptor | undefined {

- has(_target: {}, p: string | symbol): boolean {
+ has(_target: object, p: string | symbol): boolean {

- set(_target: {}, p: string | symbol, newValue: any): boolean {
+ set(_target: object, p: string | symbol, newValue: any): boolean {

- setPrototypeOf(_target: {}, v: object | null): boolean {
+ setPrototypeOf(_target: object, v: object | null): boolean {

Also applies to: 108-108, 114-114, 124-124, 134-134, 155-155, 161-161

🧰 Tools
🪛 Biome

[error] 102-102: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

Comment on lines +30 to +96
static eggObjectProxy(eggObject: EggObject): PropertyDescriptor {
let _obj: object;
function getObj() {
if (!_obj) {
_obj = eggObject.obj;
}
return _obj;
}

const proxy = new Proxy({}, {
defineProperty(_target: {}, property: string | symbol, attributes: PropertyDescriptor): boolean {
const obj = getObj();
Object.defineProperty(obj, property, attributes);
return true;
},
deleteProperty(_target: {}, p: string | symbol): boolean {
const obj = getObj();
delete obj[p];
return true;
},
get(target: {}, p: string | symbol): any {
// make get be lazy
if (p === 'then') return;
if (Object.prototype[p]) {
return target[p];
}
const obj = getObj();
return obj[p];
},
getOwnPropertyDescriptor(_target: {}, p: string | symbol): PropertyDescriptor | undefined {
const obj = getObj();
return Object.getOwnPropertyDescriptor(obj, p);
},
getPrototypeOf(): object | null {
const obj = getObj();
return Object.getPrototypeOf(obj);
},
has(_target: {}, p: string | symbol): boolean {
const obj = getObj();
return p in obj;
},
isExtensible(): boolean {
const obj = getObj();
return Object.isExtensible(obj);
},
ownKeys(): ArrayLike<string | symbol> {
const obj = getObj();
return Reflect.ownKeys(obj);
},
preventExtensions(): boolean {
const obj = getObj();
Object.preventExtensions(obj);
return true;
},
set(_target: {}, p: string | symbol, newValue: any): boolean {
const obj = getObj();
obj[p] = newValue;
return true;
},
setPrototypeOf(_target: {}, v: object | null): boolean {
const obj = getObj();
Object.setPrototypeOf(obj, v);
return true;
},
});
return proxy;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Clarify the return type of eggObjectProxy method

The eggObjectProxy method is declared to return a PropertyDescriptor, but it returns a Proxy object instead. To maintain type consistency and correctness, consider returning a valid PropertyDescriptor or updating the return type.

Option 1: Return a valid PropertyDescriptor

Modify the method to return a PropertyDescriptor with the proxy as its value:

 static eggObjectProxy(eggObject: EggObject): PropertyDescriptor {
   let _obj: object;
   function getObj() {
     if (!_obj) {
       _obj = eggObject.obj;
     }
     return _obj;
   }

   const proxy = new Proxy({}, {
     // ...handler methods...
   });
-  return proxy;
+  return {
+    value: proxy,
+    configurable: true,
+    enumerable: true,
+  };
 }

Option 2: Update the return type

If the intention is to return the Proxy object directly, update the return type:

- static eggObjectProxy(eggObject: EggObject): PropertyDescriptor {
+ static eggObjectProxy(eggObject: EggObject): any {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static eggObjectProxy(eggObject: EggObject): PropertyDescriptor {
let _obj: object;
function getObj() {
if (!_obj) {
_obj = eggObject.obj;
}
return _obj;
}
const proxy = new Proxy({}, {
defineProperty(_target: {}, property: string | symbol, attributes: PropertyDescriptor): boolean {
const obj = getObj();
Object.defineProperty(obj, property, attributes);
return true;
},
deleteProperty(_target: {}, p: string | symbol): boolean {
const obj = getObj();
delete obj[p];
return true;
},
get(target: {}, p: string | symbol): any {
// make get be lazy
if (p === 'then') return;
if (Object.prototype[p]) {
return target[p];
}
const obj = getObj();
return obj[p];
},
getOwnPropertyDescriptor(_target: {}, p: string | symbol): PropertyDescriptor | undefined {
const obj = getObj();
return Object.getOwnPropertyDescriptor(obj, p);
},
getPrototypeOf(): object | null {
const obj = getObj();
return Object.getPrototypeOf(obj);
},
has(_target: {}, p: string | symbol): boolean {
const obj = getObj();
return p in obj;
},
isExtensible(): boolean {
const obj = getObj();
return Object.isExtensible(obj);
},
ownKeys(): ArrayLike<string | symbol> {
const obj = getObj();
return Reflect.ownKeys(obj);
},
preventExtensions(): boolean {
const obj = getObj();
Object.preventExtensions(obj);
return true;
},
set(_target: {}, p: string | symbol, newValue: any): boolean {
const obj = getObj();
obj[p] = newValue;
return true;
},
setPrototypeOf(_target: {}, v: object | null): boolean {
const obj = getObj();
Object.setPrototypeOf(obj, v);
return true;
},
});
return proxy;
}
static eggObjectProxy(eggObject: EggObject): PropertyDescriptor {
let _obj: object;
function getObj() {
if (!_obj) {
_obj = eggObject.obj;
}
return _obj;
}
const proxy = new Proxy({}, {
defineProperty(_target: {}, property: string | symbol, attributes: PropertyDescriptor): boolean {
const obj = getObj();
Object.defineProperty(obj, property, attributes);
return true;
},
deleteProperty(_target: {}, p: string | symbol): boolean {
const obj = getObj();
delete obj[p];
return true;
},
get(target: {}, p: string | symbol): any {
// make get be lazy
if (p === 'then') return;
if (Object.prototype[p]) {
return target[p];
}
const obj = getObj();
return obj[p];
},
getOwnPropertyDescriptor(_target: {}, p: string | symbol): PropertyDescriptor | undefined {
const obj = getObj();
return Object.getOwnPropertyDescriptor(obj, p);
},
getPrototypeOf(): object | null {
const obj = getObj();
return Object.getPrototypeOf(obj);
},
has(_target: {}, p: string | symbol): boolean {
const obj = getObj();
return p in obj;
},
isExtensible(): boolean {
const obj = getObj();
return Object.isExtensible(obj);
},
ownKeys(): ArrayLike<string | symbol> {
const obj = getObj();
return Reflect.ownKeys(obj);
},
preventExtensions(): boolean {
const obj = getObj();
Object.preventExtensions(obj);
return true;
},
set(_target: {}, p: string | symbol, newValue: any): boolean {
const obj = getObj();
obj[p] = newValue;
return true;
},
setPrototypeOf(_target: {}, v: object | null): boolean {
const obj = getObj();
Object.setPrototypeOf(obj, v);
return true;
},
});
return {
value: proxy,
configurable: true,
enumerable: true,
};
}
🧰 Tools
🪛 Biome

[error] 40-40: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 45-45: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 50-50: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 59-59: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 67-67: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 84-84: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 89-89: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

Comment on lines +88 to +143
async initWithInjectConstructor(ctx: EggObjectLifeCycleContext) {
// 1. create inject deps
// 2. create obj
// 3. call obj lifecycle preCreate
// 4. call obj lifecycle postCreate
// 5. success create
try {
const constructArgs = await Promise.all(this.proto.injectObjects!.map(async injectObject => {
const proto = injectObject.proto;
const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);
if (!loadUnit) {
throw new Error(`can not find load unit: ${proto.loadUnitId}`);
}
if (this.proto.initType !== ObjectInitType.CONTEXT && injectObject.proto.initType === ObjectInitType.CONTEXT) {
return EggObjectUtil.contextEggObjectProxy(proto, injectObject.objName);
}
const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);
return EggObjectUtil.eggObjectProxy(injectObj);
}));

this._obj = this.proto.constructEggObject(...constructArgs);
const objLifecycleHook = this._obj as EggObjectLifecycle;

// global hook
await EggObjectLifecycleUtil.objectPreCreate(ctx, this);
// self hook
const postConstructMethod = EggObjectLifecycleUtil.getLifecycleHook('postConstruct', this.proto) ?? 'postConstruct';
if (objLifecycleHook[postConstructMethod]) {
await objLifecycleHook[postConstructMethod](ctx, this);
}

const preInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('preInject', this.proto) ?? 'preInject';
if (objLifecycleHook[preInjectMethod]) {
await objLifecycleHook[preInjectMethod](ctx, this);
}

// global hook
await EggObjectLifecycleUtil.objectPostCreate(ctx, this);

// self hook
const postInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('postInject', this.proto) ?? 'postInject';
if (objLifecycleHook[postInjectMethod]) {
await objLifecycleHook[postInjectMethod](ctx, this);
}

const initMethod = EggObjectLifecycleUtil.getLifecycleHook('init', this.proto) ?? 'init';
if (objLifecycleHook[initMethod]) {
await objLifecycleHook[initMethod](ctx, this);
}

this.status = EggObjectStatus.READY;
} catch (e) {
this.status = EggObjectStatus.ERROR;
throw e;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider refactoring to eliminate duplicate code

The methods initWithInjectConstructor and initWithInjectProperty contain similar logic for handling injected dependencies, lifecycle hooks, and error handling. To improve maintainability and adhere to the DRY (Don't Repeat Yourself) principle, consider extracting the shared logic into utility functions or a common private method.

Comment on lines +95 to +106
const constructArgs = await Promise.all(this.proto.injectObjects!.map(async injectObject => {
const proto = injectObject.proto;
const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);
if (!loadUnit) {
throw new Error(`can not find load unit: ${proto.loadUnitId}`);
}
if (this.proto.initType !== ObjectInitType.CONTEXT && injectObject.proto.initType === ObjectInitType.CONTEXT) {
return EggObjectUtil.contextEggObjectProxy(proto, injectObject.objName);
}
const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);
return EggObjectUtil.eggObjectProxy(injectObj);
}));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure null safety when accessing this.proto.injectObjects

In the line this.proto.injectObjects!.map(...), the non-null assertion operator ! assumes that injectObjects is neither null nor undefined. To prevent potential runtime errors, consider adding a check to handle cases where this.proto.injectObjects might be undefined or null.

@killagu killagu merged commit e68b1ed into master Sep 29, 2024
12 checks passed
@killagu killagu deleted the feat/inject_constructor branch September 29, 2024 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant