diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 174966c0b..129a98b98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,32 +18,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Cache Yarn cache - uses: actions/cache@v4 - env: - cache-name: yarn-cache - with: - path: ~/.yarn/berry/cache - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-${{ env.cache-name }} - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Enable Corepack - run: corepack enable - - - name: Install dependencies - run: yarn --immutable - - - name: Build package - run: yarn build + - name: Setup Biome + uses: biomejs/setup-biome@v2 - name: Run tests - run: yarn lint + run: biome lint typescript: name: Type checking @@ -88,29 +67,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Cache Yarn cache - uses: actions/cache@v4 - env: - cache-name: yarn-cache - with: - path: ~/.yarn/berry/cache - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-${{ env.cache-name }} - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Enable Corepack - run: corepack enable - - - name: Install dependencies - run: yarn --immutable + - name: Setup Biome + uses: biomejs/setup-biome@v2 - name: Run formatting - run: yarn format + run: biome format unit: name: Unit tests diff --git a/.husky/pre-commit b/.husky/pre-commit index 372362317..d644d672d 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -yarn lint-staged +yarn format --staged --no-errors-on-unmatched --write diff --git a/.lintstagedrc.json b/.lintstagedrc.json deleted file mode 100644 index 6cb3ca644..000000000 --- a/.lintstagedrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "*.{css,html,js,json,jsx,md,ts,tsx,yml}": "yarn format --write" -} diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 7f77862d7..000000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -.cache -.yarn -yarnrc.yml diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 5ac85e271..000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "printWidth": 100, - "singleQuote": true -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 75326f341..cc1a29140 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,4 +1,4 @@ { - "recommendations": ["dbaeumer.vscode-eslint", "eamodio.gitlens", "esbenp.prettier-vscode"], + "recommendations": ["biomejs.biome", "eamodio.gitlens"], "unwantedRecommendations": ["dbaeumer.jshint"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e3696725c..a6b00a851 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.defaultFormatter": "biomejs.biome", "editor.formatOnSave": true, "search.exclude": { "**/.yarn": true diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..d0aed07e8 --- /dev/null +++ b/biome.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json", + "files": { + "ignore": [".tsimp", "coverage", "dist", ".pnp.cjs", ".pnp.loader.mjs"] + }, + "formatter": { + "lineWidth": 100, + "indentStyle": "space" + }, + "linter": { + "rules": { + "complexity": { + "noUselessSwitchCase": "off" + }, + "suspicious": { + "noConsoleLog": "warn" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "overrides": [ + { + "include": ["**/package.json"], + "formatter": { + "lineWidth": 1 + } + }, + { + "include": ["**/vite.config.ts"], + "linter": { + "rules": { + "suspicious": { + "noConsoleLog": "off" + } + } + } + } + ] +} diff --git a/package.json b/package.json index 58045fe27..11b20a69d 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,7 @@ "unit": "yarn workspaces foreach --all run unit" }, "devDependencies": { - "husky": "^9.0.0", - "lint-staged": "^15.0.0", - "prettier": "^3.2.0" - }, - "resolutions": { - "eslint-plugin-import": "npm:eslint-plugin-i@^2.28.0" + "husky": "^9.0.0" }, "packageManager": "yarn@4.1.1" } diff --git a/packages/react-date-picker/.eslintignore b/packages/react-date-picker/.eslintignore deleted file mode 100644 index de4d1f007..000000000 --- a/packages/react-date-picker/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -node_modules diff --git a/packages/react-date-picker/.eslintrc.json b/packages/react-date-picker/.eslintrc.json deleted file mode 100644 index 1c5b645ce..000000000 --- a/packages/react-date-picker/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "wojtekmaj/react" -} diff --git a/packages/react-date-picker/.prettierignore b/packages/react-date-picker/.prettierignore deleted file mode 100644 index 1521c8b76..000000000 --- a/packages/react-date-picker/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/packages/react-date-picker/package.json b/packages/react-date-picker/package.json index 48bd3ce95..72767652b 100644 --- a/packages/react-date-picker/package.json +++ b/packages/react-date-picker/package.json @@ -85,8 +85,8 @@ "build-js-cjs-package": "echo '{\n \"type\": \"commonjs\"\n}' > dist/cjs/package.json", "clean": "rimraf dist", "copy-styles": "cpy 'src/**/*.css' dist", - "format": "prettier --check . --cache", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "format": "biome format", + "lint": "biome lint", "prepack": "yarn clean && yarn build", "test": "yarn lint && yarn tsc && yarn format && yarn unit", "tsc": "tsc", @@ -115,6 +115,7 @@ "update-input-width": "^1.4.0" }, "devDependencies": { + "@biomejs/biome": "^1.8.0", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0", @@ -122,11 +123,8 @@ "@types/node": "*", "@types/react": "*", "cpy-cli": "^5.0.0", - "eslint": "^8.56.0", - "eslint-config-wojtekmaj": "^1.0.0", "happy-dom": "^12.6.0", "nodemon": "^3.0.0", - "prettier": "^3.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "rimraf": "^3.0.0", diff --git a/packages/react-date-picker/src/DateInput.spec.tsx b/packages/react-date-picker/src/DateInput.spec.tsx index fa14d60f3..126acd999 100644 --- a/packages/react-date-picker/src/DateInput.spec.tsx +++ b/packages/react-date-picker/src/DateInput.spec.tsx @@ -499,11 +499,11 @@ describe('DateInput', () => { const { container } = render(); - const customInputs = container.querySelectorAll('input[data-input]'); + const customInputs = Array.from(container.querySelectorAll('input[data-input]')); - customInputs.forEach((customInput) => { + for (const customInput of customInputs) { fireEvent.change(customInput, { target: { value: '' } }); - }); + } expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith(null, false); diff --git a/packages/react-date-picker/src/DateInput.tsx b/packages/react-date-picker/src/DateInput.tsx index 9302bbb19..d085ca870 100644 --- a/packages/react-date-picker/src/DateInput.tsx +++ b/packages/react-date-picker/src/DateInput.tsx @@ -54,7 +54,7 @@ function getValue( const valueDate = toDate(rawValue); - if (isNaN(valueDate.getTime())) { + if (Number.isNaN(valueDate.getTime())) { throw new Error(`Invalid date: ${value}`); } @@ -143,11 +143,11 @@ function renderCustomInputs( return placeholder.split(pattern).reduce((arr, element, index) => { const divider = element && ( - // eslint-disable-next-line react/no-array-index-key + // biome-ignore lint/suspicious/noArrayIndexKey: index is stable here {element} ); arr.push(divider); - const currentMatch = matches && matches[index]; + const currentMatch = matches?.[index]; if (currentMatch) { const renderFunction = @@ -240,6 +240,7 @@ export default function DateInput({ setIsCalendarOpen(isCalendarOpenProps); }, [isCalendarOpenProps]); + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on props change useEffect(() => { const nextValue = getDetailValueFrom({ value: valueProps, @@ -405,7 +406,7 @@ export default function DateInput({ return; } - const isNumberKey = !isNaN(Number(key)); + const isNumberKey = !Number.isNaN(Number(key)); if (!isNumberKey) { return; @@ -455,12 +456,10 @@ export default function DateInput({ ].filter(filterBoolean); const values: Record = {}; - formElements.forEach((formElement) => { + for (const formElement of formElements) { values[formElement.name] = - 'valueAsNumber' in formElement - ? formElement.valueAsNumber - : Number((formElement as unknown as HTMLInputElement).value); - }); + 'valueAsNumber' in formElement ? formElement.valueAsNumber : Number(formElement.value); + } const isEveryValueEmpty = formElements.every((formElement) => !formElement.value); @@ -570,7 +569,6 @@ export default function DateInput({ key="day" {...commonInputProps} ariaLabel={dayAriaLabel} - // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={index === 0 && autoFocus} inputRef={dayInput} month={month} @@ -593,7 +591,6 @@ export default function DateInput({ key="month" {...commonInputProps} ariaLabel={monthAriaLabel} - // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={index === 0 && autoFocus} inputRef={monthSelect} locale={locale} @@ -612,7 +609,6 @@ export default function DateInput({ key="month" {...commonInputProps} ariaLabel={monthAriaLabel} - // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={index === 0 && autoFocus} inputRef={monthInput} placeholder={monthPlaceholder} @@ -629,7 +625,6 @@ export default function DateInput({ key="year" {...commonInputProps} ariaLabel={yearAriaLabel} - // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={index === 0 && autoFocus} inputRef={yearInput} placeholder={yearPlaceholder} @@ -668,7 +663,7 @@ export default function DateInput({ } return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions + // biome-ignore lint/a11y/useKeyWithClickEvents: This interaction is designed for mouse users only
{renderNativeInput()} {renderCustomInputsInternal()} diff --git a/packages/react-date-picker/src/DateInput/Input.tsx b/packages/react-date-picker/src/DateInput/Input.tsx index c60c9e7cc..615620080 100644 --- a/packages/react-date-picker/src/DateInput/Input.tsx +++ b/packages/react-date-picker/src/DateInput/Input.tsx @@ -2,8 +2,6 @@ import { useEffect, useLayoutEffect } from 'react'; import clsx from 'clsx'; import updateInputWidth, { getFontShorthand } from 'update-input-width'; -/* eslint-disable jsx-a11y/no-autofocus */ - type InputProps = { ariaLabel?: string; autoFocus?: boolean; @@ -95,7 +93,7 @@ function getSelectionString(input: HTMLInputElement) { if ('getSelection' in window) { const selection = window.getSelection(); - return selection && selection.toString(); + return selection?.toString(); } return null; @@ -172,6 +170,7 @@ export default function Input({ { const { container } = render(); const select = container.querySelector('select') as HTMLSelectElement; - const options = select.querySelectorAll('option'); + const options = Array.from(select.querySelectorAll('option')); - options.forEach((option) => { + for (const option of options) { expect(option).not.toBeDisabled(); - }); + } }); it('has all options enabled given minDate in a past year', () => { @@ -138,11 +138,11 @@ describe('MonthSelect', () => { ); const select = container.querySelector('select') as HTMLSelectElement; - const options = select.querySelectorAll('option[value]'); + const options = Array.from(select.querySelectorAll('option[value]')); - options.forEach((option) => { + for (const option of options) { expect(option).not.toBeDisabled(); - }); + } }); it('has first (month in minDate) options disabled given minDate in a current year', () => { @@ -154,13 +154,13 @@ describe('MonthSelect', () => { const options = Array.from(select.querySelectorAll('option')).slice(1); // Getting rid of "--" option // January - June - options.slice(0, 6).forEach((option) => { + for (const option of options.slice(0, 6)) { expect(option).toBeDisabled(); - }); + } // July - December - options.slice(6).forEach((option) => { + for (const option of options.slice(6)) { expect(option).not.toBeDisabled(); - }); + } }); it('has all options enabled given maxDate in a future year', () => { @@ -171,9 +171,9 @@ describe('MonthSelect', () => { const select = container.querySelector('select') as HTMLSelectElement; const options = Array.from(select.querySelectorAll('option')).slice(1); // Getting rid of "--" option - options.forEach((option) => { + for (const option of options) { expect(option).not.toBeDisabled(); - }); + } }); it('has last (month in maxDate) options disabled given maxDate in a current year', () => { @@ -185,12 +185,12 @@ describe('MonthSelect', () => { const options = Array.from(select.querySelectorAll('option')).slice(1); // Getting rid of "--" option // January - July - options.slice(0, 7).forEach((option) => { + for (const option of options.slice(0, 7)) { expect(option).not.toBeDisabled(); - }); + } // August - December - options.slice(7).forEach((option) => { + for (const option of options.slice(7)) { expect(option).toBeDisabled(); - }); + } }); }); diff --git a/packages/react-date-picker/src/DateInput/MonthSelect.tsx b/packages/react-date-picker/src/DateInput/MonthSelect.tsx index 9f255accc..74a7d0665 100644 --- a/packages/react-date-picker/src/DateInput/MonthSelect.tsx +++ b/packages/react-date-picker/src/DateInput/MonthSelect.tsx @@ -4,8 +4,6 @@ import { getYear, getMonthHuman } from '@wojtekmaj/date-utils'; import { formatMonth, formatShortMonth } from '../shared/dateFormatter.js'; import { safeMin, safeMax } from '../shared/utils.js'; -/* eslint-disable jsx-a11y/no-autofocus */ - type MonthSelectProps = { ariaLabel?: string; autoFocus?: boolean; @@ -56,6 +54,7 @@ export default function MonthSelect({ return (