From 556a848f632a9b72ee8875eb0f011c2d13fca559 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Fri, 24 Mar 2023 14:27:19 +0100 Subject: [PATCH] [NO JIRA] Improve local eslint rules - Also trigger on conditional DeferredSpinner when using ternary conditions. - Turn all "use enum" rules into standalone rule files, and move the reusable logic to a lib/ folder instead. - Run tests on CI --- ...ional-rendering-of-deferredspinner-test.js | 16 ++ server/sonar-web/eslint-local-rules/index.js | 147 +---------------- .../eslint-local-rules/jest.config.js | 13 ++ .../{ => lib}/__tests__/use-enum-test.js | 28 ++-- .../eslint-local-rules/lib/use-enum.js | 41 +++++ ...onditional-rendering-of-deferredspinner.js | 31 +++- ...enum.js => use-componentqualifier-enum.js} | 23 +-- .../eslint-local-rules/use-metrickey-enum.js | 152 ++++++++++++++++++ .../eslint-local-rules/use-metrictype-enum.js | 25 +++ .../eslint-local-rules/use-visibility-enum.js | 21 +++ server/sonar-web/package.json | 6 +- 11 files changed, 318 insertions(+), 185 deletions(-) create mode 100644 server/sonar-web/eslint-local-rules/jest.config.js rename server/sonar-web/eslint-local-rules/{ => lib}/__tests__/use-enum-test.js (63%) create mode 100644 server/sonar-web/eslint-local-rules/lib/use-enum.js rename server/sonar-web/eslint-local-rules/{use-enum.js => use-componentqualifier-enum.js} (64%) create mode 100644 server/sonar-web/eslint-local-rules/use-metrickey-enum.js create mode 100644 server/sonar-web/eslint-local-rules/use-metrictype-enum.js create mode 100644 server/sonar-web/eslint-local-rules/use-visibility-enum.js diff --git a/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js b/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js index 960938a3977..9d9f9c695ca 100644 --- a/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js +++ b/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js @@ -48,6 +48,22 @@ ruleTester.run( return <> {loading && } +}`, + errors: [{ messageId: 'noConditionalRenderingOfDeferredSpinner' }], + }, + { + code: `function MyComponent({ loading }) { + return <> + {loading ? :
} + +}`, + errors: [{ messageId: 'noConditionalRenderingOfDeferredSpinner' }], + }, + { + code: `function MyCompontent({ loaded }) { + return <> + {loaded ?
: } + }`, errors: [{ messageId: 'noConditionalRenderingOfDeferredSpinner' }], }, diff --git a/server/sonar-web/eslint-local-rules/index.js b/server/sonar-web/eslint-local-rules/index.js index 3bd8c8290cc..8f7f5ccf70b 100644 --- a/server/sonar-web/eslint-local-rules/index.js +++ b/server/sonar-web/eslint-local-rules/index.js @@ -21,147 +21,8 @@ module.exports = { 'use-jest-mocked': require('./use-jest-mocked'), 'convert-class-to-function-component': require('./convert-class-to-function-component'), 'no-conditional-rendering-of-deferredspinner': require('./no-conditional-rendering-of-deferredspinner'), - 'use-visibility-enum': require('./use-enum')(['public', 'private'], 'Visibility'), - 'use-componentqualifier-enum': require('./use-enum')( - ['APP', 'DIR', 'DEV', 'FIL', 'VW', 'TRK', 'SVW', 'UTS'], - 'ComponentQualifier', - 'representing qualifiers' - ), - 'use-metrickey-enum': require('./use-enum')( - [ - 'alert_status', - 'blocker_violations', - 'branch_coverage', - 'bugs', - 'burned_budget', - 'business_value', - 'class_complexity', - 'classes', - 'code_smells', - 'cognitive_complexity', - 'comment_lines', - 'comment_lines_data', - 'comment_lines_density', - 'complexity', - 'complexity_in_classes', - 'complexity_in_functions', - 'conditions_to_cover', - 'confirmed_issues', - 'coverage', - 'critical_violations', - 'development_cost', - 'directories', - 'duplicated_blocks', - 'duplicated_files', - 'duplicated_lines', - 'duplicated_lines_density', - 'duplications_data', - 'effort_to_reach_maintainability_rating_a', - 'executable_lines_data', - 'false_positive_issues', - 'file_complexity', - 'file_complexity_distribution', - 'filename_size', - 'filename_size_rating', - 'files', - 'function_complexity', - 'function_complexity_distribution', - 'functions', - 'generated_lines', - 'generated_ncloc', - 'info_violations', - 'last_change_on_maintainability_rating', - 'last_change_on_releasability_rating', - 'last_change_on_reliability_rating', - 'last_change_on_security_rating', - 'last_change_on_security_review_rating', - 'last_commit_date', - 'leak_projects', - 'line_coverage', - 'lines', - 'lines_to_cover', - 'maintainability_rating_effort', - 'major_violations', - 'minor_violations', - 'ncloc', - 'ncloc_data', - 'ncloc_language_distribution', - 'new_blocker_violations', - 'new_branch_coverage', - 'new_bugs', - 'new_code_smells', - 'new_conditions_to_cover', - 'new_coverage', - 'new_critical_violations', - 'new_development_cost', - 'new_duplicated_blocks', - 'new_duplicated_lines', - 'new_duplicated_lines_density', - 'new_info_violations', - 'new_line_coverage', - 'new_lines', - 'new_lines_to_cover', - 'new_maintainability_rating', - 'new_major_violations', - 'new_minor_violations', - 'new_reliability_rating', - 'new_reliability_remediation_effort', - 'new_security_hotspots', - 'new_security_hotspots_reviewed', - 'new_security_rating', - 'new_security_remediation_effort', - 'new_security_review_rating', - 'new_sqale_debt_ratio', - 'new_technical_debt', - 'new_uncovered_conditions', - 'new_uncovered_lines', - 'new_violations', - 'new_vulnerabilities', - 'open_issues', - 'projects', - 'public_api', - 'public_documented_api_density', - 'public_undocumented_api', - 'quality_gate_details', - 'quality_profiles', - 'releasability_effort', - 'releasability_rating', - 'reliability_rating', - 'reliability_rating_effort', - 'reliability_remediation_effort', - 'reopened_issues', - 'security_hotspots', - 'security_hotspots_reviewed', - 'security_rating', - 'security_rating_effort', - 'security_remediation_effort', - 'security_review_rating', - 'security_review_rating_effort', - 'skipped_tests', - 'sonarjava_feedback', - 'sqale_debt_ratio', - 'sqale_index', - 'sqale_rating', - 'statements', - 'team_at_sonarsource', - 'team_size', - 'test_errors', - 'test_execution_time', - 'test_failures', - 'test_success_density', - 'tests', - 'uncovered_conditions', - 'uncovered_lines', - 'violations', - 'vulnerabilities', - 'wont_fix_issues', - ], - 'MetricKey', - 'representing metric keys' - ), - 'use-metrictype-enum': require('./use-enum')( - ['RATING', 'PERCENT', 'INT', 'LEVEL', 'SHORT_INT', 'SHORT_WORK_DUR', 'DATA'], - 'MetricType', - 'representing metric types' - ), + 'use-visibility-enum': require('./use-visibility-enum'), + 'use-componentqualifier-enum': require('./use-componentqualifier-enum'), + 'use-metrickey-enum': require('./use-metrickey-enum'), + 'use-metrictype-enum': require('./use-metrictype-enum'), }; diff --git a/server/sonar-web/eslint-local-rules/jest.config.js b/server/sonar-web/eslint-local-rules/jest.config.js new file mode 100644 index 00000000000..e03741f98ad --- /dev/null +++ b/server/sonar-web/eslint-local-rules/jest.config.js @@ -0,0 +1,13 @@ +module.exports = { + testRegex: '(/__tests__/.*|\\-test)\\.(t|j)s$', + transform: { + '^.+\\.(t|j)s$': [ + '@swc/jest', + { + jsc: { + target: 'es2018', + }, + }, + ], + }, +}; diff --git a/server/sonar-web/eslint-local-rules/__tests__/use-enum-test.js b/server/sonar-web/eslint-local-rules/lib/__tests__/use-enum-test.js similarity index 63% rename from server/sonar-web/eslint-local-rules/__tests__/use-enum-test.js rename to server/sonar-web/eslint-local-rules/lib/__tests__/use-enum-test.js index 153460959ab..494a19209a5 100644 --- a/server/sonar-web/eslint-local-rules/__tests__/use-enum-test.js +++ b/server/sonar-web/eslint-local-rules/lib/__tests__/use-enum-test.js @@ -18,39 +18,37 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ const { RuleTester } = require('eslint'); -const useComponentQualifierEnum = require('../use-enum')( - ['APP', 'DIR', 'DEV', 'FIL', 'VW', 'TRK', 'SVW', 'UTS'], - 'ComponentQualifier' -); +const { useEnum } = require('../use-enum'); +const useSomeEnum = useEnum(['Val1', 'Val2', 'Val3'], 'SomeEnum'); const ruleTester = new RuleTester({ parser: require.resolve('@typescript-eslint/parser'), }); -ruleTester.run('use-componentqualifier-enum', useComponentQualifierEnum, { +ruleTester.run('use-some-enum', useSomeEnum, { valid: [ { - code: '{ qualifier: ComponentQualifier.Project };', + code: '{ qualifier: SomeEnum.FirstValue };', }, { - code: 'varName === ComponentQualifier.File', + code: 'varName === SomeEnum.SecondValue', }, { - code: `enum ComponentQualifier { - Portfolio = 'VW', - Project = 'TRK', - SubPortfolio = 'SVW', + code: `enum SomeEnum { + FirstValue = 'Val1', + SecondValue = 'Val2', + ThirdValue = 'Val3', }`, }, ], invalid: [ { - code: '{ qualifier: "TRK" };', - errors: [{ messageId: 'useComponentQualifierEnum' }], + code: '{ value: "Val1" };', + errors: [{ messageId: 'useSomeEnumEnum' }], }, { - code: 'varName === "FIL"', - errors: [{ messageId: 'useComponentQualifierEnum' }], + code: "varName === 'Val2'", + errors: [{ messageId: 'useSomeEnumEnum' }], }, ], }); diff --git a/server/sonar-web/eslint-local-rules/lib/use-enum.js b/server/sonar-web/eslint-local-rules/lib/use-enum.js new file mode 100644 index 00000000000..78b2463aacd --- /dev/null +++ b/server/sonar-web/eslint-local-rules/lib/use-enum.js @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +module.exports = { + useEnum(values, name, help) { + return { + meta: { + messages: { + [`use${name}Enum`]: `Hard-coded strings ${ + help ? help + ' ' : '' + }are not allowed; use the ${name} enum instead`, + }, + }, + create(context) { + return { + Literal(node) { + if (node.parent.type !== 'TSEnumMember' && values.includes(node.value)) { + context.report({ node, messageId: `use${name}Enum` }); + } + }, + }; + }, + }; + }, +}; diff --git a/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js b/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js index 3db12928b89..03d16a26652 100644 --- a/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js +++ b/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js @@ -27,14 +27,33 @@ module.exports = { create(context) { return { JSXExpressionContainer(node) { - if ( - node.expression.type === 'LogicalExpression' && - node.expression.right.type === 'JSXElement' && - node.expression.right.openingElement.name.name === 'DeferredSpinner' - ) { - context.report({ node, messageId: 'noConditionalRenderingOfDeferredSpinner' }); + switch (node.expression.type) { + case 'LogicalExpression': + const { right } = node.expression; + if (isDeferredSpinnerComponent(right)) { + context.report({ node, messageId: 'noConditionalRenderingOfDeferredSpinner' }); + } + break; + + case 'ConditionalExpression': + const { consequent, alternate } = node.expression; + if (isDeferredSpinnerComponent(consequent)) { + context.report({ node, messageId: 'noConditionalRenderingOfDeferredSpinner' }); + } + if (isDeferredSpinnerComponent(alternate)) { + context.report({ node, messageId: 'noConditionalRenderingOfDeferredSpinner' }); + } + break; } }, }; }, }; + +function isDeferredSpinnerComponent(element) { + return ( + element.type === 'JSXElement' && + element.openingElement && + element.openingElement.name.name === 'DeferredSpinner' + ); +} diff --git a/server/sonar-web/eslint-local-rules/use-enum.js b/server/sonar-web/eslint-local-rules/use-componentqualifier-enum.js similarity index 64% rename from server/sonar-web/eslint-local-rules/use-enum.js rename to server/sonar-web/eslint-local-rules/use-componentqualifier-enum.js index 50a840b97c5..085b5c8d142 100644 --- a/server/sonar-web/eslint-local-rules/use-enum.js +++ b/server/sonar-web/eslint-local-rules/use-componentqualifier-enum.js @@ -17,21 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -module.exports = (values, name, help) => ({ - meta: { - messages: { - [`use${name}Enum`]: `Hard-coded strings ${ - help ? help + ' ' : '' - }are not allowed; use the ${name} enum instead`, - }, - }, - create(context) { - return { - Literal(node) { - if (node.parent.type !== 'TSEnumMember' && values.includes(node.value)) { - context.report({ node, messageId: `use${name}Enum` }); - } - }, - }; - }, -}); +const { useEnum } = require('./lib/use-enum'); +module.exports = useEnum( + ['APP', 'DIR', 'DEV', 'FIL', 'VW', 'TRK', 'SVW', 'UTS'], + 'ComponentQualifier' +); diff --git a/server/sonar-web/eslint-local-rules/use-metrickey-enum.js b/server/sonar-web/eslint-local-rules/use-metrickey-enum.js new file mode 100644 index 00000000000..c5b979f2d23 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/use-metrickey-enum.js @@ -0,0 +1,152 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +const { useEnum } = require('./lib/use-enum'); +module.exports = useEnum( + [ + 'alert_status', + 'blocker_violations', + 'branch_coverage', + 'bugs', + 'burned_budget', + 'business_value', + 'class_complexity', + 'classes', + 'code_smells', + 'cognitive_complexity', + 'comment_lines', + 'comment_lines_data', + 'comment_lines_density', + 'complexity', + 'complexity_in_classes', + 'complexity_in_functions', + 'conditions_to_cover', + 'confirmed_issues', + 'coverage', + 'critical_violations', + 'development_cost', + 'directories', + 'duplicated_blocks', + 'duplicated_files', + 'duplicated_lines', + 'duplicated_lines_density', + 'duplications_data', + 'effort_to_reach_maintainability_rating_a', + 'executable_lines_data', + 'false_positive_issues', + 'file_complexity', + 'file_complexity_distribution', + 'filename_size', + 'filename_size_rating', + 'files', + 'function_complexity', + 'function_complexity_distribution', + 'functions', + 'generated_lines', + 'generated_ncloc', + 'info_violations', + 'last_change_on_maintainability_rating', + 'last_change_on_releasability_rating', + 'last_change_on_reliability_rating', + 'last_change_on_security_rating', + 'last_change_on_security_review_rating', + 'last_commit_date', + 'leak_projects', + 'line_coverage', + 'lines', + 'lines_to_cover', + 'maintainability_rating_effort', + 'major_violations', + 'minor_violations', + 'ncloc', + 'ncloc_data', + 'ncloc_language_distribution', + 'new_blocker_violations', + 'new_branch_coverage', + 'new_bugs', + 'new_code_smells', + 'new_conditions_to_cover', + 'new_coverage', + 'new_critical_violations', + 'new_development_cost', + 'new_duplicated_blocks', + 'new_duplicated_lines', + 'new_duplicated_lines_density', + 'new_info_violations', + 'new_line_coverage', + 'new_lines', + 'new_lines_to_cover', + 'new_maintainability_rating', + 'new_major_violations', + 'new_minor_violations', + 'new_reliability_rating', + 'new_reliability_remediation_effort', + 'new_security_hotspots', + 'new_security_hotspots_reviewed', + 'new_security_rating', + 'new_security_remediation_effort', + 'new_security_review_rating', + 'new_sqale_debt_ratio', + 'new_technical_debt', + 'new_uncovered_conditions', + 'new_uncovered_lines', + 'new_violations', + 'new_vulnerabilities', + 'open_issues', + 'projects', + 'public_api', + 'public_documented_api_density', + 'public_undocumented_api', + 'quality_gate_details', + 'quality_profiles', + 'releasability_effort', + 'releasability_rating', + 'reliability_rating', + 'reliability_rating_effort', + 'reliability_remediation_effort', + 'reopened_issues', + 'security_hotspots', + 'security_hotspots_reviewed', + 'security_rating', + 'security_rating_effort', + 'security_remediation_effort', + 'security_review_rating', + 'security_review_rating_effort', + 'skipped_tests', + 'sonarjava_feedback', + 'sqale_debt_ratio', + 'sqale_index', + 'sqale_rating', + 'statements', + 'team_at_sonarsource', + 'team_size', + 'test_errors', + 'test_execution_time', + 'test_failures', + 'test_success_density', + 'tests', + 'uncovered_conditions', + 'uncovered_lines', + 'violations', + 'vulnerabilities', + 'wont_fix_issues', + ], + 'MetricKey', + 'representing metric keys' +); diff --git a/server/sonar-web/eslint-local-rules/use-metrictype-enum.js b/server/sonar-web/eslint-local-rules/use-metrictype-enum.js new file mode 100644 index 00000000000..d20617f2cc8 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/use-metrictype-enum.js @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +const { useEnum } = require('./lib/use-enum'); +module.exports = useEnum( + ['RATING', 'PERCENT', 'INT', 'LEVEL', 'SHORT_INT', 'SHORT_WORK_DUR', 'DATA'], + 'MetricType', + 'representing metric types' +); diff --git a/server/sonar-web/eslint-local-rules/use-visibility-enum.js b/server/sonar-web/eslint-local-rules/use-visibility-enum.js new file mode 100644 index 00000000000..acdb5a4ce28 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/use-visibility-enum.js @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +const { useEnum } = require('./lib/use-enum'); +module.exports = useEnum(['public', 'private'], 'Visibility'); diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index ce080080dd2..95f41998fb8 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -116,7 +116,7 @@ "build": "node scripts/build.js", "build-release": "yarn install --immutable && node scripts/build.js release", "test": "jest", - "test-eslint-local-rules": "jest eslint-local-rules", + "test-eslint-local-rules": "jest -c eslint-local-rules/jest.config.js", "format": "prettier --write --list-different \"src/main/js/**/*.{js,ts,tsx,css}\"", "format-check": "prettier --list-different \"src/main/js/**/*.{js,ts,tsx,css}\"", "lint": "eslint --ext js,ts,tsx --quiet src/main/js", @@ -125,7 +125,7 @@ "ts-check": "tsc --noEmit", "validate": "yarn dep-check && yarn lint && yarn ts-check && yarn format-check && yarn test", "validate-ci": "yarn install --immutable && yarn dep-check && yarn test --coverage --maxWorkers=4 --ci", - "check-ci": "yarn install --immutable && yarn ts-check && yarn format-check", + "check-ci": "yarn install --immutable && yarn ts-check && yarn format-check && yarn test-eslint-local-rules", "update-cwes": "node scripts/update-cwes.js", "dep-check": "node scripts/validate-package-json.js" }, @@ -136,4 +136,4 @@ "path": "path-browserify" }, "packageManager": "yarn@3.5.0" -} +} \ No newline at end of file -- 2.39.5