diff --git a/eslint.config.js b/eslint.config.js index 870d60a458..b0d1ab821c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -409,38 +409,276 @@ export default defineConfig([ 'react-x/prefer-use-state-lazy-initialization': 1, // SonarJS rules - // https://github.com/SonarSource/eslint-plugin-sonarjs#rules - 'sonarjs/no-all-duplicated-branches': 1, - 'sonarjs/no-element-overwrite': 1, - 'sonarjs/no-empty-collection': 1, - 'sonarjs/no-extra-arguments': 0, - 'sonarjs/no-identical-conditions': 1, - 'sonarjs/no-identical-expressions': 1, - 'sonarjs/no-ignored-return': 1, - 'sonarjs/no-use-of-empty-return-value': 1, - 'sonarjs/non-existent-operator': 1, + // https://github.com/SonarSource/SonarJS/blob/master/packages/jsts/src/rules/README.md#rules + 'sonarjs/anchor-precedence': 1, + 'sonarjs/argument-type': 1, + 'sonarjs/arguments-order': 1, + 'sonarjs/arguments-usage': 1, + 'sonarjs/array-callback-without-return': 1, + 'sonarjs/array-constructor': 1, + 'sonarjs/arrow-function-convention': 0, + 'sonarjs/assertions-in-tests': 1, + 'sonarjs/aws-apigateway-public-api': 0, + 'sonarjs/aws-ec2-rds-dms-public': 0, + 'sonarjs/aws-ec2-unencrypted-ebs-volume': 0, + 'sonarjs/aws-efs-unencrypted': 0, + 'sonarjs/aws-iam-all-privileges': 0, + 'sonarjs/aws-iam-all-resources-accessible': 0, + 'sonarjs/aws-iam-privilege-escalation': 0, + 'sonarjs/aws-iam-public-access': 0, + 'sonarjs/aws-opensearchservice-domain': 0, + 'sonarjs/aws-rds-unencrypted-databases': 0, + 'sonarjs/aws-restricted-ip-admin-access': 0, + 'sonarjs/aws-s3-bucket-granted-access': 0, + 'sonarjs/aws-s3-bucket-insecure-http': 0, + 'sonarjs/aws-s3-bucket-public-access': 0, + 'sonarjs/aws-s3-bucket-server-encryption': 0, + 'sonarjs/aws-s3-bucket-versioning': 0, + 'sonarjs/aws-sagemaker-unencrypted-notebook': 0, + 'sonarjs/aws-sns-unencrypted-topics': 0, + 'sonarjs/aws-sqs-unencrypted-queue': 0, + 'sonarjs/bitwise-operators': 1, + 'sonarjs/block-scoped-var': 1, + 'sonarjs/bool-param-default': 0, + 'sonarjs/call-argument-line': 1, + 'sonarjs/certificate-transparency': 1, + 'sonarjs/chai-determinate-assertion': 1, + 'sonarjs/class-name': 1, + 'sonarjs/class-prototype': 1, + 'sonarjs/code-eval': 1, 'sonarjs/cognitive-complexity': 0, + 'sonarjs/comma-or-logical-or-case': 1, + 'sonarjs/comment-regex': 1, + 'sonarjs/concise-regex': 1, + 'sonarjs/conditional-indentation': 1, + 'sonarjs/confidential-information-logging': 1, + 'sonarjs/constructor-for-side-effects': 1, + 'sonarjs/content-length': 1, + 'sonarjs/content-security-policy': 1, + 'sonarjs/cookie-no-httponly': 1, + 'sonarjs/cookies': 1, + 'sonarjs/cors': 1, + 'sonarjs/csrf': 1, + 'sonarjs/cyclomatic-complexity': 0, + 'sonarjs/declarations-in-global-scope': 0, + 'sonarjs/deprecation': 0, + 'sonarjs/destructuring-assignment-syntax': 1, + 'sonarjs/different-types-comparison': 0, + 'sonarjs/disabled-auto-escaping': 1, + 'sonarjs/disabled-resource-integrity': 1, + 'sonarjs/disabled-timeout': 1, + 'sonarjs/dns-prefetching': 1, + 'sonarjs/duplicates-in-character-class': 1, + 'sonarjs/dynamically-constructed-templates': 1, 'sonarjs/elseif-without-else': 0, + 'sonarjs/empty-string-repetition': 1, + 'sonarjs/encryption': 1, + 'sonarjs/encryption-secure-mode': 1, + 'sonarjs/enforce-trailing-comma': 0, + 'sonarjs/existing-groups': 1, + 'sonarjs/expression-complexity': 0, + 'sonarjs/file-header': 0, + 'sonarjs/file-name-differ-from-class': 1, + 'sonarjs/file-permissions': 1, + 'sonarjs/file-uploads': 1, + 'sonarjs/fixme-tag': 1, + 'sonarjs/for-in': 1, + 'sonarjs/for-loop-increment-sign': 1, + 'sonarjs/frame-ancestors': 1, + 'sonarjs/function-inside-loop': 1, + 'sonarjs/function-name': 0, + 'sonarjs/function-return-type': 0, + 'sonarjs/future-reserved-words': 1, + 'sonarjs/generator-without-yield': 1, + 'sonarjs/hardcoded-secret-signatures': 1, + 'sonarjs/hashing': 1, + 'sonarjs/hidden-files': 1, + 'sonarjs/in-operator-type-error': 1, + 'sonarjs/inconsistent-function-call': 1, + 'sonarjs/index-of-compare-to-positive-number': 1, + 'sonarjs/insecure-cookie': 1, + 'sonarjs/insecure-jwt-token': 1, + 'sonarjs/inverted-assertion-arguments': 1, + 'sonarjs/jsx-no-leaked-render': 1, + 'sonarjs/label-position': 1, + 'sonarjs/link-with-target-blank': 1, + 'sonarjs/max-lines': 0, + 'sonarjs/max-lines-per-function': 0, 'sonarjs/max-switch-cases': 0, + 'sonarjs/max-union-size': 0, + 'sonarjs/misplaced-loop-counter': 1, + 'sonarjs/nested-control-flow': 0, + 'sonarjs/new-operator-misuse': 1, + 'sonarjs/no-all-duplicated-branches': 1, + 'sonarjs/no-alphabetical-sort': 1, + 'sonarjs/no-angular-bypass-sanitization': 1, + 'sonarjs/no-array-delete': 1, + 'sonarjs/no-associative-arrays': 1, + 'sonarjs/no-async-constructor': 1, + 'sonarjs/no-built-in-override': 1, + 'sonarjs/no-case-label-in-switch': 1, + 'sonarjs/no-clear-text-protocols': 1, + 'sonarjs/no-code-after-done': 1, 'sonarjs/no-collapsible-if': 1, 'sonarjs/no-collection-size-mischeck': 1, + 'sonarjs/no-commented-code': 1, + 'sonarjs/no-control-regex': 1, + 'sonarjs/no-dead-store': 1, + 'sonarjs/no-delete-var': 1, + 'sonarjs/no-duplicate-in-composite': 1, 'sonarjs/no-duplicate-string': 0, 'sonarjs/no-duplicated-branches': 1, + 'sonarjs/no-element-overwrite': 1, + 'sonarjs/no-empty-after-reluctant': 1, + 'sonarjs/no-empty-alternatives': 1, + 'sonarjs/no-empty-character-class': 1, + 'sonarjs/no-empty-collection': 1, + 'sonarjs/no-empty-group': 1, + 'sonarjs/no-empty-test-file': 1, + 'sonarjs/no-equals-in-for-termination': 1, + 'sonarjs/no-exclusive-tests': 1, + 'sonarjs/no-extra-arguments': 0, + 'sonarjs/no-fallthrough': 1, + 'sonarjs/no-for-in-iterable': 1, + 'sonarjs/no-function-declaration-in-block': 1, + 'sonarjs/no-global-this': 1, + 'sonarjs/no-globals-shadowing': 1, 'sonarjs/no-gratuitous-expressions': 1, + 'sonarjs/no-hardcoded-ip': 1, + 'sonarjs/no-hardcoded-passwords': 1, + 'sonarjs/no-hardcoded-secrets': 1, + 'sonarjs/no-hook-setter-in-body': 1, + 'sonarjs/no-identical-conditions': 1, + 'sonarjs/no-identical-expressions': 1, 'sonarjs/no-identical-functions': 1, + 'sonarjs/no-ignored-exceptions': 1, + 'sonarjs/no-ignored-return': 1, + 'sonarjs/no-implicit-dependencies': 1, + 'sonarjs/no-implicit-global': 1, + 'sonarjs/no-in-misuse': 1, + 'sonarjs/no-incomplete-assertions': 1, + 'sonarjs/no-inconsistent-returns': 0, + 'sonarjs/no-incorrect-string-concat': 1, + 'sonarjs/no-internal-api-use': 1, + 'sonarjs/no-intrusive-permissions': 1, + 'sonarjs/no-invalid-regexp': 1, + 'sonarjs/no-invariant-returns': 1, 'sonarjs/no-inverted-boolean-check': 1, + 'sonarjs/no-ip-forward': 1, + 'sonarjs/no-labels': 1, + 'sonarjs/no-literal-call': 1, + 'sonarjs/no-mime-sniff': 1, + 'sonarjs/no-misleading-array-reverse': 1, + 'sonarjs/no-misleading-character-class': 1, + 'sonarjs/no-mixed-content': 1, + 'sonarjs/no-nested-assignment': 1, + 'sonarjs/no-nested-conditional': 0, + 'sonarjs/no-nested-functions': 0, + 'sonarjs/no-nested-incdec': 1, 'sonarjs/no-nested-switch': 1, 'sonarjs/no-nested-template-literals': 1, + 'sonarjs/no-os-command-from-path': 1, + 'sonarjs/no-parameter-reassignment': 1, + 'sonarjs/no-primitive-wrappers': 1, + 'sonarjs/no-redundant-assignments': 1, 'sonarjs/no-redundant-boolean': 1, 'sonarjs/no-redundant-jump': 1, + 'sonarjs/no-redundant-optional': 1, + 'sonarjs/no-redundant-parentheses': 1, + 'sonarjs/no-reference-error': 0, + 'sonarjs/no-referrer-policy': 1, + 'sonarjs/no-regex-spaces': 1, + 'sonarjs/no-require-or-define': 1, + 'sonarjs/no-return-type-any': 1, + 'sonarjs/no-same-argument-assert': 1, 'sonarjs/no-same-line-conditional': 1, + 'sonarjs/no-selector-parameter': 0, + 'sonarjs/no-skipped-tests': 1, 'sonarjs/no-small-switch': 1, + 'sonarjs/no-sonar-comments': 1, + 'sonarjs/no-tab': 1, + 'sonarjs/no-table-as-layout': 1, + 'sonarjs/no-try-promise': 1, + 'sonarjs/no-undefined-argument': 1, + 'sonarjs/no-undefined-assignment': 0, + 'sonarjs/no-unenclosed-multiline-block': 1, + 'sonarjs/no-uniq-key': 1, + 'sonarjs/no-unsafe-unzip': 1, + 'sonarjs/no-unthrown-error': 1, 'sonarjs/no-unused-collection': 1, + 'sonarjs/no-unused-function-argument': 1, + 'sonarjs/no-unused-vars': 1, + 'sonarjs/no-use-of-empty-return-value': 1, 'sonarjs/no-useless-catch': 1, + 'sonarjs/no-useless-increment': 1, + 'sonarjs/no-useless-intersection': 1, + 'sonarjs/no-useless-react-setstate': 1, + 'sonarjs/no-variable-usage-before-declaration': 1, + 'sonarjs/no-vue-bypass-sanitization': 1, + 'sonarjs/no-weak-cipher': 1, + 'sonarjs/no-weak-keys': 1, + 'sonarjs/no-wildcard-import': 0, + 'sonarjs/non-existent-operator': 1, + 'sonarjs/non-number-in-arithmetic-expression': 1, + 'sonarjs/null-dereference': 1, + 'sonarjs/object-alt-content': 1, + 'sonarjs/operation-returning-nan': 1, + 'sonarjs/os-command': 1, + 'sonarjs/post-message': 1, + 'sonarjs/prefer-default-last': 1, 'sonarjs/prefer-immediate-return': 1, 'sonarjs/prefer-object-literal': 1, + 'sonarjs/prefer-promise-shorthand': 1, + 'sonarjs/prefer-read-only-props': 0, + 'sonarjs/prefer-regexp-exec': 1, 'sonarjs/prefer-single-boolean-return': 1, + 'sonarjs/prefer-type-guard': 1, 'sonarjs/prefer-while': 1, + 'sonarjs/process-argv': 1, + 'sonarjs/production-debug': 1, + 'sonarjs/pseudo-random': 0, + 'sonarjs/public-static-readonly': 1, + 'sonarjs/publicly-writable-directories': 1, + 'sonarjs/reduce-initial-value': 1, + 'sonarjs/redundant-type-aliases': 0, + 'sonarjs/regex-complexity': 1, + 'sonarjs/regular-expr': 1, + 'sonarjs/review-blockchain-mnemonic': 1, + 'sonarjs/session-regeneration': 1, + 'sonarjs/shorthand-property-grouping': 0, + 'sonarjs/single-char-in-character-classes': 1, + 'sonarjs/single-character-alternation': 1, + 'sonarjs/slow-regex': 1, + 'sonarjs/sockets': 1, + 'sonarjs/sql-queries': 1, + 'sonarjs/stable-tests': 1, + 'sonarjs/standard-input': 1, + 'sonarjs/stateful-regex': 1, + 'sonarjs/strict-transport-security': 1, + 'sonarjs/strings-comparison': 1, + 'sonarjs/super-invocation': 1, + 'sonarjs/table-header': 1, + 'sonarjs/table-header-reference': 1, + 'sonarjs/test-check-exception': 1, + 'sonarjs/todo-tag': 0, + 'sonarjs/too-many-break-or-continue-in-loop': 0, + 'sonarjs/unicode-aware-regex': 1, + 'sonarjs/unused-import': 1, + 'sonarjs/unused-named-groups': 1, + 'sonarjs/unverified-certificate': 1, + 'sonarjs/unverified-hostname': 1, + 'sonarjs/updated-const-var': 1, + 'sonarjs/updated-loop-counter': 0, + 'sonarjs/use-type-alias': 1, + 'sonarjs/useless-string-operation': 1, + 'sonarjs/values-not-convertible-to-numbers': 1, + 'sonarjs/variable-name': 1, + 'sonarjs/void-use': 1, + 'sonarjs/weak-ssl': 1, + 'sonarjs/web-sql-database': 1, + 'sonarjs/x-powered-by': 1, + 'sonarjs/xml-parser-xxe': 1, + 'sonarjs/xpath': 1, // @typescript-eslint/eslint-plugin rules // https://typescript-eslint.io/rules/#supported-rules diff --git a/package.json b/package.json index 7ef19a2426..15e562057d 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "eslint-plugin-jest-dom": "^5.5.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-sonarjs": "^3.0.5", + "eslint-plugin-sonarjs": "^3.0.6", "eslint-plugin-testing-library": "^7.13.5", "jspdf": "^4.0.0", "jspdf-autotable": "^5.0.2", diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index b8532c13fd..acc1e646f1 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -596,7 +596,7 @@ export function DataGrid(props: DataGridPr previousRowIdx < rows.length ) { const step = sign(rowIdx - previousRowIdx); - for (let i = previousRowIdx + step; i !== rowIdx; i += step) { + for (let i = previousRowIdx + step; i < rowIdx; i += step) { const row = rows[i]; if (isRowSelectionDisabled?.(row) === true) continue; if (checked) { diff --git a/src/GroupRow.tsx b/src/GroupRow.tsx index abc356682e..e5c59fd895 100644 --- a/src/GroupRow.tsx +++ b/src/GroupRow.tsx @@ -41,7 +41,7 @@ function GroupedRow({ gridRowStart, groupBy, toggleGroup, - isRowSelectionDisabled, + isRowSelectionDisabled: _isRowSelectionDisabled, ...props }: GroupRowRendererProps) { // Select is always the first column diff --git a/test/browser/keyboardNavigation.test.tsx b/test/browser/keyboardNavigation.test.tsx index 437d3327fa..a6e0d04bed 100644 --- a/test/browser/keyboardNavigation.test.tsx +++ b/test/browser/keyboardNavigation.test.tsx @@ -6,11 +6,11 @@ import { getSelectedCell, setup, tabIntoGrid, testCount, validateCellPosition } type Row = undefined; -const rows: readonly Row[] = new Array(100); +const rows: readonly Row[] = Array.from({ length: 100 }); const topSummaryRows: readonly Row[] = [undefined]; const bottomSummaryRows: readonly Row[] = [undefined, undefined]; -const columns = [ +const columns: readonly Column[] = [ SelectColumn, { key: 'col2', name: 'col2' }, { key: 'col3', name: 'col3' }, @@ -18,7 +18,7 @@ const columns = [ { key: 'col5', name: 'col5' }, { key: 'col6', name: 'col6' }, { key: 'col7', name: 'col7' } -] as const satisfies Column[]; +]; test('keyboard navigation', async () => { await setup({ columns, rows, topSummaryRows, bottomSummaryRows }, true); @@ -122,7 +122,7 @@ test('arrow and tab navigation', async () => { }); test('grid enter/exit', async () => { - await setup({ columns, rows: new Array(5), bottomSummaryRows }, true); + await setup({ columns, rows: Array.from({ length: 5 }), bottomSummaryRows }, true); const beforeButton = page.getByRole('button', { name: 'Before' }); const afterButton = page.getByRole('button', { name: 'After' }); @@ -164,7 +164,7 @@ test('grid enter/exit', async () => { }); test('navigation with focusable cell renderer', async () => { - await setup({ columns, rows: new Array(1), bottomSummaryRows }, true); + await setup({ columns, rows: Array.from({ length: 1 }), bottomSummaryRows }, true); await tabIntoGrid(); await userEvent.keyboard('{arrowdown}'); await validateCellPosition(0, 1); @@ -205,7 +205,10 @@ test('navigation when header and summary rows have focusable elements', async () } ]; - await setup({ columns, rows: new Array(2), bottomSummaryRows: [1, 2] }, true); + await setup( + { columns, rows: Array.from({ length: 2 }), bottomSummaryRows: [1, 2] }, + true + ); await tabIntoGrid(); // should set focus on the header filter @@ -320,7 +323,7 @@ test('reset selected cell when row is removed', async () => { }); test('should not change the left and right arrow behavior for right to left languages', async () => { - await setup({ rows, columns, direction: 'rtl' }, true); + await setup({ columns, rows, direction: 'rtl' }, true); await tabIntoGrid(); await validateCellPosition(0, 0); await userEvent.tab(); diff --git a/test/browser/virtualization.test.ts b/test/browser/virtualization.test.ts index da5ab670c4..c3868095bf 100644 --- a/test/browser/virtualization.test.ts +++ b/test/browser/virtualization.test.ts @@ -10,10 +10,10 @@ function setupGrid( frozenColumnCount = 0, summaryRowCount = 0 ) { - const columns: Column[] = []; - const rows = new Array(rowCount); - const topSummaryRows = new Array(summaryRowCount).fill(null); - const bottomSummaryRows = new Array(summaryRowCount).fill(null); + const columns: Column[] = []; + const rows = Array.from({ length: rowCount }); + const topSummaryRows = Array.from({ length: summaryRowCount }, () => null); + const bottomSummaryRows = Array.from({ length: summaryRowCount }, () => null); for (let i = 0; i < columnCount; i++) { const key = String(i);