From 132554f7ff7c9ee486b2c872b4e4728be1efa4e6 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Fri, 17 Feb 2023 14:44:09 +0100 Subject: [PATCH] [NO JIRA] Bootstrap custom ESLint rules --- server/sonar-web/.eslintrc | 12 +- server/sonar-web/design-system/.eslintrc | 11 +- server/sonar-web/design-system/build.gradle | 1 + .../design-system/eslint-local-rules/index.js | 20 +++ server/sonar-web/design-system/package.json | 1 + ...onvert-class-to-function-component-test.js | 61 +++++++ ...ional-rendering-of-deferredspinner-test.js | 56 ++++++ .../__tests__/use-enum-test.js | 56 ++++++ .../__tests__/use-jest-mocked-test.js | 39 ++++ .../convert-class-to-function-component.js | 43 +++++ server/sonar-web/eslint-local-rules/index.js | 167 ++++++++++++++++++ ...onditional-rendering-of-deferredspinner.js | 40 +++++ .../sonar-web/eslint-local-rules/use-enum.js | 37 ++++ .../eslint-local-rules/use-jest-mocked.js | 38 ++++ server/sonar-web/package.json | 2 + server/sonar-web/yarn.lock | 9 + 16 files changed, 591 insertions(+), 2 deletions(-) create mode 100644 server/sonar-web/design-system/eslint-local-rules/index.js create mode 100644 server/sonar-web/eslint-local-rules/__tests__/convert-class-to-function-component-test.js create mode 100644 server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js create mode 100644 server/sonar-web/eslint-local-rules/__tests__/use-enum-test.js create mode 100644 server/sonar-web/eslint-local-rules/__tests__/use-jest-mocked-test.js create mode 100644 server/sonar-web/eslint-local-rules/convert-class-to-function-component.js create mode 100644 server/sonar-web/eslint-local-rules/index.js create mode 100644 server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js create mode 100644 server/sonar-web/eslint-local-rules/use-enum.js create mode 100644 server/sonar-web/eslint-local-rules/use-jest-mocked.js diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc index 1e45ca41a4f..0c445a4a950 100644 --- a/server/sonar-web/.eslintrc +++ b/server/sonar-web/.eslintrc @@ -1,9 +1,19 @@ { "extends": ["sonarqube", "./.eslintrc-typescript"], + "plugins": ["eslint-plugin-local-rules"], + "ignorePatterns": ["eslint-local-rules/**/*"], "rules": { "camelcase": "off", "promise/no-return-wrap": "warn", "react/jsx-curly-brace-presence": "warn", - "testing-library/render-result-naming-convention": "off" + "testing-library/render-result-naming-convention": "off", + /* Local rules, defined in ./eslint-local-rules/ */ + "local-rules/use-componentqualifier-enum": "warn", + "local-rules/use-metrickey-enum": "warn", + "local-rules/use-metrictype-enum": "warn", + "local-rules/use-visibility-enum": "warn", + "local-rules/convert-class-to-function-component": "warn", + "local-rules/no-conditional-rendering-of-deferredspinner": "warn", + "local-rules/use-jest-mocked": "warn" } } diff --git a/server/sonar-web/design-system/.eslintrc b/server/sonar-web/design-system/.eslintrc index 181af256e97..a7266dd7eb6 100644 --- a/server/sonar-web/design-system/.eslintrc +++ b/server/sonar-web/design-system/.eslintrc @@ -1,6 +1,6 @@ { "extends": ["sonarqube", "../.eslintrc-typescript"], - "plugins": ["header", "typescript-sort-keys"], + "plugins": ["header", "typescript-sort-keys", "eslint-plugin-local-rules"], "rules": { // Custom SonarCloud config that differs from eslint-config-sonarqube "camelcase": "off", @@ -14,6 +14,15 @@ "no-implicit-coercion": [2, { "boolean": true, "number": true, "string": true }], "jest/no-large-snapshots": ["warn", { "maxSize": 200 }], + // Local rules + "local-rules/use-componentqualifier-enum": "warn", + "local-rules/use-metrickey-enum": "warn", + "local-rules/use-metrictype-enum": "warn", + "local-rules/use-visibility-enum": "warn", + "local-rules/convert-class-to-function-component": "warn", + "local-rules/no-conditional-rendering-of-deferredspinner": "warn", + "local-rules/use-jest-mocked": "warn", + // New rules added after updating eslint packages to more recent versions than eslint-config-sonarqube "jest/prefer-mock-promise-shorthand": "error", "header/header": [ diff --git a/server/sonar-web/design-system/build.gradle b/server/sonar-web/design-system/build.gradle index 05e41b92dfa..5af4116261b 100644 --- a/server/sonar-web/design-system/build.gradle +++ b/server/sonar-web/design-system/build.gradle @@ -5,6 +5,7 @@ sonar { property "sonar.exclusions", "src/**/__tests__/**,src/types/**,src/@types/**,src/helpers/testUtils.tsx" property "sonar.tests", "src" property "sonar.test.inclusions", "src/**/__tests__/**" + property "sonar.eslint.reportPaths", "eslint-report/eslint-report.json" property "sonar.javascript.lcov.reportPaths", "./coverage/lcov.info" } } diff --git a/server/sonar-web/design-system/eslint-local-rules/index.js b/server/sonar-web/design-system/eslint-local-rules/index.js new file mode 100644 index 00000000000..b64b1534079 --- /dev/null +++ b/server/sonar-web/design-system/eslint-local-rules/index.js @@ -0,0 +1,20 @@ +/* + * 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 = require('../eslint-local-rules'); diff --git a/server/sonar-web/design-system/package.json b/server/sonar-web/design-system/package.json index bbdc8c8535d..d49f84e5e95 100644 --- a/server/sonar-web/design-system/package.json +++ b/server/sonar-web/design-system/package.json @@ -29,6 +29,7 @@ "autoprefixer": "10.4.13", "eslint": "8.32.0", "eslint-plugin-header": "3.1.1", + "eslint-plugin-local-rules": "1.3.2", "eslint-plugin-typescript-sort-keys": "2.1.0", "history": "5.3.0", "jest": "29.3.1", diff --git a/server/sonar-web/eslint-local-rules/__tests__/convert-class-to-function-component-test.js b/server/sonar-web/eslint-local-rules/__tests__/convert-class-to-function-component-test.js new file mode 100644 index 00000000000..19f4e2a4834 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/__tests__/convert-class-to-function-component-test.js @@ -0,0 +1,61 @@ +/* + * 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 { RuleTester } = require('eslint'); +const convertClassToFunctionComponent = require('../convert-class-to-function-component'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 2021, + sourceType: 'module', + }, +}); + +ruleTester.run('convert-class-to-function-component', convertClassToFunctionComponent, { + valid: [ + { + code: `class ConditionsTable extends React.PureComponent { + componentDidMount() {} + render() {} +}`, + }, + { + code: `class ConditionsTable extends React.PureComponent { + handleClick = () => {} + render() {} +}`, + }, + ], + invalid: [ + { + code: `class ConditionsTable extends React.PureComponent { + render() {} +}`, + errors: [{ messageId: 'convertClassToFunctionComponent' }], + }, + { + code: `class ConditionsTable extends React.PureComponent { + ref = null; + render() {} +}`, + errors: [{ messageId: 'convertClassToFunctionComponent' }], + }, + ], +}); 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 new file mode 100644 index 00000000000..960938a3977 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js @@ -0,0 +1,56 @@ +/* + * 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 { RuleTester } = require('eslint'); +const noConditionalRenderingOfDeferredSpinner = require('../no-conditional-rendering-of-deferredspinner'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, +}); + +ruleTester.run( + 'no-conditional-rendering-of-deferredspinner', + noConditionalRenderingOfDeferredSpinner, + { + valid: [ + { + code: `function MyCompontent({ loading }) { + return <> + + +}`, + }, + ], + invalid: [ + { + code: `function MyCompontent({ loading }) { + return <> + {loading && } + +}`, + errors: [{ messageId: 'noConditionalRenderingOfDeferredSpinner' }], + }, + ], + } +); diff --git a/server/sonar-web/eslint-local-rules/__tests__/use-enum-test.js b/server/sonar-web/eslint-local-rules/__tests__/use-enum-test.js new file mode 100644 index 00000000000..153460959ab --- /dev/null +++ b/server/sonar-web/eslint-local-rules/__tests__/use-enum-test.js @@ -0,0 +1,56 @@ +/* + * 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 { RuleTester } = require('eslint'); +const useComponentQualifierEnum = require('../use-enum')( + ['APP', 'DIR', 'DEV', 'FIL', 'VW', 'TRK', 'SVW', 'UTS'], + 'ComponentQualifier' +); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), +}); + +ruleTester.run('use-componentqualifier-enum', useComponentQualifierEnum, { + valid: [ + { + code: '{ qualifier: ComponentQualifier.Project };', + }, + { + code: 'varName === ComponentQualifier.File', + }, + { + code: `enum ComponentQualifier { + Portfolio = 'VW', + Project = 'TRK', + SubPortfolio = 'SVW', +}`, + }, + ], + invalid: [ + { + code: '{ qualifier: "TRK" };', + errors: [{ messageId: 'useComponentQualifierEnum' }], + }, + { + code: 'varName === "FIL"', + errors: [{ messageId: 'useComponentQualifierEnum' }], + }, + ], +}); diff --git a/server/sonar-web/eslint-local-rules/__tests__/use-jest-mocked-test.js b/server/sonar-web/eslint-local-rules/__tests__/use-jest-mocked-test.js new file mode 100644 index 00000000000..89db05def52 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/__tests__/use-jest-mocked-test.js @@ -0,0 +1,39 @@ +/* + * 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 { RuleTester } = require('eslint'); +const useJestMocked = require('../use-jest-mocked'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), +}); + +ruleTester.run('use-jest-mocked', useJestMocked, { + valid: [ + { + code: 'jest.mocked(doSomething)', + }, + ], + invalid: [ + { + code: '(doSomething as jest.Mock).mockImplementation()', + errors: [{ messageId: 'useJestMocked' }], + }, + ], +}); diff --git a/server/sonar-web/eslint-local-rules/convert-class-to-function-component.js b/server/sonar-web/eslint-local-rules/convert-class-to-function-component.js new file mode 100644 index 00000000000..4075ec9a529 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/convert-class-to-function-component.js @@ -0,0 +1,43 @@ +/* + * 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 = { + meta: { + messages: { + convertClassToFunctionComponent: + 'A class component only containing a render() method should be converted to a function component', + }, + }, + create(context) { + return { + ClassDeclaration(node) { + const methods = node.body.body.filter( + (n) => + n.type === 'MethodDefinition' || + (n.type === 'PropertyDefinition' && + n.value && + n.value.type === 'ArrowFunctionExpression') + ); + if (methods.length === 1 && methods[0].key.name === 'render') { + context.report({ node, messageId: 'convertClassToFunctionComponent' }); + } + }, + }; + }, +}; diff --git a/server/sonar-web/eslint-local-rules/index.js b/server/sonar-web/eslint-local-rules/index.js new file mode 100644 index 00000000000..3bd8c8290cc --- /dev/null +++ b/server/sonar-web/eslint-local-rules/index.js @@ -0,0 +1,167 @@ +/* + * 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 = { + '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' + ), +}; 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 new file mode 100644 index 00000000000..3db12928b89 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js @@ -0,0 +1,40 @@ +/* + * 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 = { + meta: { + messages: { + noConditionalRenderingOfDeferredSpinner: + 'For accessibility reasons, you should not conditionally render a . Always render it, and pass a loading prop instead.', + }, + }, + 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' }); + } + }, + }; + }, +}; diff --git a/server/sonar-web/eslint-local-rules/use-enum.js b/server/sonar-web/eslint-local-rules/use-enum.js new file mode 100644 index 00000000000..50a840b97c5 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/use-enum.js @@ -0,0 +1,37 @@ +/* + * 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 = (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` }); + } + }, + }; + }, +}); diff --git a/server/sonar-web/eslint-local-rules/use-jest-mocked.js b/server/sonar-web/eslint-local-rules/use-jest-mocked.js new file mode 100644 index 00000000000..c70cd4128d5 --- /dev/null +++ b/server/sonar-web/eslint-local-rules/use-jest-mocked.js @@ -0,0 +1,38 @@ +/* + * 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 = { + meta: { + messages: { + useJestMocked: 'Prefer using jest.mocked(func) instead of (func as jest.Mock)', + }, + }, + create(context) { + return { + TSAsExpression(node) { + if (node.typeAnnotation.typeName) { + const { left, right } = node.typeAnnotation.typeName; + if (left && left.name === 'jest' && right && right.name === 'Mock') { + context.report({ node, messageId: 'useJestMocked' }); + } + } + }, + }; + }, +}; diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index ee69f7c958d..28011ea62ca 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -83,6 +83,7 @@ "eslint-plugin-jest": "27.2.1", "eslint-plugin-jest-dom": "4.0.3", "eslint-plugin-jsx-a11y": "6.7.1", + "eslint-plugin-local-rules": "1.3.2", "eslint-plugin-promise": "6.1.1", "eslint-plugin-react": "7.32.1", "eslint-plugin-react-hooks": "4.6.0", @@ -114,6 +115,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", "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", diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index 5a1d45ae8fe..08ed270d3b9 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -4503,6 +4503,7 @@ __metadata: eslint-plugin-jest: 27.2.1 eslint-plugin-jest-dom: 4.0.3 eslint-plugin-jsx-a11y: 6.7.1 + eslint-plugin-local-rules: 1.3.2 eslint-plugin-promise: 6.1.1 eslint-plugin-react: 7.32.1 eslint-plugin-react-hooks: 4.6.0 @@ -6206,6 +6207,7 @@ __metadata: autoprefixer: 10.4.13 eslint: 8.32.0 eslint-plugin-header: 3.1.1 + eslint-plugin-local-rules: 1.3.2 eslint-plugin-typescript-sort-keys: 2.1.0 history: 5.3.0 jest: 29.3.1 @@ -7099,6 +7101,13 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-local-rules@npm:1.3.2": + version: 1.3.2 + resolution: "eslint-plugin-local-rules@npm:1.3.2" + checksum: d1d94c0832d7a5e1686c4d685614c407998e7a60318893a405fb8c97c3856b05a8d9479c20ad2799667a59ebfebc4c4f3268528d21d849d2062dee2041b9e909 + languageName: node + linkType: hard + "eslint-plugin-promise@npm:6.1.1": version: 6.1.1 resolution: "eslint-plugin-promise@npm:6.1.1" -- 2.39.5