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 esm project to parse documentation #30

Merged
merged 4 commits into from
Oct 28, 2024

Conversation

Romakita
Copy link
Collaborator

@Romakita Romakita commented Oct 28, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a new ESLint configuration for improved linting of TypeScript and JavaScript projects.
    • Enhanced asynchronous file handling capabilities in various classes.
  • Bug Fixes

    • Updated linting commands in the package.json for better clarity and functionality.
  • Documentation

    • Improved readability of code with formatting changes in several configuration files.
  • Chores

    • Updated various dependencies to their latest versions for better performance and security.
    • Streamlined workflow configuration for testing and deployment processes.

@Romakita Romakita force-pushed the feat-add-esm-support-import branch from 7682333 to 3c52df0 Compare October 28, 2024 11:31
Copy link

coderabbitai bot commented Oct 28, 2024

Walkthrough

The changes involve the removal of several configuration files and updates to the ESLint setup, including the introduction of a new eslint.config.mjs file for TypeScript and JavaScript projects. The workflow configuration in .github/workflows/build.yml has been simplified to use a single Node.js version and updated action versions. Additionally, multiple files have been modified to adopt asynchronous programming practices, particularly in the DocFile, DocSymbol, and DocParser classes, enhancing their file handling capabilities.

Changes

File Path Change Summary
.eslintignore, .eslintrc Deleted configuration files; removed ESLint ignore rules and settings.
.github/workflows/build.yml Updated workflow to use Node.js version 20.x, upgraded action versions to v4, added caching for dependencies, and modified deployment steps.
.mocharc.js Reformatted spec property for improved readability.
eslint.config.mjs Added new ESLint configuration tailored for TypeScript and JavaScript, including ignored files and rules.
package.json Updated scripts for linting, added and updated multiple dependencies related to ESLint and TypeScript.
packages/ts-doc/src/highlight/index.js Reformatted KEYWORDS constant for improved readability.
packages/ts-doc/src/models/DocFile.js Modified file handling to use asynchronous methods, updated method names, and improved file reading logic.
packages/ts-doc/src/models/DocSymbol.js Updated setDocFile method to be asynchronous, affecting how module information is retrieved.
packages/ts-doc/src/models/DocSymbol.spec.js Updated test suite to reflect asynchronous changes in setDocFile.
packages/ts-doc/src/parsers/DocParser.js Changed parse method to be asynchronous, allowing for proper handling of symbols in an asynchronous context.
packages/ts-doc/src/scan/scan.js Updated scanFiles method to be asynchronous, enabling correct handling of file parsing and logging.

Poem

🐰 In the garden where code does bloom,
Changes sprout, dispelling gloom.
With async hops, we leap and bound,
In files and scripts, new joys are found.
So let us cheer, with a joyful thump,
For cleaner code and a happier jump! 🌼


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.

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: 11

🧹 Outside diff range and nitpick comments (6)
packages/ts-doc/src/models/DocFile.js (1)

3-4: Consider consolidating fs operations in future updates.

While maintaining both sync and async fs operations is valid during the transition, consider moving entirely to async operations in a future update for consistency.

package.json (1)

46-66: Dependencies well-aligned with ESM support objective.

The comprehensive update of the development toolchain provides a solid foundation for ESM support:

  • ESLint v9 with native ESM support
  • TypeScript 5.6.3 with enhanced ESM capabilities
  • Modern versions of all related tools

Consider adding "type": "module" to package.json to fully embrace ESM.

eslint.config.mjs (1)

9-18: Consider adding more common ignore patterns.

The ignore patterns cover most cases, but you might want to consider adding:

  • **/node_modules
  • **/.nyc_output
  • **/.yarn
packages/ts-doc/src/models/DocSymbol.spec.js (1)

42-44: Consider enhancing the mock implementation

The importModule mock returns an empty object, which might not provide sufficient test coverage. Consider adding more realistic mock data based on actual module structure.

packages/ts-doc/src/models/DocSymbol.js (2)

Line range hint 162-192: Add error handling and parameter validation.

The async implementation should include proper error handling and parameter validation to ensure robustness.

