diff --git a/.github/workflows/code_coverage_disable_check.py b/.github/workflows/code_coverage_disable_check.py new file mode 100644 index 0000000000..4a4fd756d0 --- /dev/null +++ b/.github/workflows/code_coverage_disable_check.py @@ -0,0 +1,169 @@ +"""Code Coverage Disable Checker Script. + +Methodology: + + Recursively analyzes TypeScript files in the specified directories or + checks specific files + to ensure they do not contain code coverage disable statements. + + This script enforces proper code coverage practices in the project. + +NOTE: + This script complies with our python3 coding and documentation standards. + It complies with: + + 1) Pylint + 2) Pydocstyle + 3) Pycodestyle + 4) Flake8 + 5) Python Black + +""" + +import os +import re +import argparse +import sys + + +def has_code_coverage_disable(file_path): + """ + Check if a TypeScript file contains code coverage disable statements. + + Args: + file_path (str): Path to the TypeScript file. + + Returns: + bool: True if code coverage disable statement is found, False + otherwise. + """ + code_coverage_disable_pattern = re.compile( + r"""//?\s*istanbul\s+ignore(?:\s+(?:next|-line))?[^\n]*| + /\*\s*istanbul\s+ignore\s+(?:next|-line)\s*\*/""", + re.IGNORECASE, + ) + try: + with open(file_path, "r", encoding="utf-8") as file: + content = file.read() + return bool(code_coverage_disable_pattern.search(content)) + except FileNotFoundError: + print(f"File not found: {file_path}") + return False + except PermissionError: + print(f"Permission denied: {file_path}") + return False + except (IOError, OSError) as e: + print(f"Error reading file {file_path}: {e}") + return False + + +def check_code_coverage(files_or_dirs): + """ + Check TypeScript files for code coverage disable statements. + + Args: + files_or_dirs (list): List of files or directories to check. + + Returns: + bool: True if code coverage disable statement is found, False + otherwise. + """ + code_coverage_found = False + + for item in files_or_dirs: + if os.path.isdir(item): + # If it's a directory, recursively walk through the files in it + for root, _, files in os.walk(item): + if "node_modules" in root: + continue + for file_name in files: + if ( + file_name.endswith(".tsx") + or file_name.endswith(".ts") + and not file_name.endswith(".test.tsx") + and not file_name.endswith(".test.ts") + and not file_name.endswith(".spec.tsx") + and not file_name.endswith(".spec.ts") + ): + file_path = os.path.join(root, file_name) + if has_code_coverage_disable(file_path): + print( + f"""File {file_path} contains code coverage + disable statement.""" + ) + code_coverage_found = True + elif os.path.isfile(item): + # If it's a file, check it directly + if ( + item.endswith(".tsx") + or item.endswith(".ts") + and not item.endswith(".test.tsx") + and not item.endswith(".test.ts") + and not item.endswith(".spec.tsx") + and not item.endswith(".spec.ts") + ): + if has_code_coverage_disable(item): + print( + f"""File {item} contains code coverage disable + statement.""" + ) + code_coverage_found = True + + return code_coverage_found + + +def arg_parser_resolver(): + """Resolve the CLI arguments provided by the user. + + Returns: + result: Parsed argument object + """ + parser = argparse.ArgumentParser() + parser.add_argument( + "--directory", + type=str, + nargs="+", + default=[os.getcwd()], + help="""One or more directories to check for code coverage disable + statements (default: current directory).""", + ) + parser.add_argument( + "--files", + type=str, + nargs="+", + default=[], + help="""One or more files to check directly for code coverage disable + statements (default: check directories).""", + ) + return parser.parse_args() + + +def main(): + """ + Execute the script's main functionality. + + This function serves as the entry point for the script. It performs + the following tasks: + 1. Validates and retrieves the files or directories to check from + command line arguments. + 2. Checks files or directories for code coverage disable statements. + 3. Provides informative messages based on the analysis. + 4. Exits with an error if code coverage disable statements are found. + + Raises: + SystemExit: If an error occurs during execution. + """ + args = arg_parser_resolver() + files_or_dirs = args.files if args.files else args.directory + # Check code coverage in the specified files or directories + code_coverage_found = check_code_coverage(files_or_dirs) + + if code_coverage_found: + print("Code coverage disable check failed. Exiting with error.") + sys.exit(1) + + print("Code coverage disable check completed successfully.") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/eslint_disable_check.py b/.github/workflows/eslint_disable_check.py index 201b4462b8..ee17fcb220 100644 --- a/.github/workflows/eslint_disable_check.py +++ b/.github/workflows/eslint_disable_check.py @@ -4,13 +4,13 @@ Methodology: - Recursively analyzes TypeScript files in the 'src' directory and its subdirectories - as well as 'setup.ts' files to ensure they do not contain eslint-disable statements. + Recursively analyzes TypeScript files in the specified directory + or checks specific files directly to ensure they do not contain + eslint-disable statements. This script enforces code quality practices in the project. NOTE: - This script complies with our python3 coding and documentation standards. It complies with: @@ -18,14 +18,15 @@ 2) Pydocstyle 3) Pycodestyle 4) Flake8 + 5) Python Black """ - import os import re import argparse import sys + def has_eslint_disable(file_path): """ Check if a TypeScript file contains eslint-disable statements. @@ -36,43 +37,65 @@ def has_eslint_disable(file_path): Returns: bool: True if eslint-disable statement is found, False otherwise. """ - eslint_disable_pattern = re.compile(r'//\s*eslint-disable(?:-next-line|-line)?', re.IGNORECASE) - + eslint_disable_pattern = re.compile( + r"""\/\/\s*eslint-disable(?:-next-line + |-line)?[^\n]*|\/\*\s*eslint-disable[^\*]*\*\/""", + re.IGNORECASE, + ) + try: - with open(file_path, 'r', encoding='utf-8') as file: + with open(file_path, "r", encoding="utf-8") as file: content = file.read() return bool(eslint_disable_pattern.search(content)) - except Exception as e: + except FileNotFoundError: + print(f"File not found: {file_path}") + return False + except PermissionError: + print(f"Permission denied: {file_path}") + return False + except (IOError, OSError) as e: print(f"Error reading file {file_path}: {e}") return False -def check_eslint(directory): + +def check_eslint(files_or_directories): """ - Recursively check TypeScript files for eslint-disable statements in the 'src' directory. + Check TypeScript files for eslint-disable statements. Args: - directory (str): Path to the directory. + files_or_directories (list): List of files or directories to check. Returns: bool: True if eslint-disable statement is found, False otherwise. """ eslint_found = False - for root, dirs, files in os.walk(os.path.join(directory, 'src')): - for file_name in files: - if file_name.endswith('.tsx') and not file_name.endswith('.test.tsx'): - file_path = os.path.join(root, file_name) - if has_eslint_disable(file_path): - print(f'File {file_path} contains eslint-disable statement.') + for item in files_or_directories: + if os.path.isfile(item): + # If it's a file, directly check it + if item.endswith(".ts") or item.endswith(".tsx"): + if has_eslint_disable(item): + print(f"File {item} contains eslint-disable statement.") eslint_found = True - - setup_path = os.path.join(directory, 'setup.ts') - if os.path.exists(setup_path) and has_eslint_disable(setup_path): - print(f'Setup file {setup_path} contains eslint-disable statement.') - eslint_found = True + elif os.path.isdir(item): + # If it's a directory, walk through it and check all + # .ts and .tsx files + for root, _, files in os.walk(item): + if "node_modules" in root: + continue + for file_name in files: + if file_name.endswith(".ts") or file_name.endswith(".tsx"): + file_path = os.path.join(root, file_name) + if has_eslint_disable(file_path): + print( + f"""File {file_path} contains eslint-disable + statement.""" + ) + eslint_found = True return eslint_found + def arg_parser_resolver(): """Resolve the CLI arguments provided by the user. @@ -80,21 +103,32 @@ def arg_parser_resolver(): result: Parsed argument object """ parser = argparse.ArgumentParser() + parser.add_argument( + "--files", + type=str, + nargs="+", + default=[], + help="""List of files to check for eslint disable + statements (default: None).""", + ) parser.add_argument( "--directory", type=str, - default=os.getcwd(), - help="Path to the directory to check (default: current directory)" + nargs="+", + default=[os.getcwd()], + help="""One or more directories to check for eslint disable + statements (default: current directory).""", ) return parser.parse_args() + def main(): """ Execute the script's main functionality. This function serves as the entry point for the script. It performs the following tasks: - 1. Validates and retrieves the directory to check from + 1. Validates and retrieves the files and directories to check from command line arguments. 2. Recursively checks TypeScript files for eslint-disable statements. 3. Provides informative messages based on the analysis. @@ -105,12 +139,10 @@ def main(): """ args = arg_parser_resolver() - if not os.path.exists(args.directory): - print(f"Error: The specified directory '{args.directory}' does not exist.") - sys.exit(1) - - # Check eslint in the specified directory - eslint_found = check_eslint(args.directory) + # Determine whether to check files or directories based on the arguments + files_or_directories = args.files if args.files else args.directory + # Check eslint in the specified files or directories + eslint_found = check_eslint(files_or_directories) if eslint_found: print("ESLint-disable check failed. Exiting with error.") @@ -118,5 +150,6 @@ def main(): print("ESLint-disable check completed successfully.") + if __name__ == "__main__": main() diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 95572a6d81..4708e39869 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -59,7 +59,7 @@ jobs: if: steps.changed-files.outputs.only_changed != 'true' env: CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} - run: npx eslint ${CHANGED_FILES} && python .github/workflows/eslint_disable_check.py + run: npx eslint ${CHANGED_FILES} - name: Check for TSDoc comments run: npm run check-tsdoc # Run the TSDoc check script @@ -181,6 +181,30 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v45 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Run Python script + run: | + python .github/workflows/eslint_disable_check.py --files ${{ steps.changed-files.outputs.all_changed_files }} + + Check-Code-Coverage-Disable: + name: Check for code coverage disable + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v45 + - name: Set up Python uses: actions/setup-python@v5 with: @@ -188,12 +212,12 @@ jobs: - name: Run Python script run: | - python .github/workflows/eslint_disable_check.py + python .github/workflows/code_coverage_disable_check.py --files ${{ steps.changed-files.outputs.all_changed_files }} Test-Application: name: Test Application runs-on: ubuntu-latest - needs: [Code-Quality-Checks, Check-ESlint-Disable] + needs: [Code-Quality-Checks, Check-ESlint-Disable,Check-Code-Coverage-Disable] steps: - name: Checkout the Repository uses: actions/checkout@v4 diff --git a/src/setupTests.ts b/src/setupTests.ts index d204b3ddc9..19c34e040a 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -3,7 +3,6 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; - global.fetch = jest.fn(); import { format } from 'util';