trendmicro-frontend / tonic-ui

Tonic UI is a UI component library for React, built with Emotion and Styled System. It is designed to be easy to use and easy to customize.
https://trendmicro-frontend.github.io/tonic-ui
MIT License
125 stars 28 forks source link

feat(codemod): add `--extensions`, `--ignore-pattern`, and `--verbose` as configurable command-line options #877

Closed cheton closed 2 months ago

cheton commented 2 months ago

Changelog

codesandbox[bot] commented 2 months ago

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders
Open Preview

codesandbox-ci[bot] commented 2 months ago

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

trendmicro-frontend-bot commented 2 months ago

Tonic UI Demo

codecov[bot] commented 2 months ago

Codecov Report

All modified and coverable lines are covered by tests :white_check_mark:

Project coverage is 71.92%. Comparing base (373161b) to head (0c96293).

:exclamation: Current head 0c96293 differs from pull request most recent head 2b731ad

Please upload reports for the commit 2b731ad to get more accurate results.

Additional details and impacted files ```diff @@ Coverage Diff @@ ## v2 #877 +/- ## ========================================== - Coverage 71.97% 71.92% -0.05% ========================================== Files 375 375 Lines 6140 6140 ========================================== - Hits 4419 4416 -3 - Misses 1721 1724 +3 ``` | [Flag](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | Coverage Δ | | |---|---|---| | [codemod](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | `74.28% <ø> (ø)` | | | [react](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | `69.71% <ø> (-0.06%)` | :arrow_down: | | [react-base](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | `100.00% <ø> (ø)` | | | [react-hooks](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | `87.59% <ø> (ø)` | | | [styled-system](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | `93.42% <ø> (ø)` | | | [theme](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | `100.00% <ø> (ø)` | | | [utils](https://app.codecov.io/gh/trendmicro-frontend/tonic-ui/pull/877/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend) | `70.28% <ø> (ø)` | | Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trendmicro-frontend#carryforward-flags-in-the-pull-request-comment) to find out more.

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

cheton commented 2 months ago

/review --pr_reviewer.extra_instructions=" In the possible issues section, emphasize the following:

codiumai-pr-agent-pro[bot] commented 2 months ago

PR Reviewer Guide 🔍

(Review updated until commit https://github.com/trendmicro-frontend/tonic-ui/commit/4d72bd90b42784328363276ace6126493024df09)

⏱️ Estimated effort to review [1-5] 3
🏅 Score 89
🧪 Relevant tests Yes
🔒 Security concerns No
⚡ Key issues to review None
cheton commented 2 months ago

/ask Are there any tests that can be improved in this PR?

cheton commented 2 months ago

/update_changelog

codiumai-pr-agent-pro[bot] commented 2 months ago

Changelog updates: 🔄

2024-06-13

Added

Changed

to commit the new content to the CHANGELOG.md file, please type: '/update_changelog --pr_update_changelog.push_changelog_changes=true'

codiumai-pr-agent-pro[bot] commented 2 months ago

Ask

Are there any tests that can be improved in this PR?

Answer:

The tests included in this PR are generally well-structured and cover the basic functionality of the new command-line options. However, there are a few areas where the tests could be improved for better coverage and robustness:

  1. Test for --print Option:

    • Currently, there is no test that verifies the behavior when the --print option is used. Adding a test to ensure that the transformed files are printed to stdout would be beneficial.
  2. Test for --jscodeshift Option:

    • There is no test that checks the behavior when advanced options are passed directly to jscodeshift using the --jscodeshift flag. Adding a test for this would ensure that the flag is correctly handled.
  3. Test for Multiple --ignore-pattern Values:

    • The code allows for multiple ignore patterns, but there is no test to verify this functionality. Adding a test to check if multiple ignore patterns are correctly passed to jscodeshift would be useful.
  4. Test for Default Values:

    • While the tests do check for some default values, it would be good to have explicit tests that verify all default values for the flags (extensions, parser, ignorePattern, etc.).
  5. Edge Cases and Error Handling:

    • Adding tests for edge cases, such as empty file paths or invalid extensions, would help ensure the robustness of the code.

Here are some examples of how these tests could be added:

Test for --print Option

it('should print transformed files to stdout when --print is true', () => {
  const mockSpawnSync = jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ error: null });

  const transform = 'react/v2.0.0/import-react-icons';
  const files = ['/path/to/file1.js'];
  const flags = {
    extensions: 'js,ts,jsx,tsx,json',
    parser: 'babel',
    ignorePattern: '**/node_modules/**',
    dry: false,
    print: true,
    jscodeshift: null,
  };

  run(transform, files, flags, []);

  expect(mockSpawnSync).toHaveBeenCalledWith(
    'node',
    expect.arrayContaining([
      '--print',
      ...files,
    ]),
    { stdio: 'inherit' }
  );
});

Test for --jscodeshift Option

it('should pass advanced options to jscodeshift when --jscodeshift is provided', () => {
  const mockSpawnSync = jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ error: null });

  const transform = 'react/v2.0.0/import-react-icons';
  const files = ['/path/to/file1.js'];
  const flags = {
    extensions: 'js,ts,jsx,tsx,json',
    parser: 'babel',
    ignorePattern: '**/node_modules/**',
    dry: false,
    print: false,
    jscodeshift: '--some-advanced-option',
  };

  run(transform, files, flags, []);

  expect(mockSpawnSync).toHaveBeenCalledWith(
    'node',
    expect.arrayContaining([
      '--some-advanced-option',
      ...files,
    ]),
    { stdio: 'inherit' }
  );
});

Test for Multiple --ignore-pattern Values

it('should handle multiple ignore patterns', () => {
  const mockSpawnSync = jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ error: null });

  const transform = 'react/v2.0.0/import-react-icons';
  const files = ['/path/to/file1.js'];
  const flags = {
    extensions: 'js,ts,jsx,tsx,json',
    parser: 'babel',
    ignorePattern: ['**/node_modules/**', '**/dist/**'],
    dry: false,
    print: false,
    jscodeshift: null,
  };

  run(transform, files, flags, []);

  expect(mockSpawnSync).toHaveBeenCalledWith(
    'node',
    expect.arrayContaining([
      '--ignore-pattern',
      '**/node_modules/**',
      '--ignore-pattern',
      '**/dist/**',
      ...files,
    ]),
    { stdio: 'inherit' }
  );
});

Test for Default Values

it('should use default values for unspecified flags', () => {
  const mockSpawnSync = jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ error: null });

  const transform = 'react/v2.0.0/import-react-icons';
  const files = ['/path/to/file1.js'];
  const flags = {};

  run(transform, files, flags, []);

  expect(mockSpawnSync).toHaveBeenCalledWith(
    'node',
    expect.arrayContaining([
      '--extensions',
      'js,ts,jsx,tsx,json',
      '--parser',
      'babel',
      '--ignore-pattern',
      '**/node_modules/**',
      ...files,
    ]),
    { stdio: 'inherit' }
  );
});

Adding these tests will improve the coverage and ensure that the new command-line options are thoroughly tested.

cheton commented 2 months ago

/ask How to sanitize inputs for this PR when using childProcess.spawnSync?

codiumai-pr-agent-pro[bot] commented 2 months ago

Ask

How to sanitize inputs for this PR when using childProcess.spawnSync?

Answer:

To sanitize inputs when using childProcess.spawnSync in this PR, you should ensure that any user-provided inputs are validated and escaped to prevent command injection attacks. Here are some steps you can take to sanitize the inputs:

  1. Validate Inputs: Ensure that the inputs conform to expected formats and types. For example, file paths should be valid paths, and flags should be within an expected set of values.

  2. Escape Special Characters: Escape any special characters in the inputs that could be interpreted by the shell.

  3. Use a Whitelist Approach: For flags and options, use a whitelist approach where only known and expected values are allowed.

  4. Avoid Shell Interpretation: Use spawnSync with an array of arguments rather than a single command string to avoid shell interpretation.

Here is an example of how you can implement these steps in the run function:

const childProcess = require('child_process');
const fs = require('fs');
const path = require('path');
const jscodeshiftPackage = require('jscodeshift/package.json');

const jscodeshiftDirectory = path.dirname(require.resolve('jscodeshift'));
const jscodeshiftExecutable = path.join(jscodeshiftDirectory, jscodeshiftPackage.bin.jscodeshift);

function sanitizeInput(input) {
  // Basic sanitization example: remove any characters that are not alphanumeric, dashes, or underscores
  return input.replace(/[^a-zA-Z0-9-_.,/]/g, '');
}

function run(transform, files, flags, codemodFlags) {
  const transformPaths = [
    path.resolve(__dirname, './src', `${sanitizeInput(transform)}/index.js`),
    path.resolve(__dirname, './src', `${sanitizeInput(transform)}.js`),
    path.resolve(__dirname, './dist', `${sanitizeInput(transform)}/index.js`),
    path.resolve(__dirname, './dist', `${sanitizeInput(transform)}.js`),
  ];

  let transformPath;
  let error;

  for (transformPath of transformPaths) {
    try {
      fs.statSync(transformPath);
      error = undefined;
      break;
    } catch (err) {
      error = err;
      continue;
    }
  }

  if (error) {
    if (error?.code === 'ENOENT') {
      throw new Error(
        `${JSON.stringify(transform)} not found. Check out ${path.resolve(__dirname, './README.md for a list of available codemods.')}`,
      );
    }
    throw error;
  }

  const args = [
    jscodeshiftExecutable,
    '--transform',
    transformPath,
    ...codemodFlags,
  ];

  if (flags.extensions) {
    args.push('--extensions');
    args.push(sanitizeInput(flags.extensions));
  }

  if (flags.parser) {
    args.push('--parser');
    args.push(sanitizeInput(flags.parser));
  }

  if (flags.ignorePattern) {
    const ignorePatterns = Array.isArray(flags.ignorePattern) ? flags.ignorePattern : [flags.ignorePattern];
    ignorePatterns.filter(Boolean).forEach(pattern => {
      args.push('--ignore-pattern', sanitizeInput(pattern));
    });
  }

  if (flags.dry) {
    args.push('--dry');
  }
  if (flags.print) {
    args.push('--print');
  }
  if (flags.jscodeshift) {
    args.push(sanitizeInput(flags.jscodeshift));
  }

  files.forEach(file => {
    args.push(path.resolve(sanitizeInput(file)));
  });

  console.log(`Executing command: ${args.join(' ')}`);
  const jscodeshiftProcess = childProcess.spawnSync('node', args, { stdio: 'inherit' });

  if (jscodeshiftProcess.error) {
    throw jscodeshiftProcess.error;
  }
}

module.exports = run;

In this example, the sanitizeInput function is a simple implementation that removes any characters that are not alphanumeric, dashes, underscores, commas, or slashes. This helps to prevent injection attacks by ensuring that only safe characters are passed to the command. You may need to adjust the sanitization logic based on the specific requirements and constraints of your application.

cheton commented 2 months ago

/ask The glob pattern should be allowed in your sanitizeInput function

codiumai-pr-agent-pro[bot] commented 2 months ago

Ask

The glob pattern should be allowed in your sanitizeInput function

Answer:

The provided PR diff does not include a sanitizeInput function. However, based on the context of the changes, it seems like the sanitizeInput function should be designed to handle and allow glob patterns, especially since the new --ignore-pattern option uses glob patterns.

To ensure glob patterns are allowed in your sanitizeInput function, you should make sure that the function does not strip or alter characters that are commonly used in glob patterns (e.g., *, ?, [, ], {, }, !, etc.).

Here is an example of how you might implement or modify a sanitizeInput function to allow glob patterns:

function sanitizeInput(input) {
  // Assuming input is a string or an array of strings
  if (Array.isArray(input)) {
    return input.map(sanitizeInput);
  }

  // Allow common glob pattern characters and ensure the input is a string
  const allowedCharacters = /^[a-zA-Z0-9_\-\/\*\?\[\]\{\}\!\.\,]+$/;
  if (typeof input === 'string' && allowedCharacters.test(input)) {
    return input;
  }

  throw new Error('Invalid input: Only alphanumeric characters, dashes, slashes, and common glob pattern characters are allowed.');
}

In this example, the allowedCharacters regular expression includes alphanumeric characters, dashes, slashes, and common glob pattern characters. The function checks if the input matches this pattern and throws an error if it does not.

If you need more specific handling or sanitization based on your application's requirements, you can adjust the regular expression accordingly.

codiumai-pr-agent-pro[bot] commented 2 months ago

Ask

Is there anything remaining that's worth improving

Answer:

The PR introduces several improvements and new features, but there are a few areas that could be further enhanced:

  1. Error Handling and Logging:

    • The run function in codemod-main.js logs the command being executed but does not log errors in a user-friendly way. Consider adding more descriptive error messages and logging for better debugging.
    • Example:
      if (jscodeshiftProcess.error) {
      console.error('Error executing jscodeshift:', jscodeshiftProcess.error);
      throw jscodeshiftProcess.error;
      }
  2. Validation of Command-Line Arguments:

    • While the sanitizeInput function ensures that inputs contain only allowed characters, it might be beneficial to add more specific validation for each flag (e.g., ensuring extensions are valid file extensions).
    • Example:
      function validateExtensions(extensions) {
      const validExtensions = ['js', 'ts', 'jsx', 'tsx', 'json'];
      extensions.split(',').forEach(ext => {
       if (!validExtensions.includes(ext)) {
         throw new Error(`Invalid extension: ${ext}`);
       }
      });
      }
  3. Unit Test Coverage:

    • The unit tests cover various scenarios, but it would be beneficial to add tests for edge cases, such as invalid inputs for the --extensions and --ignore-pattern options.
    • Example:

      it('should throw an error for invalid extensions', () => {
      const transform = 'react/v2.0.0/import-react-icons';
      const files = ['/path/to/file1.js'];
      const flags = {
       extensions: 'invalidExt',
       parser: 'babel',
       ignorePattern: '**/node_modules/**',
       dry: false,
       print: false,
       jscodeshift: null,
      };
      
      expect(() => run(transform, files, flags, [])).toThrowError('Invalid extension: invalidExt');
      });
  4. Documentation:

    • The README has been updated, but it could include more detailed examples and explanations of the new options (--extensions and --ignore-pattern).
    • Example:

      ### Using `--extensions`
      To specify which file extensions to transform, use the `--extensions` option:
      ```bash
      npx @tonic-ui/codemod@latest <codemod> <path> --extensions=ts,tsx

      Using --ignore-pattern

      To ignore files matching a specific pattern, use the --ignore-pattern option:

      npx @tonic-ui/codemod@latest <codemod> <path> --ignore-pattern='**/dist/**'
  5. Code Duplication:

    • The sanitizeInput function is used multiple times for different flags. Consider refactoring to reduce duplication.
    • Example:

      function addFlag(args, flagName, flagValue) {
      if (flagValue) {
       args.push(`--${flagName}=${sanitizeInput(flagValue)}`);
      }
      }
      
      // Usage
      addFlag(args, 'extensions', flags.extensions);
      addFlag(args, 'parser', flags.parser);

By addressing these points, the PR can be further improved in terms of robustness, usability, and maintainability.

cheton commented 2 months ago

/ask Is there anything remaining that's worth improving

codiumai-pr-agent-pro[bot] commented 2 months ago

Ask

Is there anything remaining that's worth improving

Answer:

The PR introduces several improvements and new features to the codemod CLI, including the addition of --extensions and --ignore-pattern options, and refactors the implementation. However, there are a few areas where further improvements could be made:

  1. Documentation Updates:

    • The README.md file should be updated to include examples and explanations for the new --extensions and --ignore-pattern options. This will help users understand how to use these new features effectively.
  2. Error Handling:

    • The error message for missing codemods could be more user-friendly. Instead of throwing a generic error, consider providing suggestions or a link to the documentation where users can find available codemods.
  3. Code Duplication:

    • The sanitizeInput function is used to validate inputs. It might be beneficial to add unit tests specifically for this function to ensure it handles all edge cases correctly.
  4. Logging and Debugging:

    • The current implementation logs the command being executed. It might be useful to add more detailed logging, especially for debugging purposes. For example, logging the sanitized inputs or the resolved paths of the transforms.
  5. Test Coverage:

    • While the PR includes several unit tests, consider adding more tests to cover edge cases, such as invalid inputs for the --extensions and --ignore-pattern options.
  6. Code Comments:

    • Adding comments to explain the purpose of certain blocks of code, especially in the run function in codemod-main.js, can improve readability and maintainability.
  7. Consistency in Option Naming:

    • Ensure that all command-line options follow a consistent naming convention. For example, ignore-pattern uses a hyphen, while jscodeshift does not. Consider standardizing this for better consistency.
  8. Performance Considerations:

    • If the sanitizeInput function is called frequently, consider optimizing it for performance, especially if it needs to handle large arrays of inputs.

By addressing these points, the PR can be further improved to enhance usability, maintainability, and robustness.

cheton commented 2 months ago

/update_changelog

codiumai-pr-agent-pro[bot] commented 2 months ago

Changelog updates: 🔄

2024-06-13

Added

Changed

to commit the new content to the CHANGELOG.md file, please type: '/update_changelog --pr_update_changelog.push_changelog_changes=true'

cheton commented 2 months ago

/review --pr_reviewer.inline_code_comments=true --pr_reviewer.require_score_review=true

codiumai-pr-agent-pro[bot] commented 2 months ago

Persistent review updated to latest commit https://github.com/trendmicro-frontend/tonic-ui/commit/df9b486d5a2953ab34ac2b75296d6bf0c8cfaf4f

cheton commented 2 months ago

/review --pr_reviewer.inline_code_comments=true --pr_reviewer.require_score_review=true

codiumai-pr-agent-pro[bot] commented 2 months ago

Persistent review updated to latest commit https://github.com/trendmicro-frontend/tonic-ui/commit/4d72bd90b42784328363276ace6126493024df09