Apply this diff to improve error handling:

   async setDocFile(docFile) {
+    if (!docFile) {
+      throw new Error('DocFile is required');
+    }
+
     this.docFile = docFile;
 
     if (this.relativePath.match("decorators") && this.symbolType === "function") {
       this.symbolType = "decorator";
       this.symbolLabel = context.symbolTypes["decorator"].label;
       this.symbolCode = context.symbolTypes["decorator"].code;
     }
 
     if (this.relativePath.match("services") && this.symbolType === "function") {
       this.symbolType = "service";
       this.symbolLabel = context.symbolTypes["service"].label;
       this.symbolCode = context.symbolTypes["service"].code;
     }
 
     if (this.private === undefined) {
       if (this.symbolType !== "interface") {
         this.private = true;
 
-        const exported = await this.docFile.importModule();
+        try {
+          const exported = await this.docFile.importModule();
 
-        if (exported) {
-          const symbolPrivate = exported[this.symbolName.trim()];
+          if (exported) {
+            const symbolPrivate = exported[this.symbolName.trim()];
 
-          this.private = !symbolPrivate;
-        }
+            this.private = !symbolPrivate;
+          }
-        if (this.private) {
-          this.labels.push({key: "private", value: "private"});
+          if (this.private) {
+            this.labels.push({key: "private", value: "private"});
+          }
+        } catch (error) {
+          console.warn(`Failed to import module for ${this.symbolName}: ${error.message}`);
+          // Fallback to private if import fails
+          this.private = true;
+          this.labels.push({key: "private", value: "private"});
         }
       }
     }
   }

Line range hint 162-192: Consider refactoring for better separation of concerns.

The method currently handles multiple responsibilities: setting the doc file, determining symbol type, and managing privacy. Consider splitting these into separate methods for better maintainability.

Consider refactoring like this:

   async setDocFile(docFile) {
     if (!docFile) {
       throw new Error('DocFile is required');
     }
     this.docFile = docFile;
+    await this.determineSymbolType();
+    await this.determinePrivacy();
+  }
+
+  determineSymbolType() {
     if (this.relativePath.match("decorators") && this.symbolType === "function") {
       this.symbolType = "decorator";
       this.symbolLabel = context.symbolTypes["decorator"].label;
       this.symbolCode = context.symbolTypes["decorator"].code;
     }
 
     if (this.relativePath.match("services") && this.symbolType === "function") {
       this.symbolType = "service";
       this.symbolLabel = context.symbolTypes["service"].label;
       this.symbolCode = context.symbolTypes["service"].code;
     }
+  }
+
+  async determinePrivacy() {
     if (this.private === undefined) {
       if (this.symbolType !== "interface") {
         this.private = true;
         try {
           const exported = await this.docFile.importModule();
           if (exported) {
             const symbolPrivate = exported[this.symbolName.trim()];
             this.private = !symbolPrivate;
           }
           if (this.private) {
             this.labels.push({key: "private", value: "private"});
           }
         } catch (error) {
           console.warn(`Failed to import module for ${this.symbolName}: ${error.message}`);
           this.private = true;
           this.labels.push({key: "private", value: "private"});
         }
       }
     }
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 092a96e and 4095987.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (12)
  • .eslintignore (0 hunks)
  • .eslintrc (0 hunks)
  • .github/workflows/build.yml (3 hunks)
  • .mocharc.js (1 hunks)
  • eslint.config.mjs (1 hunks)
  • package.json (2 hunks)
  • packages/ts-doc/src/highlight/index.js (1 hunks)
  • packages/ts-doc/src/models/DocFile.js (3 hunks)
  • packages/ts-doc/src/models/DocSymbol.js (2 hunks)
  • packages/ts-doc/src/models/DocSymbol.spec.js (3 hunks)
  • packages/ts-doc/src/parsers/DocParser.js (1 hunks)
  • packages/ts-doc/src/scan/scan.js (1 hunks)
💤 Files with no reviewable changes (2)
  • .eslintignore
  • .eslintrc
✅ Files skipped from review due to trivial changes (2)
  • .mocharc.js
  • packages/ts-doc/src/highlight/index.js
🔇 Additional comments (14)
.github/workflows/build.yml (2)

34-39: LGTM! Action versions and configuration are well updated.

Good improvements:

  • Updated to latest action versions
  • Added caching for better performance
  • Proper secret management

Just a reminder: Ensure the repository secrets (GH_TOKEN and NPM_TOKEN) are up to date and have the minimum required permissions.

#!/bin/bash
# Check if the workflow has the required permissions in repository settings
gh api repos/:owner/:repo/actions/permissions --jq '.enabled_repositories, .allowed_actions'

Also applies to: 48-48


17-22: Consider testing against multiple Node.js versions.

While the action version updates are good improvements, testing against a single Node.js version (20.x) might miss compatibility issues with other versions that your users might be using. Consider keeping a matrix strategy for critical Node.js versions.

packages/ts-doc/src/models/DocFile.js (1)

67-69: Consider implementing a cache cleanup mechanism.

The mapExported cache grows indefinitely which could lead to memory issues. Consider implementing a cleanup mechanism or using a proper caching solution:

Consider using a proper caching solution like:

const moduleCache = new Map();
// Cleanup old entries periodically or use LRU cache
setInterval(() => moduleCache.clear(), 3600000); // Clear every hour
package.json (3)

46-47: Major ESLint upgrade with enhanced capabilities.

The ESLint ecosystem upgrade brings significant improvements:

  • ESLint v9 with better ESM support
  • TypeScript integration via @typescript-eslint
  • Import sorting and Vitest testing support

However, the major version jump from ESLint v7 to v9 may introduce breaking changes.

#!/bin/bash
# Description: Check for potential ESLint configuration issues
# Test: Look for old ESLint config files that might conflict
echo "Checking for old ESLint config files..."
fd -g '.eslintrc*'

# Test: Check for any ESLint disable comments that might need review
echo -e "\nChecking for eslint-disable comments that might need review..."
rg -i "eslint-disable"

Also applies to: 52-56


62-62: Review Prettier formatting changes and TypeScript configuration.

The upgrade to Prettier v3 may cause significant formatting changes. Additionally, ensure TypeScript is properly configured for ESM support.

#!/bin/bash
# Description: Check for TypeScript and Prettier configuration
# Test: Look for TypeScript config
echo "Checking for TypeScript config..."
fd -g 'tsconfig*.json'

# Test: Look for Prettier config
echo -e "\nChecking for Prettier config..."
fd -g '.prettier*'

Also applies to: 66-66


13-14: Verify ESLint configuration coverage.

The simplified lint commands suggest reliance on ESLint config for scope definition. Ensure the new eslint.config.mjs properly includes all necessary files and extensions.

✅ Verification successful

ESLint configuration is properly set up for the project scope

The simplified lint commands in package.json are justified as the eslint.config.mjs file properly defines:

  • Coverage for all necessary file types (.js, .mjs, .cjs, .jsx, .ts, .tsx)
  • Appropriate ignores for build artifacts and generated files
  • TypeScript support with proper parser configuration
  • Project-specific rule customizations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify ESLint configuration coverage
# Test: Check for ESLint config and its content
echo "Checking for ESLint config file..."
fd -e mjs -e js -e cjs -e json -g '*eslint*config*'

# Test: List all JS/TS files that should be linted
echo -e "\nFiles that should be covered by linting:"
fd -e js -e mjs -e ts -e tsx

Length of output: 481


Script:

#!/bin/bash
# Let's try a more comprehensive search for ESLint configuration
echo "Searching for ESLint config files (including hidden)..."
fd -H -e mjs -e js -e cjs -e json -g '*eslint*config*'
fd -H -g '.eslintrc*'

echo -e "\nChecking package.json for ESLint config..."
cat package.json | grep -A 10 '"eslintConfig"'

echo -e "\nChecking source files that need linting..."
fd -t f -e js -e mjs -e ts -e tsx -e jsx

echo -e "\nChecking if there's any ESLint configuration in package.json..."
cat package.json | jq '.eslintConfig'

Length of output: 15970


Script:

#!/bin/bash
# Let's examine the ESLint configuration file content
echo "Checking ESLint config content..."
cat eslint.config.mjs

echo -e "\nChecking for any ESLint overrides in package.json..."
cat package.json | jq '.overrides'

Length of output: 2171

eslint.config.mjs (3)

1-6: LGTM! Clean and well-organized imports.

The imports are properly structured and use the recommended node: prefix for Node.js built-in modules.


62-62: Verify Prettier configuration exists.

The ESLint configuration includes Prettier integration. Ensure that a Prettier configuration file exists.

#!/bin/bash
# Check for Prettier configuration files
fd -g '.prettierrc*'
if [ $? -ne 0 ]; then
  echo "No Prettier configuration file found. Consider adding one for consistent formatting."
fi

49-61: Verify if Vitest configuration is needed.

The commented Vitest configuration suggests that the project might use Vitest for testing. If so, this configuration should be uncommented and properly configured.

#!/bin/bash
# Check if project uses Vitest
if rg -q "vitest" package.json; then
  echo "Vitest dependency found. Consider uncommenting and configuring the Vitest rules."
fi
packages/ts-doc/src/models/DocSymbol.spec.js (3)

7-7: LGTM: Proper async/await usage

The addition of the async keyword is appropriate for handling the asynchronous setDocFile operation.


Line range hint 47-95: LGTM: Well-structured test suite

The test suite comprehensively covers the DocSymbol functionality with clear, focused test cases. The unchanged assertions indicate good interface stability despite the async implementation changes.


Line range hint 29-45: Verify the impact of requireModule → importModule rename

The rename from requireModule to importModule suggests a transition towards ESM. Let's verify this change is consistent across the codebase.

✅ Verification successful

Let me verify if this is a complete migration by checking the git history for any recent changes related to this rename.


Based on the git history and previous search results, I can now generate the final response:

The rename from requireModule to importModule is complete and consistent

The migration from requireModule to importModule has been properly implemented across all relevant files in the codebase:

  • packages/ts-doc/src/models/DocFile.js: Main implementation
  • packages/ts-doc/src/models/DocSymbol.js: Usage in symbol resolution
  • packages/ts-doc/src/models/DocSymbol.spec.js: Test implementation

The git history confirms this was an intentional rename with all instances updated, and there are no remaining usages of requireModule in the current codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining instances of requireModule to ensure complete migration
echo "Checking for remaining requireModule usage..."
rg "requireModule" -l

# Search for new importModule usage to verify consistent implementation
echo "Checking importModule implementation across files..."
rg "importModule" -A 3

Length of output: 1081


Script:

#!/bin/bash
# Check git history for requireModule to importModule changes
git log -p | grep -E '[-+].*require(Module|module)|[-+].*import(Module|module)' -A 2 -B 2

Length of output: 863

packages/ts-doc/src/parsers/DocParser.js (1)

39-42: Verify concurrent access safety.

The code modifies shared state (context.symbols and docFile.symbols) in an async loop. This could lead to race conditions if parse() is called concurrently on the same docFile.

Let's check for concurrent usage:

Consider one of these approaches to prevent race conditions:

  1. Add a mutex/lock mechanism
  2. Use immutable data structures
  3. Document that the method is not thread-safe
packages/ts-doc/src/models/DocSymbol.js (1)

Line range hint 162-192: Verify the impact of async changes across the codebase.

The conversion to async might require updates in calling code.

Let's verify the usage of this method:

✅ Verification successful

Based on the search results, I can generate the final response as we have sufficient information about the usage of setDocFile.

All calls to setDocFile are properly awaited

The method is called in two locations and both correctly use the await keyword:

  • packages/ts-doc/src/models/DocSymbol.spec.js: await this.docSymbol.setDocFile({
  • packages/ts-doc/src/parsers/DocParser.js: await symbol.setDocFile(docFile);
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for setDocFile calls to ensure they're properly awaited
rg "setDocFile\(" -A 2

Length of output: 807

Comment on lines 52 to 56
async scanFiles(patterns) {
context.logger("Scan folders '" + chalk.cyan(JSON.stringify(patterns)) + "'");

let symbolsSize = 0;
const files = globby.sync(patterns);
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 globby.sync() with async alternative.

Using synchronous file system operations in an async function can block the event loop. Consider using the Promise-based API from globby instead.

- const files = globby.sync(patterns);
+ const files = await globby(patterns);
📝 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
async scanFiles(patterns) {
context.logger("Scan folders '" + chalk.cyan(JSON.stringify(patterns)) + "'");
let symbolsSize = 0;
const files = globby.sync(patterns);
async scanFiles(patterns) {
context.logger("Scan folders '" + chalk.cyan(JSON.stringify(patterns)) + "'");
let symbolsSize = 0;
const files = await globby(patterns);

Comment on lines +58 to +69
for (const file of files) {
try {
DocParser.parse(new DocFile(file)).forEach((symbol) => {
const symbols = await DocParser.parse(new DocFile(file));

symbols.forEach((symbol) => {
context.logger(`Scanned symbol '${chalk.cyan(symbol.symbolName)}'`);
symbolsSize++;
});
} catch (er) {
context.logger.error(chalk.red(er), er.stack);
}
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling and reporting.

While the async/await conversion is correct, the error handling could be improved to:

  1. Track failed files separately
  2. Provide more accurate final statistics
+ let failedFiles = 0;
  for (const file of files) {
    try {
      const symbols = await DocParser.parse(new DocFile(file));
      symbols.forEach((symbol) => {
        context.logger(`Scanned symbol '${chalk.cyan(symbol.symbolName)}'`);
        symbolsSize++;
      });
    } catch (er) {
      context.logger.error(chalk.red(er), er.stack);
+     context.logger.error(`Failed to parse file: ${chalk.red(file)}`);
+     failedFiles++;
    }
  }

- context.logger(`${chalk.green(symbolsSize)} scanned symbols`);
+ context.logger(
+   `${chalk.green(symbolsSize)} scanned symbols, ` +
+   `${chalk.red(failedFiles)} files failed`
+ );

Committable suggestion was skipped due to low confidence.

@@ -13,7 +14,7 @@ class DocFile {
constructor(file) {
this.file = normalizePath(file);
this.symbols = new Map();
this.contents = fs.readFileSync(file).toString();
this.contents = fsSync.readFileSync(file).toString();
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 implementing lazy loading for file contents.

Reading file contents synchronously in the constructor could block execution for large files. Consider implementing lazy loading where contents are read only when needed:

 constructor(file) {
   this.file = normalizePath(file);
   this.symbols = new Map();
-  this.contents = fsSync.readFileSync(file).toString();
+  this._contents = null;
 }
+
+get contents() {
+  if (!this._contents) {
+    this._contents = fsSync.readFileSync(this.file).toString();
+  }
+  return this._contents;
+}
📝 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
this.contents = fsSync.readFileSync(file).toString();
constructor(file) {
this.file = normalizePath(file);
this.symbols = new Map();
this._contents = null;
}
get contents() {
if (!this._contents) {
this._contents = fsSync.readFileSync(this.file).toString();
}
return this._contents;
}

Comment on lines 58 to 59
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
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 using async file existence check.

Using synchronous existsSync with async readFile creates an inconsistent pattern. Consider using async operations throughout:

-if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
-  const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
+try {
+  const pkgContent = await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"});
+  const pkg = JSON.parse(pkgContent);
   file = path.join(modulePath, pkg.main);
+} catch (error) {
+  if (error.code !== 'ENOENT') {
+    throw error;
+  }
 }
📝 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
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
try {
const pkgContent = await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"});
const pkg = JSON.parse(pkgContent);
file = path.join(modulePath, pkg.main);
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}

Comment on lines 54 to 70
async importModule() {
const {modulePath} = this.module;
let file = path.join(modulePath, "index.js");

if (fs.existsSync(path.join(modulePath, "package.json"))) {
const pkg = require(path.join(modulePath, "package.json"));
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
file = path.join(modulePath, pkg.main);
}

if (mapExported.has(file)) {
return mapExported.get(file);
}

if (fs.existsSync(file)) {
mapExported.set(file, require(file));
if (fsSync.existsSync(file)) {
mapExported.set(file, await import(file));
return mapExported.get(file);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for file operations.

The method lacks try-catch blocks for file operations which could fail. Consider adding proper error handling:

 async importModule() {
   const {modulePath} = this.module;
   let file = path.join(modulePath, "index.js");

+  try {
     if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
       const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
       file = path.join(modulePath, pkg.main);
     }

     if (mapExported.has(file)) {
       return mapExported.get(file);
     }

     if (fsSync.existsSync(file)) {
       mapExported.set(file, await import(file));
       return mapExported.get(file);
     }

     return undefined;
+  } catch (error) {
+    throw new Error(`Failed to import module from ${file}: ${error.message}`);
+  }
 }
📝 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
async importModule() {
const {modulePath} = this.module;
let file = path.join(modulePath, "index.js");
if (fs.existsSync(path.join(modulePath, "package.json"))) {
const pkg = require(path.join(modulePath, "package.json"));
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
file = path.join(modulePath, pkg.main);
}
if (mapExported.has(file)) {
return mapExported.get(file);
}
if (fs.existsSync(file)) {
mapExported.set(file, require(file));
if (fsSync.existsSync(file)) {
mapExported.set(file, await import(file));
return mapExported.get(file);
}
async importModule() {
const {modulePath} = this.module;
let file = path.join(modulePath, "index.js");
try {
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
file = path.join(modulePath, pkg.main);
}
if (mapExported.has(file)) {
return mapExported.get(file);
}
if (fsSync.existsSync(file)) {
mapExported.set(file, await import(file));
return mapExported.get(file);
}
return undefined;
} catch (error) {
throw new Error(`Failed to import module from ${file}: ${error.message}`);
}
}

Comment on lines 26 to 30
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
tsconfigRootDir: join(import.meta.dirname, "tsconfig.eslint.json")
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix potential tsconfig path resolution issue.

The current path resolution might not work correctly. Consider using the following approach:

-        tsconfigRootDir: join(import.meta.dirname, "tsconfig.eslint.json")
+        project: "./tsconfig.eslint.json"
📝 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
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
tsconfigRootDir: join(import.meta.dirname, "tsconfig.eslint.json")
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.eslint.json"
},

]
},
{
files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
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 separating TypeScript and JavaScript patterns.

For better maintainability, consider splitting the file patterns:

-    files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
+    files: [
+      "**/*.{js,mjs,cjs,jsx,mjsx}",  // JavaScript files
+      "**/*.{ts,tsx,mtsx}"           // TypeScript files
+    ],
📝 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
files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
files: [
"**/*.{js,mjs,cjs,jsx,mjsx}", // JavaScript files
"**/*.{ts,tsx,mtsx}" // TypeScript files
],

Comment on lines +38 to +47
rules: {
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-non-null-assertion": 0
}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Enable TypeScript ESLint rules for better code quality

The project heavily uses TypeScript as evidenced by:

  • Numerous .d.ts files in the codebase
  • TypeScript dependencies in package.json
  • TypeScript parser and plugin configuration in ESLint

Consider enabling these essential rules with at least warning level (1):

  • @typescript-eslint/no-explicit-any: Prevents the use of unsafe any type
  • @typescript-eslint/no-unused-vars: Catches dead code and typos
  • @typescript-eslint/explicit-module-boundary-types: Ensures better API documentation
  • @typescript-eslint/no-non-null-assertion: Prevents potential runtime errors

The other disabled rules can remain at 0 if they conflict with your coding standards.

🔗 Analysis chain

Review disabled TypeScript rules.

Disabling all TypeScript rules (0) might lead to code quality issues. Consider:

  1. Enabling essential rules like no-explicit-any and no-unused-vars
  2. Using warning level (1) instead of completely disabling rules
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any TypeScript files in the project
fd -e ts -e tsx

# If TypeScript files exist, we should have stricter rules
if [ $? -eq 0 ]; then
  echo "TypeScript files found. Consider enabling essential TypeScript rules."
fi

Length of output: 485


Script:

#!/bin/bash
# Let's check for TypeScript files more thoroughly
rg --type-list | grep -i typescript
echo "=== Checking for TypeScript files ==="
find . -type f -name "*.ts" -o -name "*.tsx" 2>/dev/null

echo -e "\n=== Checking package.json for TypeScript ==="
rg "typescript" package.json

echo -e "\n=== Checking for tsconfig.json ==="
find . -name "tsconfig*.json" 2>/dev/null

echo -e "\n=== Checking current ESLint configuration ==="
cat eslint.config.mjs

Length of output: 9609

Comment on lines 36 to 43
static async parse(docFile) {
const {symbols} = new DocParser(docFile.contents).parse();

symbols.forEach((symbol) => {
symbol.setDocFile(docFile);
symbol = context.symbols.push(symbol);
docFile.symbols.set(symbol.symbolName, symbol);
});
for (const [, symbol] of symbols) {
await symbol.setDocFile(docFile);
const newSymbol = context.symbols.push(symbol);
docFile.symbols.set(newSymbol.symbolName, newSymbol);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider adding error handling for async operations.

While the async implementation is correct, the method lacks proper error handling for potential failures in setDocFile or context operations.

Consider wrapping the async operations in a try-catch block:

  static async parse(docFile) {
    const {symbols} = new DocParser(docFile.contents).parse();

    for (const [, symbol] of symbols) {
+     try {
        await symbol.setDocFile(docFile);
        const newSymbol = context.symbols.push(symbol);
        docFile.symbols.set(newSymbol.symbolName, newSymbol);
+     } catch (error) {
+       console.error(`Failed to process symbol: ${error.message}`);
+       throw error;
+     }
    }

    return docFile.symbols;
  }
📝 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 async parse(docFile) {
const {symbols} = new DocParser(docFile.contents).parse();
symbols.forEach((symbol) => {
symbol.setDocFile(docFile);
symbol = context.symbols.push(symbol);
docFile.symbols.set(symbol.symbolName, symbol);
});
for (const [, symbol] of symbols) {
await symbol.setDocFile(docFile);
const newSymbol = context.symbols.push(symbol);
docFile.symbols.set(newSymbol.symbolName, newSymbol);
}
static async parse(docFile) {
const {symbols} = new DocParser(docFile.contents).parse();
for (const [, symbol] of symbols) {
try {
await symbol.setDocFile(docFile);
const newSymbol = context.symbols.push(symbol);
docFile.symbols.set(newSymbol.symbolName, newSymbol);
} catch (error) {
console.error(`Failed to process symbol: ${error.message}`);
throw error;
}
}
return docFile.symbols;
}

🛠️ Refactor suggestion

Consider batching symbol processing for performance.

The current implementation processes symbols sequentially. For large documentation files, this could be inefficient.

Consider using Promise.all for parallel processing:

  static async parse(docFile) {
    const {symbols} = new DocParser(docFile.contents).parse();

-   for (const [, symbol] of symbols) {
-     await symbol.setDocFile(docFile);
-     const newSymbol = context.symbols.push(symbol);
-     docFile.symbols.set(newSymbol.symbolName, newSymbol);
-   }
+   await Promise.all(
+     Array.from(symbols.values()).map(async (symbol) => {
+       await symbol.setDocFile(docFile);
+       const newSymbol = context.symbols.push(symbol);
+       docFile.symbols.set(newSymbol.symbolName, newSymbol);
+     })
+   );

    return docFile.symbols;
  }
📝 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 async parse(docFile) {
const {symbols} = new DocParser(docFile.contents).parse();
symbols.forEach((symbol) => {
symbol.setDocFile(docFile);
symbol = context.symbols.push(symbol);
docFile.symbols.set(symbol.symbolName, symbol);
});
for (const [, symbol] of symbols) {
await symbol.setDocFile(docFile);
const newSymbol = context.symbols.push(symbol);
docFile.symbols.set(newSymbol.symbolName, newSymbol);
}
static async parse(docFile) {
const {symbols} = new DocParser(docFile.contents).parse();
await Promise.all(
Array.from(symbols.values()).map(async (symbol) => {
await symbol.setDocFile(docFile);
const newSymbol = context.symbols.push(symbol);
docFile.symbols.set(newSymbol.symbolName, newSymbol);
})
);
return docFile.symbols;
}

@Romakita Romakita merged commit 546b6fc into master Oct 28, 2024
4 checks passed
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: 4

🧹 Outside diff range and nitpick comments (1)
packages/ts-doc/src/models/DocFile.js (1)

64-71: Consider using a more robust caching mechanism.

The global mapExported Map could lead to memory leaks and makes testing difficult. Consider:

  1. Moving cache to class level
  2. Adding cache invalidation
  3. Implementing a proper cache interface
 class DocFile {
+  static #moduleCache = new Map();
+  
+  static clearCache() {
+    this.#moduleCache.clear();
+  }
   
   async importModule() {
     // ...
-    if (mapExported.has(file)) {
-      return mapExported.get(file);
+    if (DocFile.#moduleCache.has(file)) {
+      return DocFile.#moduleCache.get(file);
     }
     
     if (fsSync.existsSync(file)) {
-      mapExported.set(file, await import(file));
-      return mapExported.get(file);
+      const module = await import(file);
+      DocFile.#moduleCache.set(file, module);
+      return module;
     }
   }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4095987 and 995cbd9.

📒 Files selected for processing (4)
  • eslint.config.mjs (1 hunks)
  • packages/ts-doc/src/models/DocFile.js (3 hunks)
  • packages/ts-doc/src/parsers/DocParser.js (2 hunks)
  • packages/ts-doc/src/scan/scan.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • eslint.config.mjs
  • packages/ts-doc/src/parsers/DocParser.js
🔇 Additional comments (4)
packages/ts-doc/src/scan/scan.js (2)

56-56: LGTM! Async globby implementation.

The conversion from globby.sync to async globby is correct and addresses the event loop blocking concern.


58-69: 🛠️ Refactor suggestion

Enhance error handling with file tracking.

While the async conversion is correct, consider implementing more robust error handling to track failed files and provide better feedback.

Apply this enhancement:

+ let failedFiles = 0;
  for (const file of files) {
    try {
      const symbols = await DocParser.parse(new DocFile(file));
      symbols.forEach((symbol) => {
        context.logger(`Scanned symbol '${chalk.cyan(symbol.symbolName)}'`);
        symbolsSize++;
      });
    } catch (er) {
      context.logger.error(chalk.red(er), er.stack);
+     context.logger.error(`Failed to parse file: ${chalk.red(file)}`);
+     failedFiles++;
    }
  }

- context.logger(`${chalk.green(symbolsSize)} scanned symbols`);
+ context.logger(
+   `${chalk.green(symbolsSize)} scanned symbols, ` +
+   `${chalk.red(failedFiles)} files failed`
+ );
packages/ts-doc/src/models/DocFile.js (2)

3-4: LGTM! Good separation of sync/async fs operations.

The split between sync and async file system operations is well-structured, and the logger addition supports proper error handling.

Also applies to: 7-7


18-18: Previous review comment about lazy loading is still applicable.

The synchronous file reading in the constructor could still benefit from lazy loading implementation as suggested in the previous review.

@@ -49,21 +49,24 @@ module.exports = {
*
* @param patterns
*/
scanFiles(patterns) {
async scanFiles(patterns) {
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 converting other scanning methods to async.

While the async conversion of scanFiles is correct, consider converting scanComponents and scanTemplate methods to async as well for consistency, since they also use globby.

Apply this pattern to other methods:

- scanComponents(directory) {
+ async scanComponents(directory) {
   context.logger("Scan components '" + chalk.cyan(directory) + "'");
-  const files = globby.sync(path.join(directory, "**/*.ejs"));
+  const files = await globby(path.join(directory, "**/*.ejs"));

- scanTemplate(templatePattern) {
+ async scanTemplate(templatePattern) {
   context.logger("Scan template directory '" + chalk.cyan(templatePattern) + "'");
-  let files = globby.sync(templatePattern);
+  let files = await globby(templatePattern);

Committable suggestion was skipped due to low confidence.

Comment on lines +72 to 74
} catch (er) {
logger.error("Fail to import module", {file, modulePath});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance error handling with specific error types.

The current error handling swallows all errors and only logs them. Consider:

  1. Creating specific error types
  2. Providing more context in error messages
  3. Allowing error propagation for critical failures
-  } catch (er) {
-    logger.error("Fail to import module", {file, modulePath});
+  } catch (error) {
+    logger.error("Failed to import module", {
+      file,
+      modulePath,
+      errorCode: error.code,
+      errorMessage: error.message
+    });
+    if (error.code !== 'MODULE_NOT_FOUND') {
+      throw new Error(`Failed to import module at ${file}: ${error.message}`);
+    }
   }
📝 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
} catch (er) {
logger.error("Fail to import module", {file, modulePath});
}
} catch (error) {
logger.error("Failed to import module", {
file,
modulePath,
errorCode: error.code,
errorMessage: error.message
});
if (error.code !== 'MODULE_NOT_FOUND') {
throw new Error(`Failed to import module at ${file}: ${error.message}`);
}
}

Comment on lines +59 to +62
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
file = path.join(modulePath, pkg.main);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace sync existence check with async implementation.

The mix of existsSync with async readFile creates an inconsistent pattern. Consider using fs.access or catching ENOENT from readFile.

-if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
-  const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
-  file = path.join(modulePath, pkg.main);
+try {
+  const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
+  file = path.join(modulePath, pkg.main);
+} catch (error) {
+  if (error.code !== 'ENOENT') {
+    throw error;
+  }
+}
📝 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
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
file = path.join(modulePath, pkg.main);
}
try {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
file = path.join(modulePath, pkg.main);
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}

Comment on lines +55 to 74
async importModule() {
const {modulePath} = this.module;
let file = path.join(modulePath, "index.js");
try {
if (fsSync.existsSync(path.join(modulePath, "package.json"))) {
const pkg = JSON.parse(await fs.readFile(path.join(modulePath, "package.json"), {encoding: "utf-8"}));
file = path.join(modulePath, pkg.main);
}

if (fs.existsSync(path.join(modulePath, "package.json"))) {
const pkg = require(path.join(modulePath, "package.json"));
file = path.join(modulePath, pkg.main);
}

if (mapExported.has(file)) {
return mapExported.get(file);
}
if (mapExported.has(file)) {
return mapExported.get(file);
}

if (fs.existsSync(file)) {
mapExported.set(file, require(file));
return mapExported.get(file);
if (fsSync.existsSync(file)) {
mapExported.set(file, await import(file));
return mapExported.get(file);
}
} catch (er) {
logger.error("Fail to import module", {file, modulePath});
}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the gathered information, I can now provide the final response:

ESM changes require additional configuration before implementation

The codebase currently uses CommonJS (require/module.exports) throughout, and there's no ESM configuration in package.json. Switching to import statements without proper setup will cause compatibility issues:

  • packages/ts-doc/src/scan/scan.js and packages/ts-doc/src/models/DocFile.spec.js use CommonJS requires
  • packages/ts-doc/src/models/DocFile.js exports using CommonJS
  • Package.json lacks "type": "module" field and ESM-related configurations
🔗 Analysis chain

Verify the impact of ESM changes across the codebase.

The switch from require to import might affect other parts of the codebase that depend on this module.


Let me gather more information about the ESM compatibility by checking the package.json and any other ESM-related configurations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for requires of this module
rg "require.*DocFile" -A 2

# Search for potential CommonJS interop issues
rg "module\.exports.*=.*DocFile" -A 2

Length of output: 576


Script:

#!/bin/bash
# Check package.json for type and exports configuration
cat packages/ts-doc/package.json

# Look for any ESM-specific configurations or imports
rg "type.*=.*module" packages/ts-doc/
rg "import.*from" packages/ts-doc/src/

Length of output: 2515

@Romakita
Copy link
Collaborator Author

🎉 This PR is included in version 4.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant